Copilot commented on code in PR #8516:
URL: https://github.com/apache/texera/pull/8516#discussion_r3994390842
##########
frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts:
##########
@@ -1301,40 +1526,56 @@ export class WorkflowFormComponent implements OnInit,
OnDestroy {
* workflow when the payload has no id, so saving whatever the graph holds
would spawn
* stray "Untitled workflow" rows when the page is left before its workflow
loaded.
*/
- private save(): void {
+ private save(afterwards?: () => void): void {
// A read-only viewer can open and run the form (execution is gated on
computing-unit access,
// not workflow access) but must never persist: every such save is a
guaranteed 403 that would
// spam "Could not save" on each debounce. Their inputs are non-editable,
so nothing is lost.
+ // `afterwards` runs once the save has completed, or at once when there is
nothing to save;
+ // it does not run when the save fails, so a caller that navigates on it
stays put instead.
if (!this.canEdit) {
+ afterwards?.();
return;
}
if (!this.userService.isLogin() ||
!this.workflowPersistService.isWorkflowPersistEnabled()) {
+ afterwards?.();
return;
}
const workflow = this.workflowActionService.getWorkflow();
if (workflow.wid === undefined || workflow.wid !== this.wid) {
+ afterwards?.();
return;
}
+ if (this.destroyed) {
+ // On the way out there is no page left to queue on, and the request
must NOT be tied to this
+ // component: ngOnDestroy calls save(), and untilDestroyed would tear
the subscription down as
+ // part of the very same destroy sequence, aborting the request that was
the point of the
+ // call. The persist is a one-shot HTTP request that completes on its
own, so it needs no
+ // teardown operator; the error is reported inside persistNow.
+ // eslint-disable-next-line rxjs-angular/prefer-takeuntil
+ this.persistNow().subscribe({ error: () => undefined });
Review Comment:
This destroy-time save bypasses `persistQueue`, so it can run concurrently
with an autosave already in flight. Unsubscribing that older HTTP observable
after `ngOnDestroy` returns cannot guarantee that the backend cancels a request
it already received; the older snapshot can still commit after this final
snapshot, recreating the out-of-order overwrite the queue was added to prevent.
Preserve ordering during teardown—for example, queue captured snapshots in a
drain that may outlive the component—instead of starting an uncoordinated
persist.
##########
frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts:
##########
@@ -510,11 +545,41 @@ export class WorkflowFormComponent implements OnInit,
OnDestroy {
}
/**
- * Show the workflow rather than edit it: the graph shape and its properties
are read-only
- * on this page. A later PR's authoring mode makes properties editable with
write access.
+ * Whether this page may hold the graph unlocked: a writer in edit mode, and
no run in flight. The
+ * run rule is the canvas's own (a run locks the graph until it ends), kept
here too so that entering
+ * edit mode mid-run cannot undo it. Every other state is locked, so a
reader, or a writer merely
+ * viewing, cannot change the workflow through this page.
*/
+ private mayUnlock(): boolean {
+ return this.authoring && this.canEdit && !this.isRunning;
+ }
+
+ /** Lock or unlock editing from the current state (see mayUnlock); the one
reducer for both directions. */
private applyEditability(): void {
- this.workflowActionService.disableWorkflowModification();
+ if (this.mayUnlock()) {
+ this.workflowActionService.enableWorkflowModification();
+ } else {
+ this.workflowActionService.disableWorkflowModification();
+ }
+ }
+
+ /**
+ * The lock is a root-level flag with writers that know nothing of this
page: the execute service
+ * unlocks it whenever a run ends (completed, failed, killed, reset), the
computing-unit selector
+ * unlocks it when it finds no run on the chosen unit, and any future caller
may too. Rather than
+ * chase each one, clamp at the one place they all report to: whenever the
flag turns on while this
+ * page must stay locked, turn it off again. In edit mode the canvas rule
then stands unchanged:
+ * locked while a run is in flight, unlocked by the execute service once it
ends.
+ */
+ private clampEditability(): void {
+ this.workflowActionService
+ .getWorkflowModificationEnabledStream()
+ .pipe(untilDestroyed(this))
+ .subscribe(enabled => {
+ if (enabled && !this.mayUnlock()) {
+ this.workflowActionService.disableWorkflowModification();
Review Comment:
`ExecuteWorkflowService.updateExecutionState()` enables modification before
it updates and emits the terminal execution state
(`execute-workflow.service.ts:371-379,385-393`). Consequently, when a run
completes this callback still sees the component's previous `Running` state,
disables modification again, and the later execution-state handler never
reapplies editability. Edit mode therefore remains locked after every run.
Reconcile editability after assigning `current.state`, and make the regression
test emit the unlock before the completion event to match production ordering.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]