feat: retire closed issues during Following review (Closes #1305)
This commit is contained in:
1 parent
fdf38a72ac
commit
c4bb7a9be1
20 files changed
+278
-46
No files matched your search
@@ -5774,6 +5774,7 @@
|
||||
if (!followingQueue.session()) taskOverlayHistory.update({preview:item});
|
||||
},
|
||||
onOpened:item => followingQueue.previewLoaded(item),
|
||||
afterUnwatch:item => followingQueue.retire(item),
|
||||
navigationRoot:document,
|
||||
onState: renderSearchPreview,
|
||||
});
|
||||
|
||||
+28
-5
@@ -63,7 +63,7 @@
|
||||
async function open(index) {
|
||||
const item = snapshot.items[Number(index)];
|
||||
if (!item) return false;
|
||||
await options.onOpen?.({...item, kind:'issue'});
|
||||
await options.onOpen?.({...item, kind:'issue', following:true});
|
||||
await acknowledge(item);
|
||||
return true;
|
||||
}
|
||||
@@ -71,7 +71,7 @@
|
||||
async function startReview() {
|
||||
const items = snapshot.items
|
||||
.filter(item => item.has_unseen_change === true)
|
||||
.map(item => ({...item, kind:'issue'}));
|
||||
.map(item => ({...item, kind:'issue', following:true}));
|
||||
if (!items.length) return false;
|
||||
review = {items, more:false, active:true, acknowledged:new Set()};
|
||||
await open(snapshot.items.indexOf(snapshot.items.find(item =>
|
||||
@@ -80,8 +80,8 @@
|
||||
}
|
||||
|
||||
function finishReview() {
|
||||
const completed = Boolean(review?.active &&
|
||||
!snapshot.items.some(item => item.has_unseen_change === true));
|
||||
const completed = Boolean(review?.completed || (review?.active &&
|
||||
!snapshot.items.some(item => item.has_unseen_change === true)));
|
||||
if (review) review.active = false;
|
||||
if (completed && !review.completed) {
|
||||
review.completed = true;
|
||||
@@ -91,8 +91,30 @@
|
||||
return completed;
|
||||
}
|
||||
|
||||
function retire(item) {
|
||||
const same = candidate => candidate.repository === item?.repository &&
|
||||
Number(candidate.number) === Number(item?.number);
|
||||
const index = review?.active ? review.items.findIndex(same) : -1;
|
||||
snapshot.items = snapshot.items.filter(candidate => !same(candidate));
|
||||
if (index < 0) {
|
||||
publish('ready');
|
||||
return null;
|
||||
}
|
||||
review.items.splice(index, 1);
|
||||
const next = review.items[index] || null;
|
||||
if (!next) {
|
||||
review.active = false;
|
||||
if (!review.completed) {
|
||||
review.completed = true;
|
||||
options.onReviewComplete?.();
|
||||
}
|
||||
}
|
||||
publish('ready');
|
||||
return next ? {...next} : null;
|
||||
}
|
||||
|
||||
return {
|
||||
load, open, startReview, previewLoaded:acknowledge, finishReview,
|
||||
load, open, startReview, previewLoaded:acknowledge, finishReview, retire,
|
||||
session:() => review?.active ? {items:[...review.items], more:false} : null,
|
||||
items:() => snapshot.items.map(item => ({...item})),
|
||||
count:() => snapshot.items.length,
|
||||
@@ -179,6 +201,7 @@
|
||||
},
|
||||
session:feature.session,
|
||||
previewLoaded:feature.previewLoaded,
|
||||
retire:feature.retire,
|
||||
returnToFollowing() {
|
||||
const completed = feature.finishReview();
|
||||
if (completed) return 'completed-following';
|
||||
|
||||
@@ -34,8 +34,12 @@
|
||||
'/comments?kind=' + encodeURIComponent(item.kind);
|
||||
root.searchPreviewSubscriptionPath = item => root.searchPreviewPath(item).replace(/\?.*$/, '') +
|
||||
'/subscription?kind=' + encodeURIComponent(item.kind);
|
||||
root.searchPreviewSubscriptionOptions = fetchJson => ({
|
||||
root.searchPreviewSubscriptionOptions = fetchJson => {
|
||||
const options = {
|
||||
load:async detail => {
|
||||
if (detail.kind === 'issue' && detail.state === 'closed' && detail.following === true) {
|
||||
return {...detail, watching:true};
|
||||
}
|
||||
if (!(detail.kind === 'issue' && detail.state === 'open')) return detail;
|
||||
const result = await fetchJson(root.searchPreviewSubscriptionPath(detail), {headers:{Accept:'application/json'}});
|
||||
return {...detail, watching:result.watching === true};
|
||||
@@ -43,7 +47,12 @@
|
||||
watch:(detail,watching) => fetchJson(root.searchPreviewSubscriptionPath(detail), {
|
||||
method:watching ? 'PUT' : 'DELETE', headers:{Accept:'application/json'},
|
||||
}),
|
||||
});
|
||||
};
|
||||
options.preview = async item => options.load({...item, ...await fetchJson(root.searchPreviewPath(item), {
|
||||
headers:{Accept:'application/json'},
|
||||
})});
|
||||
return options;
|
||||
};
|
||||
root.searchPreviewWatchStatus = state => ({
|
||||
watching:'Starting watch…', unwatching:'Stopping watch…',
|
||||
watched:'Watching · available in Following. Future activity will appear in Updates.',
|
||||
@@ -52,8 +61,11 @@
|
||||
'watch-error':(state.error?.message || 'Watch status was not changed.') + ' Retry.',
|
||||
})[state.status] || '';
|
||||
root.renderSearchPreviewWatch = (detail, state, button) => {
|
||||
button.hidden = !(detail.kind === 'issue' && detail.state === 'open');
|
||||
button.textContent = detail.watching ? 'Stop watching' : 'Watch issue';
|
||||
const retiring = detail.kind === 'issue' && detail.state === 'closed' &&
|
||||
detail.following === true && detail.watching === true;
|
||||
button.hidden = !(detail.kind === 'issue' && detail.state === 'open') && !retiring;
|
||||
button.textContent = retiring ? 'Stop watching & next' :
|
||||
(detail.watching ? 'Stop watching' : 'Watch issue');
|
||||
button.disabled = state.status === 'watching' || state.status === 'unwatching';
|
||||
};
|
||||
root.wireSearchPreviewWatch = (button, preview, getDetail) => button.addEventListener('click', () => {
|
||||
@@ -120,7 +132,7 @@
|
||||
};
|
||||
}
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
||||
return function createSearchPreview({ fetchJson, fetchConversation, mutate, watch, share, postReply, queueReply, prepareReply, afterReply, hasAttachments, clearAttachments, storage, createOperationId, session, getSession, loadMore, onNavigate, onOpened, navigationRoot, onState }) {
|
||||
return function createSearchPreview({ fetchJson, fetchConversation, mutate, watch, share, postReply, queueReply, prepareReply, afterReply, afterUnwatch, hasAttachments, clearAttachments, storage, createOperationId, session, getSession, loadMore, onNavigate, onOpened, navigationRoot, onState }) {
|
||||
if (Array.isArray(session)) {
|
||||
getSession = session[0];
|
||||
loadMore = () => session[1].loadMore();
|
||||
@@ -385,11 +397,18 @@
|
||||
}
|
||||
const detail = current;
|
||||
publish({ status:watching ? 'watching' : 'unwatching', item:current, detail });
|
||||
watchRequest = watch(detail, watching).then(result => {
|
||||
watchRequest = watch(detail, watching).then(async result => {
|
||||
current = { ...current, watching:result?.watching === true };
|
||||
const status = result?.following_synced === false
|
||||
? 'watch-partial' : (watching ? 'watched' : 'unwatched');
|
||||
publish({ status, item:current, detail:current, result });
|
||||
if (!watching && result?.watching === false && typeof afterUnwatch === 'function') {
|
||||
const next = await afterUnwatch({...current});
|
||||
if (next) {
|
||||
onNavigate?.(next);
|
||||
await api.open(next);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}).catch(error => {
|
||||
publish({ status:'watch-error', item:current, detail, error });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const BASE = new URL('./', self.location.href).pathname;
|
||||
importScripts(BASE + 'static/private-data-registry.js');
|
||||
importScripts(BASE + 'static/background-issue-sync.js');
|
||||
const CACHE = 'stackchain-dashboard-shell-v133';
|
||||
const CACHE = 'stackchain-dashboard-shell-v134';
|
||||
const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href;
|
||||
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
|
||||
const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000;
|
||||
|
||||
Reference in new issue
Block a user