Merge "Only show Create Flow elements if user is the uploader."
diff --git a/polygerrit-ui/app/elements/change/gr-flows/gr-flows.ts b/polygerrit-ui/app/elements/change/gr-flows/gr-flows.ts
index 2692136..2d9361a 100644
--- a/polygerrit-ui/app/elements/change/gr-flows/gr-flows.ts
+++ b/polygerrit-ui/app/elements/change/gr-flows/gr-flows.ts
@@ -10,7 +10,12 @@
 import {resolve} from '../../../models/dependency';
 import {changeModelToken} from '../../../models/change/change-model';
 import {subscribe} from '../../lit/subscription-controller';
-import {FlowInfo, FlowStageState} from '../../../api/rest-api';
+import {
+  AccountDetailInfo,
+  AccountId,
+  FlowInfo,
+  FlowStageState,
+} from '../../../api/rest-api';
 import {flowsModelToken} from '../../../models/flows/flows-model';
 import {NumericChangeId} from '../../../types/common';
 import './gr-create-flow';
@@ -19,6 +24,7 @@
 import '@material/web/select/filled-select';
 import '@material/web/select/select-option';
 import {computeFlowStringFromFlowStageInfo} from '../../../utils/flows-util';
+import {userModelToken} from '../../../models/user/user-model';
 
 const iconForFlowStageState = (status: FlowStageState) => {
   switch (status) {
@@ -44,6 +50,10 @@
 
   @state() private changeNum?: NumericChangeId;
 
+  @state() private changeUploader?: AccountId;
+
+  @state() private account?: AccountDetailInfo;
+
   @state() private loading = true;
 
   @state() private flowIdToDelete?: string;
@@ -52,6 +62,8 @@
 
   private readonly getChangeModel = resolve(this, changeModelToken);
 
+  private readonly getUserModel = resolve(this, userModelToken);
+
   private readonly getFlowsModel = resolve(this, flowsModelToken);
 
   static override get styles() {
@@ -62,6 +74,9 @@
         .container {
           padding: var(--spacing-l);
         }
+        b {
+          font-weight: bolder;
+        }
         hr {
           margin-top: var(--spacing-l);
           margin-bottom: var(--spacing-l);
@@ -139,6 +154,21 @@
     );
     subscribe(
       this,
+      () => this.getChangeModel().change$,
+      change => {
+        this.changeUploader =
+          change?.revisions[change?.current_revision].uploader?._account_id;
+      }
+    );
+    subscribe(
+      this,
+      () => this.getUserModel().account$,
+      account => {
+        this.account = account;
+      }
+    );
+    subscribe(
+      this,
       () => this.getFlowsModel().flows$,
       flows => {
         this.flows = flows;
@@ -172,8 +202,14 @@
   override render() {
     return html`
       <div class="container">
-        <h2 class="main-heading">Create new flow</h2>
-        <gr-create-flow .changeNum=${this.changeNum}></gr-create-flow>
+        ${when(
+          this.showCreateFlow(),
+          () =>
+            html`<h2 class="main-heading">Create new flow</h2>
+              <gr-create-flow .changeNum=${this.changeNum}></gr-create-flow>`,
+          () =>
+            html`<b>Note:</b> New flows can only be added by change uploader.`
+        )}
         <hr />
         ${this.renderFlowsList()}
       </div>
@@ -196,6 +232,13 @@
     </dialog>`;
   }
 
+  private showCreateFlow() {
+    return (
+      this.account?._account_id !== undefined &&
+      this.account._account_id === this.changeUploader
+    );
+  }
+
   private renderStatus(stage: FlowInfo['stages'][0]): TemplateResult {
     const icon = iconForFlowStageState(stage.state);
     return html`<gr-icon
diff --git a/polygerrit-ui/app/elements/change/gr-flows/gr-flows_test.ts b/polygerrit-ui/app/elements/change/gr-flows/gr-flows_test.ts
index 4a15840..6cba4d9 100644
--- a/polygerrit-ui/app/elements/change/gr-flows/gr-flows_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-flows/gr-flows_test.ts
@@ -7,30 +7,74 @@
 import './gr-flows';
 import {assert, fixture, html} from '@open-wc/testing';
 import {GrFlows} from './gr-flows';
-import {FlowInfo, FlowStageState, Timestamp} from '../../../api/rest-api';
+import {
+  AccountId,
+  CommitId,
+  FlowInfo,
+  FlowStageState,
+  Timestamp,
+} from '../../../api/rest-api';
 import {queryAndAssert} from '../../../test/test-utils';
 import {NumericChangeId} from '../../../types/common';
 import sinon from 'sinon';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
 import {FlowsModel, flowsModelToken} from '../../../models/flows/flows-model';
+import {
+  ChangeModel,
+  changeModelToken,
+} from '../../../models/change/change-model';
+import {UserModel, userModelToken} from '../../../models/user/user-model';
 import {testResolver} from '../../../test/common-test-setup';
+import {
+  createAccountDetailWithId,
+  createParsedChange,
+  createRevision,
+} from '../../../test/test-data-generators';
+
+function setChangeWithUploader(
+  changeModel: ChangeModel,
+  uploaderId: AccountId
+) {
+  changeModel.updateState({
+    change: {
+      ...createParsedChange(),
+      _number: 123 as NumericChangeId,
+      revisions: {
+        rev1: {
+          ...createRevision(1),
+          uploader: createAccountDetailWithId(uploaderId),
+        },
+      },
+      current_revision: 'rev1' as CommitId,
+    },
+  });
+}
 
 suite('gr-flows tests', () => {
   let element: GrFlows;
   let clock: sinon.SinonFakeTimers;
   let flowsModel: FlowsModel;
+  let changeModel: ChangeModel;
+  let userModel: UserModel;
 
   setup(async () => {
     clock = sinon.useFakeTimers();
 
+    changeModel = testResolver(changeModelToken);
+    userModel = testResolver(userModelToken);
     flowsModel = testResolver(flowsModelToken);
     // The model is created by the DI system. The test setup replaces the real
     // model with a mock. To prevent real API calls, we stub the reload method.
     sinon.stub(flowsModel, 'reload');
 
     element = await fixture<GrFlows>(html`<gr-flows></gr-flows>`);
-    element['changeNum'] = 123 as NumericChangeId;
+    await element.updateComplete;
+    setChangeWithUploader(changeModel, 123 as AccountId);
+    userModel.setState({
+      account: createAccountDetailWithId(123 as AccountId),
+      accountLoaded: true,
+    });
     await element.updateComplete;
   });
 
@@ -442,4 +486,39 @@
       assert.equal(flowElements.length, 4);
     });
   });
+
+  suite('create flow visibility', () => {
+    setup(async () => {
+      flowsModel.setState({flows: [], loading: false, isEnabled: true});
+      await element.updateComplete;
+    });
+
+    test('shows gr-create-flow when current user is uploader', async () => {
+      const uploaderId = 123 as AccountId;
+      const currentUserId = 123 as AccountId;
+      setChangeWithUploader(changeModel, uploaderId);
+      userModel.setState({
+        account: createAccountDetailWithId(currentUserId),
+        accountLoaded: true,
+      });
+      await element.updateComplete;
+
+      const createFlow = element.shadowRoot!.querySelector('gr-create-flow');
+      assert.isNotNull(createFlow);
+    });
+
+    test('hides gr-create-flow when current user is not uploader', async () => {
+      const uploaderId = 456 as AccountId;
+      const currentUserId = 123 as AccountId;
+      setChangeWithUploader(changeModel, uploaderId);
+      userModel.setState({
+        account: createAccountDetailWithId(currentUserId),
+        accountLoaded: true,
+      });
+      await element.updateComplete;
+
+      const createFlow = element.shadowRoot!.querySelector('gr-create-flow');
+      assert.isNull(createFlow);
+    });
+  });
 });