feat: keep Following changes for later (Closes #1320)
This commit is contained in:
1 parent
f168a0187d
commit
c3cb498ce1
25 files changed
+296
-47
No files matched your search
+53
-4
@@ -97,7 +97,7 @@ class FollowingStore:
|
||||
or len(last_seen_updated_at) > 64
|
||||
):
|
||||
raise ValueError("last seen update is invalid")
|
||||
return {
|
||||
item = {
|
||||
"repository": repository,
|
||||
"kind": kind,
|
||||
"number": number,
|
||||
@@ -107,6 +107,12 @@ class FollowingStore:
|
||||
"url": url,
|
||||
"last_seen_updated_at": last_seen_updated_at,
|
||||
}
|
||||
kept_updated_at = raw.get("kept_updated_at")
|
||||
if kept_updated_at is not None:
|
||||
if not isinstance(kept_updated_at, str) or not kept_updated_at or len(kept_updated_at) > 64:
|
||||
raise ValueError("kept update is invalid")
|
||||
item["kept_updated_at"] = kept_updated_at
|
||||
return item
|
||||
|
||||
@classmethod
|
||||
def _present(cls, snapshot: dict) -> dict:
|
||||
@@ -114,8 +120,10 @@ class FollowingStore:
|
||||
unchanged = []
|
||||
for raw in snapshot["items"]:
|
||||
stored = cls._normalize_item(raw)
|
||||
unseen = stored["updated_at"] != stored["last_seen_updated_at"]
|
||||
item = {key: value for key, value in stored.items() if key != "last_seen_updated_at"}
|
||||
unseen = (stored["updated_at"] != stored["last_seen_updated_at"] or
|
||||
stored.get("kept_updated_at") == stored["updated_at"])
|
||||
item = {key: value for key, value in stored.items()
|
||||
if key not in {"last_seen_updated_at", "kept_updated_at"}}
|
||||
item["has_unseen_change"] = unseen
|
||||
(changed if unseen else unchanged).append(item)
|
||||
changed.sort(key=lambda item: item["updated_at"], reverse=True)
|
||||
@@ -186,6 +194,8 @@ class FollowingStore:
|
||||
items.insert(0, item)
|
||||
else:
|
||||
item["last_seen_updated_at"] = items[index]["last_seen_updated_at"]
|
||||
if "kept_updated_at" in items[index]:
|
||||
item["kept_updated_at"] = items[index]["kept_updated_at"]
|
||||
if items[index] == item:
|
||||
return self._present({"revision": current["revision"], "items": items})
|
||||
items.pop(index)
|
||||
@@ -219,6 +229,8 @@ class FollowingStore:
|
||||
if update is None:
|
||||
continue
|
||||
update["last_seen_updated_at"] = item["last_seen_updated_at"]
|
||||
if "kept_updated_at" in item:
|
||||
update["kept_updated_at"] = item["kept_updated_at"]
|
||||
if update != item:
|
||||
items[index] = update
|
||||
changed = True
|
||||
@@ -256,8 +268,45 @@ class FollowingStore:
|
||||
for item in items:
|
||||
if self._identity(item) != identity or item["updated_at"] != updated_at:
|
||||
continue
|
||||
if item["last_seen_updated_at"] != updated_at:
|
||||
if (item["last_seen_updated_at"] != updated_at or
|
||||
item.get("kept_updated_at") == updated_at):
|
||||
item["last_seen_updated_at"] = updated_at
|
||||
item.pop("kept_updated_at", None)
|
||||
revision += 1
|
||||
connection.execute(
|
||||
"UPDATE following_issues SET revision = ?, items = ? WHERE login = ?",
|
||||
(revision, self._seal(login, items), login),
|
||||
)
|
||||
break
|
||||
return self._present({"revision": revision, "items": items})
|
||||
|
||||
def keep_unseen(
|
||||
self,
|
||||
login: str,
|
||||
repository: str,
|
||||
number: int,
|
||||
updated_at: str,
|
||||
*,
|
||||
kind: str = "issue",
|
||||
) -> dict:
|
||||
"""Restore only the exact loaded revision to the unseen review queue."""
|
||||
login = self._login(login)
|
||||
if kind not in _KINDS:
|
||||
raise ValueError("kind is invalid")
|
||||
identity = (kind, str(repository).lower(), number)
|
||||
with self._connect() as connection:
|
||||
connection.execute("BEGIN IMMEDIATE")
|
||||
row = connection.execute(
|
||||
"SELECT revision, items FROM following_issues WHERE login = ?", (login,)
|
||||
).fetchone()
|
||||
current, _legacy = self._snapshot(row, login)
|
||||
items = [self._normalize_item(candidate) for candidate in current["items"]]
|
||||
revision = current["revision"]
|
||||
for item in items:
|
||||
if self._identity(item) != identity or item["updated_at"] != updated_at:
|
||||
continue
|
||||
if item.get("kept_updated_at") != updated_at:
|
||||
item["kept_updated_at"] = updated_at
|
||||
revision += 1
|
||||
connection.execute(
|
||||
"UPDATE following_issues SET revision = ?, items = ? WHERE login = ?",
|
||||
|
||||
@@ -35,11 +35,11 @@ FEATURE_SOURCES = {
|
||||
"security-center": ("static/security-center.js",),
|
||||
"planning": (
|
||||
"static/plan-today.js", "static/plan-today-readiness.js",
|
||||
"static/plan-today-preview.js", "static/today-rollover.js", "static/today-readiness.js",
|
||||
"static/plan-today-preview.js", "static/today-rollover.js", "static/today-readiness.js", "static/following.js",
|
||||
"static/tomorrow-plan.js", "static/week-calendar.js", "static/week-calendar-import.js", "static/week-plan.js", "static/today-week-reschedule.js", "static/search-week-plan.js", "static/search-batch-plan.js", "static/agenda-session-launcher.js", "static/mobile-plan-today-nav.js",
|
||||
),
|
||||
"today-timer": (
|
||||
"static/mobile-app-badge.js", "static/conversation.js", "static/widgets.js", "static/voice-transcript-store.js", "static/voice-conversation-capture.js", "static/mobile-launch.js", "static/mobile-insights.js", "static/mobile-app-shortcuts.js", "static/mobile-find-work-nav.js", "static/mobile-pull-refresh.js", "static/live-data-status.js", "static/following.js", "static/mobile-composer-viewport.js",
|
||||
"static/mobile-app-badge.js", "static/conversation.js", "static/widgets.js", "static/voice-transcript-store.js", "static/voice-conversation-capture.js", "static/mobile-launch.js", "static/mobile-insights.js", "static/mobile-app-shortcuts.js", "static/mobile-find-work-nav.js", "static/mobile-pull-refresh.js", "static/live-data-status.js", "static/mobile-composer-viewport.js",
|
||||
"static/today-completion.js", "static/card-planning.js", "static/work-detail-position.js", "static/work-route.js", "static/commands.js", "static/saved-searches.js", "static/task-overlay-history.js", "static/search-preview.js", "static/mobile-search-preview-nav.js", "static/search-reply-draft-store.js", "static/conversation-reply-draft-store.js", "static/conversation-photo-drafts.js", "static/search-defer.js", "static/mobile-search-viewport.js", "static/agenda-replan.js", "static/agenda-calendar.js", "static/my-work.js", "static/protect-today.js", "static/mobile-today-command-bar.js", "static/mobile-task-dock.js", "static/mobile-first-task.js", "static/mobile-work-entry.js", "static/mobile-queue-launcher.js", "static/mobile-delivery-recovery.js", "static/mobile-start-day.js", "static/update-triage-session.js", "static/update-review-handoff.js", "static/update-triage-launcher.js", "static/update-triage-gesture.js", "static/notification-undo.js", "static/today-timer.js", "static/today-break.js", "static/today-progress.js", "static/today-lock-screen.js", "static/today-session-sync.js", "static/today-recap.js", "static/today-wrap-up.js", "static/today-summary.js", "static/today-handoff.js",
|
||||
"static/later-work.js", "static/detail-defer.js", "static/later-picker.js", "static/drafts.js", "static/unfiled-captures.js", "static/unfiled-draft-sync.js",
|
||||
"static/assign-and-start.js", "static/filed-claim.js", "static/queue-today.js", "static/create-and-start.js",
|
||||
|
||||
+28
@@ -2725,6 +2725,34 @@ async def acknowledge_following_revision(
|
||||
)
|
||||
|
||||
|
||||
@app.put("/api/v1/following/{owner}/{repo}/issues/{number}/keep")
|
||||
async def keep_following_revision(
|
||||
payload: FollowingSeenRevision,
|
||||
owner: str,
|
||||
repo: str,
|
||||
number: int = PathParam(gt=0),
|
||||
kind: Literal["issue", "pull"] = Query(default="issue"),
|
||||
):
|
||||
login = await _confirmed_login()
|
||||
try:
|
||||
return await asyncio.to_thread(
|
||||
_following_store().keep_unseen,
|
||||
login,
|
||||
f"{owner}/{repo}",
|
||||
number,
|
||||
payload.updated_at,
|
||||
kind=kind,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
except (OSError, sqlite3.Error, PrivateStateEncryptionError):
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Following synchronization is unavailable",
|
||||
headers={"Retry-After": "1"},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/v1/completed-filed-reviews")
|
||||
async def get_completed_filed_reviews(response: Response):
|
||||
login = await _confirmed_login()
|
||||
|
||||
Reference in new issue
Block a user