Compare commits
No commits in common. "6185f6a7ff4e2b904ff852f47e4adf982af71f24" and "3decdb4cacd965ed7ebe8872011a471072285a01" have entirely different histories.
6185f6a7ff
...
3decdb4cac
|
|
@ -14,25 +14,6 @@ function issueDueState(dueDate, now) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function urgencyReason(item) {
|
|
||||||
if (!item?.is_assigned || item.kind !== 'issue') return '';
|
|
||||||
const priorityLabels = ['p0', 'priority-high', 'critical'];
|
|
||||||
const priorityLabel = (item.labels || []).find(label =>
|
|
||||||
priorityLabels.includes(String(label).toLowerCase())
|
|
||||||
);
|
|
||||||
if (priorityLabel) return priorityLabel + ' priority';
|
|
||||||
return ['Overdue', 'Due today'].includes(item.due_label) ? item.due_label : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function attentionReason(item) {
|
|
||||||
if (item?.has_update) return 'Unread update';
|
|
||||||
return urgencyReason(item) || (item?.is_review ? 'Needs your review' : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function needsAttention(item) {
|
|
||||||
return Boolean(item && (item.has_update || item.is_review || urgencyReason(item)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildMyWork(data, now = new Date()) {
|
function buildMyWork(data, now = new Date()) {
|
||||||
const login = data.user?.login || '';
|
const login = data.user?.login || '';
|
||||||
const issues = (data.issues || []).map((item) => ({ ...item, kind: 'issue' }));
|
const issues = (data.issues || []).map((item) => ({ ...item, kind: 'issue' }));
|
||||||
|
|
@ -47,7 +28,7 @@ function buildMyWork(data, now = new Date()) {
|
||||||
const assigned = (item.assignees || []).includes(login);
|
const assigned = (item.assignees || []).includes(login);
|
||||||
const isReview = (item.work_reasons || []).includes('review_requested');
|
const isReview = (item.work_reasons || []).includes('review_requested');
|
||||||
const due = item.kind === 'issue' ? issueDueState(item.due_date, now) : null;
|
const due = item.kind === 'issue' ? issueDueState(item.due_date, now) : null;
|
||||||
const normalized = {
|
return {
|
||||||
...item,
|
...item,
|
||||||
key: (item.repository || 'unknown') + '#' + item.number,
|
key: (item.repository || 'unknown') + '#' + item.number,
|
||||||
is_review: isReview,
|
is_review: isReview,
|
||||||
|
|
@ -60,9 +41,6 @@ function buildMyWork(data, now = new Date()) {
|
||||||
_priority: priorityLabel ? 0 :
|
_priority: priorityLabel ? 0 :
|
||||||
(due && due.priority < 4 ? due.priority : (isReview ? 3 : (assigned ? 4 : 5))),
|
(due && due.priority < 4 ? due.priority : (isReview ? 3 : (assigned ? 4 : 5))),
|
||||||
};
|
};
|
||||||
normalized.needs_attention = needsAttention(normalized);
|
|
||||||
normalized.attention_reason = attentionReason(normalized);
|
|
||||||
return normalized;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const byKey = new Map(work.map((item) => [item.kind + ':' + item.key, item]));
|
const byKey = new Map(work.map((item) => [item.kind + ':' + item.key, item]));
|
||||||
|
|
@ -72,8 +50,6 @@ function buildMyWork(data, now = new Date()) {
|
||||||
const existing = byKey.get(subjectKind + ':' + key);
|
const existing = byKey.get(subjectKind + ':' + key);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.has_update = true;
|
existing.has_update = true;
|
||||||
existing.needs_attention = true;
|
|
||||||
existing.attention_reason = 'Unread update';
|
|
||||||
existing.notification_id = update.id;
|
existing.notification_id = update.id;
|
||||||
existing.url = update.url || existing.url;
|
existing.url = update.url || existing.url;
|
||||||
existing.updated_at = update.updated_at || existing.updated_at;
|
existing.updated_at = update.updated_at || existing.updated_at;
|
||||||
|
|
@ -88,8 +64,6 @@ function buildMyWork(data, now = new Date()) {
|
||||||
is_review: false,
|
is_review: false,
|
||||||
is_assigned: false,
|
is_assigned: false,
|
||||||
has_update: true,
|
has_update: true,
|
||||||
needs_attention: true,
|
|
||||||
attention_reason: 'Unread update',
|
|
||||||
notification_id: update.id,
|
notification_id: update.id,
|
||||||
reason: 'Unread update',
|
reason: 'Unread update',
|
||||||
_priority: 1,
|
_priority: 1,
|
||||||
|
|
@ -110,10 +84,7 @@ function acknowledgeNotification(items, notificationId) {
|
||||||
if (item.notification_id !== notificationId) return [item];
|
if (item.notification_id !== notificationId) return [item];
|
||||||
if (item.kind === 'update') return [];
|
if (item.kind === 'update') return [];
|
||||||
const { notification_id, ...acknowledged } = item;
|
const { notification_id, ...acknowledged } = item;
|
||||||
const next = { ...acknowledged, has_update: false };
|
return [{ ...acknowledged, has_update: false }];
|
||||||
if ('needs_attention' in item) next.needs_attention = needsAttention(next);
|
|
||||||
if ('attention_reason' in item) next.attention_reason = attentionReason(next);
|
|
||||||
return [next];
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -434,7 +405,7 @@ function createNotificationReplier({
|
||||||
|
|
||||||
function filterMyWork(items, selectedFilter, selectedMilestone = 'all') {
|
function filterMyWork(items, selectedFilter, selectedMilestone = 'all') {
|
||||||
let filtered = items;
|
let filtered = items;
|
||||||
if (selectedFilter === 'attention') filtered = items.filter(needsAttention);
|
if (selectedFilter === 'attention') filtered = items.filter((item) => item.has_update || item.is_review);
|
||||||
else if (selectedFilter === 'review') filtered = items.filter((item) => item.is_review);
|
else if (selectedFilter === 'review') filtered = items.filter((item) => item.is_review);
|
||||||
else if (selectedFilter === 'update') filtered = items.filter((item) => item.has_update);
|
else if (selectedFilter === 'update') filtered = items.filter((item) => item.has_update);
|
||||||
else if (selectedFilter !== 'all') filtered = items.filter((item) => item.kind === selectedFilter);
|
else if (selectedFilter !== 'all') filtered = items.filter((item) => item.kind === selectedFilter);
|
||||||
|
|
@ -594,7 +565,7 @@ function summarizeMyWork(items) {
|
||||||
function countMyWork(items) {
|
function countMyWork(items) {
|
||||||
return {
|
return {
|
||||||
all: items.length,
|
all: items.length,
|
||||||
attention: items.filter(needsAttention).length,
|
attention: items.filter((item) => item.has_update || item.is_review).length,
|
||||||
issue: items.filter((item) => item.kind === 'issue').length,
|
issue: items.filter((item) => item.kind === 'issue').length,
|
||||||
pull: items.filter((item) => item.kind === 'pull' && !item.is_review).length,
|
pull: items.filter((item) => item.kind === 'pull' && !item.is_review).length,
|
||||||
review: items.filter((item) => item.is_review).length,
|
review: items.filter((item) => item.is_review).length,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
const BASE = new URL('./', self.location.href).pathname;
|
const BASE = new URL('./', self.location.href).pathname;
|
||||||
importScripts(BASE + 'static/background-issue-sync.js');
|
importScripts(BASE + 'static/background-issue-sync.js');
|
||||||
const CACHE = 'stackchain-dashboard-shell-v19';
|
const CACHE = 'stackchain-dashboard-shell-v18';
|
||||||
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
|
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
|
||||||
const SHELL = [
|
const SHELL = [
|
||||||
BASE,
|
BASE,
|
||||||
|
|
|
||||||
|
|
@ -313,58 +313,6 @@ process.stdout.write(JSON.stringify(queue.map(item => ({{title:item.title,reason
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_attention_includes_only_assigned_deadline_and_priority_critical_work():
|
|
||||||
payload = {
|
|
||||||
"user": {"login": "timmy"},
|
|
||||||
"issues": [
|
|
||||||
{"number": 1, "title": "Assigned P0", "repository": "stackchain/api",
|
|
||||||
"labels": ["P0"], "assignees": ["timmy"], "due_date": "2026-08-10T23:59:59Z"},
|
|
||||||
{"number": 2, "title": "Assigned overdue", "repository": "stackchain/api",
|
|
||||||
"labels": [], "assignees": ["timmy"], "due_date": "2026-08-06T23:59:59Z"},
|
|
||||||
{"number": 3, "title": "Assigned due today", "repository": "stackchain/api",
|
|
||||||
"labels": [], "assignees": ["timmy"], "due_date": "2026-08-07T23:59:59Z"},
|
|
||||||
{"number": 4, "title": "Assigned future", "repository": "stackchain/api",
|
|
||||||
"labels": [], "assignees": ["timmy"], "due_date": "2026-08-10T23:59:59Z"},
|
|
||||||
{"number": 5, "title": "Unassigned P0", "repository": "stackchain/api",
|
|
||||||
"labels": ["critical"], "assignees": [], "due_date": "2026-08-10T23:59:59Z"},
|
|
||||||
{"number": 6, "title": "Unassigned overdue", "repository": "stackchain/api",
|
|
||||||
"labels": [], "assignees": [], "due_date": "2026-08-06T23:59:59Z"},
|
|
||||||
],
|
|
||||||
"pull_requests": [
|
|
||||||
{"number": 7, "title": "Updated review", "repository": "stackchain/web",
|
|
||||||
"labels": [], "assignees": [], "work_reasons": ["review_requested"]},
|
|
||||||
],
|
|
||||||
"notifications": [
|
|
||||||
{"id": 70, "number": 7, "repository": "stackchain/web", "unread": True,
|
|
||||||
"subject_type": "PullRequest", "url": "https://forge.example/web/pulls/7"},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
script = f"""
|
|
||||||
const buildMyWork = require({json.dumps(str(MY_WORK))});
|
|
||||||
const items = buildMyWork({json.dumps(payload)}, new Date('2026-08-07T12:00:00Z'));
|
|
||||||
const attention = buildMyWork.filterMyWork(items, 'attention');
|
|
||||||
process.stdout.write(JSON.stringify({{
|
|
||||||
titles: attention.map(item => item.title),
|
|
||||||
count: buildMyWork.countMyWork(items).attention,
|
|
||||||
reasons: Object.fromEntries(attention.map(item => [item.title, item.attention_reason])),
|
|
||||||
}}));
|
|
||||||
"""
|
|
||||||
result = subprocess.run(
|
|
||||||
["node", "-e", script], check=True, capture_output=True, text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
assert json.loads(result.stdout) == {
|
|
||||||
"titles": ["Assigned P0", "Updated review", "Assigned overdue", "Assigned due today"],
|
|
||||||
"count": 4,
|
|
||||||
"reasons": {
|
|
||||||
"Assigned P0": "P0 priority",
|
|
||||||
"Updated review": "Unread update",
|
|
||||||
"Assigned overdue": "Overdue",
|
|
||||||
"Assigned due today": "Due today",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_confirmed_issue_labels_replace_snapshot_and_reprioritize_queue():
|
def test_confirmed_issue_labels_replace_snapshot_and_reprioritize_queue():
|
||||||
payload = {
|
payload = {
|
||||||
"user": {"login": "timmy"},
|
"user": {"login": "timmy"},
|
||||||
|
|
|
||||||
|
|
@ -91,10 +91,10 @@ async function dispatchNotificationClick(route) {{
|
||||||
return json.loads(completed.stdout)
|
return json.loads(completed.stdout)
|
||||||
|
|
||||||
|
|
||||||
def test_deadline_attention_flow_ships_in_a_new_shell_cache():
|
def test_session_resume_flow_ships_in_a_new_shell_cache():
|
||||||
source = WORKER.read_text()
|
source = WORKER.read_text()
|
||||||
|
|
||||||
assert "stackchain-dashboard-shell-v19" in source
|
assert "stackchain-dashboard-shell-v18" in source
|
||||||
|
|
||||||
|
|
||||||
def test_background_sync_event_flushes_closed_app_issue_outbox_only_for_its_tag():
|
def test_background_sync_event_flushes_closed_app_issue_outbox_only_for_its_tag():
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user