Fix duplicate autosubmit flow creation on view navigation In PolyGerrit flows, getChangePrefix() used window.location.pathname directly without stripping view subpaths. When users checked autosubmit on a specific patchset, diff range (e.g. /9..10), or file view, the autosubmit condition included that view suffix. Navigating to another view caused getChangePrefix() to evaluate to a different URL prefix, making hasAutosubmitFlowAlready() return false. This incorrectly unchecked the autosubmit checkbox and created duplicate flows upon re-checking. Normalize getChangePrefix using CHANGE_PREFIX_PATTERN to strip patchset, diff range, comment ID, and file path subpaths from window.location.pathname. Release-Notes: skip Google-Bug-Id: b/482366559 Change-Id: I89fa0ab862d760f5c48ead85e17aabc70e1d2acf
diff --git a/polygerrit-ui/app/models/flows/flows-model.ts b/polygerrit-ui/app/models/flows/flows-model.ts index 300a1b7..83d0c74 100644 --- a/polygerrit-ui/app/models/flows/flows-model.ts +++ b/polygerrit-ui/app/models/flows/flows-model.ts
@@ -31,12 +31,26 @@ export const SUBMIT_ACTION_NAME = 'submit'; +/** + * Matches the base change path prefix up to the change number (e.g. `/c/project/+/123`), + * ignoring any trailing patchsets, diff ranges, comment IDs, or file paths. + * + * @see RoutePattern.CHANGE in `gr-router.ts` for corresponding route pattern. + */ +const CHANGE_PREFIX_PATTERN = /^(.*?\/(?:c\/.+?\/\+)\/\d+)/; + export function getSubmitCondition() { return getChangePrefix() + ' is is:submittable'; } +/** + * Returns the change URL prefix (e.g. `http://host/c/project/+/123`), stripping + * patchset numbers, diff ranges, comment IDs, and file paths from pathname. + */ export function getChangePrefix() { - return window.location.origin + window.location.pathname; + const match = window.location.pathname.match(CHANGE_PREFIX_PATTERN); + const pathname = match ? match[1] : window.location.pathname; + return window.location.origin + pathname; } export class FlowsModel extends Model<FlowsState> {
diff --git a/polygerrit-ui/app/models/flows/flows-model_test.ts b/polygerrit-ui/app/models/flows/flows-model_test.ts index 2dfe5a7..18c9538 100644 --- a/polygerrit-ui/app/models/flows/flows-model_test.ts +++ b/polygerrit-ui/app/models/flows/flows-model_test.ts
@@ -200,4 +200,69 @@ await waitUntil(() => !flowsModel.hasAutosubmitFlowAlready()); assert.isFalse(flowsModel.hasAutosubmitFlowAlready()); }); + + suite('getChangePrefix', () => { + let originalPath: string; + + setup(() => { + originalPath = window.location.pathname; + }); + + teardown(() => { + window.history.replaceState(null, '', originalPath); + }); + + test('strips patchset, diff range, and file path subpaths', () => { + const origin = window.location.origin; + + window.history.replaceState(null, '', '/c/my-repo/+/123'); + assert.equal(getChangePrefix(), `${origin}/c/my-repo/+/123`); + + window.history.replaceState(null, '', '/c/my-repo/+/123/4'); + assert.equal(getChangePrefix(), `${origin}/c/my-repo/+/123`); + + window.history.replaceState(null, '', '/c/my-repo/+/123/1..4'); + assert.equal(getChangePrefix(), `${origin}/c/my-repo/+/123`); + + window.history.replaceState( + null, + '', + '/c/my-repo/+/123/1..4/src/file.ts' + ); + assert.equal(getChangePrefix(), `${origin}/c/my-repo/+/123`); + }); + }); + + test('hasAutosubmitFlowAlready detects flows when navigating between patchsets/diff ranges', async () => { + const originalPath = window.location.pathname; + try { + window.history.replaceState(null, '', '/c/my-repo/+/123/4..5/src/foo.ts'); + stubRestApi('getIfFlowsIsEnabled').resolves({enabled: true}); + const expectedPrefix = `${window.location.origin}/c/my-repo/+/123`; + stubRestApi('listFlows').resolves([ + createFlow({ + uuid: 'flow1', + stages: [ + { + expression: { + condition: `${expectedPrefix} is is:submittable`, + action: {name: SUBMIT_ACTION_NAME}, + }, + state: FlowStageState.DONE, + }, + ], + }), + ]); + + changeModel.updateStateChange({ + ...createParsedChange(), + _number: 123 as NumericChangeId, + }); + await waitUntil(() => flowsModel.getState().flows.length > 0); + + assert.isTrue(flowsModel.hasAutosubmitFlowAlready()); + } finally { + window.history.replaceState(null, '', originalPath); + } + }); });