feat: notif lock/unlock for duplicate processing prevention, dismiss auto-logs activity
This commit is contained in:
parent
cf2752d807
commit
f37f3d62f6
2 changed files with 69 additions and 4 deletions
61
bin/argus
61
bin/argus
|
|
@ -705,7 +705,7 @@ def cmd_notif_list(args) -> None:
|
||||||
params = []
|
params = []
|
||||||
|
|
||||||
if args.pending:
|
if args.pending:
|
||||||
conditions.append("dismissed = 0 AND acted_at IS NULL")
|
conditions.append("dismissed = 0 AND acted_at IS NULL AND processing = 0")
|
||||||
if getattr(args, 'dismissed', False):
|
if getattr(args, 'dismissed', False):
|
||||||
conditions.append("dismissed = 1")
|
conditions.append("dismissed = 1")
|
||||||
if getattr(args, 'own', False):
|
if getattr(args, 'own', False):
|
||||||
|
|
@ -726,7 +726,11 @@ def cmd_notif_list(args) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
for n in notifications:
|
for n in notifications:
|
||||||
status = "✓" if n["acted_at"] else ("✗" if n["dismissed"] else "○")
|
try:
|
||||||
|
locked = n["processing"]
|
||||||
|
except (IndexError, KeyError):
|
||||||
|
locked = 0
|
||||||
|
status = "✓" if n["acted_at"] else ("✗" if n["dismissed"] else ("🔒" if locked else "○"))
|
||||||
own_tag = " 🔵" if n["is_own_pr"] else ""
|
own_tag = " 🔵" if n["is_own_pr"] else ""
|
||||||
ci_tag = f" [CI:{n['ci_status']}]" if n["ci_status"] else ""
|
ci_tag = f" [CI:{n['ci_status']}]" if n["ci_status"] else ""
|
||||||
print(f"{status}{own_tag}{ci_tag} [{n['id'][:8]}] {n['title'][:60]}")
|
print(f"{status}{own_tag}{ci_tag} [{n['id'][:8]}] {n['title'][:60]}")
|
||||||
|
|
@ -773,6 +777,41 @@ def cmd_notif_act(args) -> None:
|
||||||
|
|
||||||
print(f"✓ Marked notification as acted: {action}")
|
print(f"✓ Marked notification as acted: {action}")
|
||||||
|
|
||||||
|
def cmd_notif_lock(args) -> None:
|
||||||
|
"""Lock notification to prevent duplicate processing."""
|
||||||
|
conn = get_db()
|
||||||
|
notif = conn.execute(
|
||||||
|
"SELECT * FROM notifications WHERE id LIKE ?",
|
||||||
|
(f"{args.id}%",)
|
||||||
|
).fetchone()
|
||||||
|
if not notif:
|
||||||
|
print(f"Error: Notification not found: {args.id}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if notif["processing"]:
|
||||||
|
print(f"⚠ Already locked: {notif['id'][:8]}")
|
||||||
|
sys.exit(1)
|
||||||
|
conn.execute("UPDATE notifications SET processing = 1 WHERE id = ?", (notif["id"],))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print(f"🔒 Locked: {notif['id'][:8]}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_notif_unlock(args) -> None:
|
||||||
|
"""Unlock notification after processing completes."""
|
||||||
|
conn = get_db()
|
||||||
|
notif = conn.execute(
|
||||||
|
"SELECT * FROM notifications WHERE id LIKE ?",
|
||||||
|
(f"{args.id}%",)
|
||||||
|
).fetchone()
|
||||||
|
if not notif:
|
||||||
|
print(f"Error: Notification not found: {args.id}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
conn.execute("UPDATE notifications SET processing = 0 WHERE id = ?", (notif["id"],))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print(f"🔓 Unlocked: {notif['id'][:8]}")
|
||||||
|
|
||||||
|
|
||||||
def cmd_notif_dismiss(args) -> None:
|
def cmd_notif_dismiss(args) -> None:
|
||||||
"""Dismiss notification."""
|
"""Dismiss notification."""
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
|
|
@ -795,6 +834,14 @@ def cmd_notif_dismiss(args) -> None:
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
""", (f"dismissed: {reason}", notif["id"]))
|
""", (f"dismissed: {reason}", notif["id"]))
|
||||||
|
|
||||||
|
# Auto-log activity
|
||||||
|
title = notif["title"] or "-"
|
||||||
|
detail = f"Dismissed: {reason} — {title}"
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO activity_log (issue_id, action, detail, github_url) VALUES (?, ?, ?, ?)",
|
||||||
|
(notif["repo_id"], "dismiss", detail, notif["url"])
|
||||||
|
)
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
@ -1146,6 +1193,12 @@ def main():
|
||||||
dismiss_parser.add_argument("id", help="Notification ID (prefix match)")
|
dismiss_parser.add_argument("id", help="Notification ID (prefix match)")
|
||||||
dismiss_parser.add_argument("reason", nargs="?", help="Dismissal reason")
|
dismiss_parser.add_argument("reason", nargs="?", help="Dismissal reason")
|
||||||
|
|
||||||
|
lock_parser = notif_sub.add_parser("lock", help="Lock notification for processing")
|
||||||
|
lock_parser.add_argument("id", help="Notification ID (prefix match)")
|
||||||
|
|
||||||
|
unlock_parser = notif_sub.add_parser("unlock", help="Unlock notification after processing")
|
||||||
|
unlock_parser.add_argument("id", help="Notification ID (prefix match)")
|
||||||
|
|
||||||
audit_parser = notif_sub.add_parser("audit", help="Audit stale notifications")
|
audit_parser = notif_sub.add_parser("audit", help="Audit stale notifications")
|
||||||
audit_parser.add_argument("--stale-hours", type=int, help="Hours to consider stale (default: 48)")
|
audit_parser.add_argument("--stale-hours", type=int, help="Hours to consider stale (default: 48)")
|
||||||
|
|
||||||
|
|
@ -1217,6 +1270,10 @@ def main():
|
||||||
cmd_notif_act(args)
|
cmd_notif_act(args)
|
||||||
elif args.notif_cmd == "dismiss":
|
elif args.notif_cmd == "dismiss":
|
||||||
cmd_notif_dismiss(args)
|
cmd_notif_dismiss(args)
|
||||||
|
elif args.notif_cmd == "lock":
|
||||||
|
cmd_notif_lock(args)
|
||||||
|
elif args.notif_cmd == "unlock":
|
||||||
|
cmd_notif_unlock(args)
|
||||||
elif args.notif_cmd == "audit":
|
elif args.notif_cmd == "audit":
|
||||||
cmd_notif_audit(args)
|
cmd_notif_audit(args)
|
||||||
elif args.notif_cmd == "filter":
|
elif args.notif_cmd == "filter":
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@
|
||||||
|
|
||||||
// Notifications table — show pending (non-dismissed) by default
|
// Notifications table — show pending (non-dismissed) by default
|
||||||
const notifications = await fetchData('notifications',
|
const notifications = await fetchData('notifications',
|
||||||
'SELECT * FROM notifications WHERE dismissed = 0 ORDER BY seen_at DESC LIMIT 30');
|
'SELECT * FROM notifications WHERE dismissed = 0 AND processing = 0 ORDER BY seen_at DESC LIMIT 30');
|
||||||
|
|
||||||
if (notifications.length === 0) {
|
if (notifications.length === 0) {
|
||||||
document.getElementById('notifications-table').innerHTML =
|
document.getElementById('notifications-table').innerHTML =
|
||||||
|
|
@ -302,7 +302,15 @@
|
||||||
const ownTag = n.is_own_pr ? '🔵 ' : '';
|
const ownTag = n.is_own_pr ? '🔵 ' : '';
|
||||||
const ciTag = n.ci_status ? ` [CI:${n.ci_status}]` : '';
|
const ciTag = n.ci_status ? ` [CI:${n.ci_status}]` : '';
|
||||||
const repo_short = n.repo_id ? n.repo_id.split('/').pop() : '-';
|
const repo_short = n.repo_id ? n.repo_id.split('/').pop() : '-';
|
||||||
const issue = n.url ? `<a href="${n.url}" target="_blank">${repo_short}</a>` : repo_short;
|
let issueLabel = repo_short;
|
||||||
|
if (n.url) {
|
||||||
|
const m = n.url.match(/\/(pull|issues)\/(\d+)/);
|
||||||
|
if (m) {
|
||||||
|
const prefix = m[1] === 'pull' ? 'PR' : 'IS';
|
||||||
|
issueLabel = `${repo_short} ${prefix} #${m[2]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const issue = n.url ? `<a href="${n.url}" target="_blank">${issueLabel}</a>` : issueLabel;
|
||||||
const reasonClass = n.reason === 'mention' ? 'accent' :
|
const reasonClass = n.reason === 'mention' ? 'accent' :
|
||||||
n.reason === 'author' ? 'success' :
|
n.reason === 'author' ? 'success' :
|
||||||
n.reason === 'review_requested' ? 'warning' : '';
|
n.reason === 'review_requested' ? 'warning' : '';
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue