feat: claim work from unread updates (#321)
This commit is contained in:
1 parent
d6c7a495ed
commit
0ea264a942
11 files changed
+244
-7
No files matched your search
@@ -371,6 +371,33 @@
|
||||
authoredOutbox,
|
||||
onStatus: message => { qs('#update-reply-status').textContent = message; },
|
||||
});
|
||||
const updateOwnership = createUpdateOwnership({
|
||||
claim: item => fetchReviewJson(
|
||||
'api/v1/repos/' + item.repository.split('/').map(encodeURIComponent).join('/') +
|
||||
'/issues/' + encodeURIComponent(item.number) + '/claim',
|
||||
{ method:'PATCH', headers:{ Accept:'application/json' } }
|
||||
),
|
||||
addToday: item => {
|
||||
const result = todayWork.add(item);
|
||||
refreshMyWorkView();
|
||||
return result;
|
||||
},
|
||||
onClaimed: item => {
|
||||
if (!lastContextSnapshot) return;
|
||||
const issues = (lastContextSnapshot.issues || []).filter(candidate =>
|
||||
candidate.repository !== item.repository || candidate.number !== item.number
|
||||
);
|
||||
lastContextSnapshot = { ...lastContextSnapshot, issues:issues.concat(item) };
|
||||
paintMyWork(lastContextSnapshot);
|
||||
},
|
||||
onState: state => {
|
||||
const button = qs('#update-ownership-action');
|
||||
button.hidden = state.action === 'hidden';
|
||||
button.disabled = state.busy;
|
||||
button.textContent = state.action === 'today' ? 'Add to Today' : 'Take ownership';
|
||||
if (state.message) qs('#update-sheet-status').textContent = state.message;
|
||||
},
|
||||
});
|
||||
const notificationReader = createNotificationReader({
|
||||
load: fetchNotificationDetail,
|
||||
loadConversation: fetchNotificationConversation,
|
||||
@@ -390,6 +417,7 @@
|
||||
qs('#update-reply').value = notificationReplier.loadDraft(item);
|
||||
qs('#update-reply-status').textContent = '';
|
||||
qs('#send-update-reply').disabled = false;
|
||||
qs('#update-ownership-action').hidden = true;
|
||||
qs('#retry-update-load').hidden = true;
|
||||
qs('#keep-update-unread').focus();
|
||||
},
|
||||
@@ -399,6 +427,7 @@
|
||||
qs('#update-subject-state').textContent = detail.state || '';
|
||||
qs('#update-subject-body').textContent = detail.subject_body || 'No subject context was provided.';
|
||||
qs('#open-update-gitea').href = detail.url || selectedUpdate?.url || '#';
|
||||
updateOwnership.open(detail, selectedUpdate);
|
||||
qs('#retry-update-load').hidden = true;
|
||||
},
|
||||
onConversation: renderUpdateConversation,
|
||||
@@ -2700,6 +2729,7 @@
|
||||
}
|
||||
});
|
||||
qs('#keep-update-unread').addEventListener('click', () => closeUpdateSheet(true));
|
||||
qs('#update-ownership-action').addEventListener('click', () => updateOwnership.act());
|
||||
qs('#retry-update-load').addEventListener('click', () => {
|
||||
if (selectedUpdate) notificationReader.open(selectedUpdate, lastMyWork);
|
||||
});
|
||||
|
||||
@@ -378,6 +378,7 @@
|
||||
<div id="update-reply-status" class="small" aria-live="assertive"></div>
|
||||
</section>
|
||||
<div class="update-sheet-actions">
|
||||
<button id="update-ownership-action" type="button" hidden aria-describedby="update-sheet-status">Take ownership</button>
|
||||
<button class="share-work-route" type="button">Share</button>
|
||||
<button id="mark-update-read-next" type="button">Mark read & next</button>
|
||||
<a id="open-update-gitea" href="#" target="_blank" rel="noopener noreferrer">Open in Gitea</a>
|
||||
@@ -519,6 +520,7 @@
|
||||
<script src="static/offline-work.js"></script>
|
||||
<script src="static/my-work.js"></script>
|
||||
<script src="static/today-work.js"></script>
|
||||
<script src="static/update-ownership.js"></script>
|
||||
<script src="static/later-work.js"></script>
|
||||
<script src="static/detail-defer.js"></script>
|
||||
<script src="static/pick-work.js"></script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const BASE = new URL('./', self.location.href).pathname;
|
||||
importScripts(BASE + 'static/background-issue-sync.js');
|
||||
const CACHE = 'stackchain-dashboard-shell-v34';
|
||||
const CACHE = 'stackchain-dashboard-shell-v35';
|
||||
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
|
||||
const SHELL = [
|
||||
BASE,
|
||||
@@ -22,6 +22,7 @@ const SHELL = [
|
||||
BASE + 'static/offline-work.js',
|
||||
BASE + 'static/my-work.js',
|
||||
BASE + 'static/today-work.js',
|
||||
BASE + 'static/update-ownership.js',
|
||||
BASE + 'static/later-work.js',
|
||||
BASE + 'static/detail-defer.js',
|
||||
BASE + 'static/pick-work.js',
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
function createUpdateOwnership({ claim, addToday, onClaimed, onState }) {
|
||||
let detail = null;
|
||||
let update = null;
|
||||
let claimedItem = null;
|
||||
let request = null;
|
||||
|
||||
function state(value) {
|
||||
onState?.(value);
|
||||
}
|
||||
|
||||
function open(nextDetail, nextUpdate) {
|
||||
detail = nextDetail;
|
||||
update = nextUpdate;
|
||||
claimedItem = null;
|
||||
request = null;
|
||||
state({
|
||||
action: nextDetail?.issue?.claimable ? 'claim' : 'hidden',
|
||||
busy: false,
|
||||
message: '',
|
||||
});
|
||||
}
|
||||
|
||||
function act() {
|
||||
if (request) return request;
|
||||
if (claimedItem) {
|
||||
const result = addToday(claimedItem);
|
||||
const messages = {
|
||||
added: 'Added to Today.',
|
||||
exists: 'This issue is already in Today.',
|
||||
full: 'Today is limited to 5 items. Remove one before adding more.',
|
||||
unavailable: 'Could not save Today on this device.',
|
||||
};
|
||||
state({ action: 'today', busy: false, message: messages[result] || messages.unavailable });
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
if (!detail?.issue?.claimable) return Promise.resolve('hidden');
|
||||
|
||||
state({ action: 'claim', busy: true, message: 'Assigning…' });
|
||||
request = Promise.resolve(claim({
|
||||
repository: detail.repository,
|
||||
number: detail.issue.number,
|
||||
})).then(result => {
|
||||
claimedItem = {
|
||||
...result,
|
||||
repository: detail.repository,
|
||||
kind: 'issue',
|
||||
notification_id: update?.notification_id,
|
||||
updated_at: update?.updated_at || result.updated_at,
|
||||
has_update: true,
|
||||
};
|
||||
detail.issue.claimable = false;
|
||||
onClaimed?.(claimedItem);
|
||||
state({ action: 'today', busy: false, message: 'Assigned to you. The update is still unread.' });
|
||||
return claimedItem;
|
||||
}).catch(error => {
|
||||
if (error?.status === 409) {
|
||||
detail.issue.claimable = false;
|
||||
state({
|
||||
action: 'hidden',
|
||||
busy: false,
|
||||
message: 'Someone else claimed or closed this issue. The update is still unread.',
|
||||
});
|
||||
return 'conflict';
|
||||
}
|
||||
state({
|
||||
action: 'claim',
|
||||
busy: false,
|
||||
message: (error?.message || 'The issue could not be assigned.') + ' The update and your reply draft are safe; retry.',
|
||||
});
|
||||
return 'retry';
|
||||
}).finally(() => { request = null; });
|
||||
return request;
|
||||
}
|
||||
|
||||
return { open, act };
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = createUpdateOwnership;
|
||||
Reference in new issue
Block a user