Workflow Center: define in-flight behavior; clear the selection, and decide what happens on navigation
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Description
Once a workflow action is fired from the Workflow Center, the app enters a state nobody designed. The dialog closes immediately by design (dot-content-drive-action-center.component.ts:696-708 — the modal would dim the toolbar indicator and block the grid), so the user is dropped back onto a live grid with the run still in flight, and the grid behaves as if nothing is happening.
Two behaviors need deciding and implementing.
1. The selection while and after a run
The selection is never cleared when the action fires. It is cleared only as a side effect of success: the shell's result effect calls loadItems() (dot-content-drive-shell.component.ts:524), and loadItems() patches selectedItems: [] (dot-content-drive.store.ts:359-362). On the error paths (withActionExecution.ts:128 / :204 / :257) no result is published, so there is no reload and the selection is left standing.
That produces three problems:
- The selection is already a lie. The payload was snapshotted into the request before the dialog closed. The checked rows no longer describe what is running, and the user can freely check and uncheck rows mid-run while the toolbar still shows a progress indicator tied to a different set.
- The grid is not guarded. Only the toolbar's Action Center button is disabled (
dot-content-drive-toolbar.component.html:50,$actionExecution). The table gets[loading]="$loading()", which is driven bystatus === LOADINGfromloadItems, not byactionExecution(dot-content-drive-shell.component.html:36-38) — so sort, paginate, drag and the row context menu all stay live. The row context menu path (dot-folder-list-context-menu.component.ts:246-314) never consultsactionExecution, so a second workflow action can be fired on the same contentlet while the first is still running. The store's re-entrancy guard (withActionExecution.ts:115) only covers the Action Center path. - Success and failure end in different states. Success clears; failure keeps a selection that is now stale for a different reason (items may have partially moved step).
Proposal — clear the selection at fire time, not on settle, so the grid stops implying the checked rows are the ones being acted on, and every outcome lands in the same state. ⚠️ Needs design confirmation — it conflicts with retry (see Open Questions).
2. What happens when the user navigates away mid-run
Today: nothing, and the outcome is worse than it looks.
The Content Drive route has no guard (lib.routes.ts:7-13; registered with only MenuGuardService at app.routes.ts:148-154) and the shell has no ngOnDestroy teardown for the run. Nothing unsubscribes and no AbortController is used, so the HTTP request is not cancelled — it completes normally server-side. What is destroyed is the store, because it is component-provided on the shell (dot-content-drive-shell.component.ts:120).
So navigating away mid-run means: the action runs and succeeds, and the user is told nothing. No toast, no skippedCount/failCount report, no grid refresh, no progress indicator. If they come back, the grid reloads from scratch and the run is invisible.
This is the decision this ticket exists to close, and the honest framing matters: a warning must not say "this might fail", because it will not. What is lost is the result.
| Option | What it means | Cost |
|---|---|---|
| A — Confirm on leave | CanDeactivate guard: "A workflow action is running on N items. If you leave, it will finish but you won't see the result." User can leave anyway. |
Small. Reuses unsaved-changes.guard.ts (functional CanDeactivateFn + ConfirmationService, libs/edit-content/src/lib/guards/). |
| B — Let it run, report it wherever the user is | Promote the run out of the component-scoped store so it survives navigation, with a global indicator and a toast on completion. | Large. Needs a root-provided service or a global-store feature; the only navigation-independent channel today is the root withWebSocket() in libs/global-store. |
| C — Cancel on leave | Abort the request when the route is destroyed. | Rejected: the server work has already started, so aborting the client leaves a partially applied bulk action with no record of it. |
Recommendation: A now, B tracked as follow-up. A closes the honesty gap for the price of one guard. B is the correct end state but is really the same shape of work as #36894 (per-item results over SSE) — a run that reports progress independently of the component that started it — and should be designed with it rather than twice.
Acceptance Criteria
Selection and grid during a run
- The selection is cleared when the action is fired, not when it settles, so success and failure end in the same state (pending design confirmation — see Open Questions)
- Row selection is disabled while an action is in flight, so the user cannot build a new selection against a stale progress indicator
- The row context menu cannot fire a workflow action while another run is in flight — the same guard the Action Center already has (
withActionExecution.ts:115) applies todot-folder-list-context-menu.component.ts - The in-flight state is visibly distinct from the grid's normal
loadingstate; a user can tell an action is running rather than that the page is fetching - The error paths (
withActionExecution.ts:128 / :204 / :257) leave the grid in a defined state rather than whatever the selection happened to be
Navigation
- Navigating away from Content Drive while an action is running produces the agreed behavior (Option A unless design/eng decides otherwise)
- If a leave confirmation is shown, its copy states that the action will complete and the result will not be visible — it must not claim the action may fail
- Leaving is never blocked outright; the user can always proceed
- The confirmation does not fire when no action is in flight
- Whichever option is chosen, the request is not aborted on navigation
General
- Unit tests cover: selection cleared on fire, context-menu guard while in flight, guard returns
truewith no run in flight and prompts with one
Open Questions
- Design — is clearing the selection right? It conflicts with #36894, which wants to retry failed rows. Retry needs the failed items to still be identifiable; clearing throws away the user's set. Possible resolution: clear the grid selection but keep the fired set inside
actionExecutionso the results view owns retry — which is what #36894 builds anyway. Needs a decision before implementing. - Design — how is "an action is running" shown on the grid? Today it is a small toolbar spinner (
dot-content-drive-toolbar.component.html:86-97). If selection is disabled, that needs to be legible enough to explain why the checkboxes stopped working. - Eng — Option A or B? A is recommended; B is the correct end state but belongs with #36894.
Priority
Medium
Additional Context
What holds the run today. withActionExecution.ts deliberately owns the run rather than the dialog (:31-46) — plain HttpClient observables with .pipe(take(1), catchError(…)).subscribe(…), no takeUntilDestroyed, so the run survives the dialog being destroyed. That works exactly as far as the shell: the store is component-provided, so it dies with the route.
Existing guard patterns:
| Pattern | Where |
|---|---|
Functional CanDeactivateFn + ConfirmationService (best fit) |
libs/edit-content/src/lib/guards/unsaved-changes.guard.ts, wired at edit-content.routes.ts:16,28 |
Legacy class guard via DotRouterService |
libs/data-access/src/lib/can-deactivate/can-deactivate-guard.service.ts, used by edit-ema |
| Root-scoped, navigation-independent channel | withWebSocket() in libs/global-store/src/lib/store.ts:46 — the only one that exists today |
There is no beforeunload precedent in the admin UI, and no job-queue store.
Related: #36894 (per-item results + SSE) is where Option B naturally lands. #36981 (select all across pages) changes what "clear the selection" means once a selection can be a query rather than a list of inodes.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with withActionExecution.ts, dot-content-drive-shell.component.ts, dot-content-drive.store.ts, and dot-folder-list-context-menu.component.ts to trace action state, selection, and context-menu behavior. Read unsaved-changes.guard.ts and the route registrations before resolving the navigation option; done means the agreed in-flight selection, guard, and error behavior are implemented with unit tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, typescript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100