Merge "Update CodeMirror"
diff --git a/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts b/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts
index 89769a1..59b7c19 100644
--- a/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts
+++ b/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts
@@ -3,7 +3,7 @@
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {customElement, property, state} from 'lit/decorators.js';
+import {customElement, property, query, state} from 'lit/decorators.js';
import {css, html, LitElement, PropertyValues} from 'lit';
import {sharedStyles} from '../../../styles/shared-styles';
import {grFormStyles} from '../../../styles/gr-form-styles';
@@ -11,6 +11,7 @@
import {getAppContext} from '../../../services/app-context';
import {NumericChangeId, ServerInfo} from '../../../types/common';
import '../../shared/gr-button/gr-button';
+import '../../shared/gr-dialog/gr-dialog';
import '../../core/gr-search-autocomplete/gr-search-autocomplete';
import '@material/web/select/outlined-select.js';
import '@material/web/select/select-option.js';
@@ -22,6 +23,7 @@
import {flowsModelToken} from '../../../models/flows/flows-model';
import {subscribe} from '../../lit/subscription-controller';
import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
+import {modalStyles} from '../../../styles/gr-modal-styles';
import {
AutocompleteSuggestion,
fetchAccountSuggestions,
@@ -45,6 +47,9 @@
// Property so that we can mock it in tests
@property({type: String}) hostUrl?: string;
+ @query('#createModal')
+ private createModal?: HTMLDialogElement;
+
@state()
private stages: Stage[] = [];
@@ -114,6 +119,7 @@
return [
sharedStyles,
grFormStyles,
+ modalStyles,
css`
.raw-flow-container {
display: flex;
@@ -235,89 +241,115 @@
override render() {
return html`
- <div class="raw-flow-container">
- <gr-autogrow-textarea
- placeholder="raw flow"
- label="Raw Flow"
- .value=${this.flowString}
- @input=${(e: InputEvent) => {
- this.flowString = (e.target as HTMLTextAreaElement).value;
- this.parseStagesFromRawFlow(this.flowString);
- }}
- ></gr-autogrow-textarea>
- <gr-copy-clipboard
- .text=${this.flowString}
- buttonTitle="Copy raw flow to clipboard"
- hideinput
- ></gr-copy-clipboard>
- </div>
- <div>${this.renderTable()}</div>
- <div class="add-stage-row">
- <md-outlined-select
- value=${this.currentConditionPrefix}
- @change=${(e: Event) => {
- const select = e.target as HTMLSelectElement;
- this.currentConditionPrefix = select.value;
- }}
- >
- <md-select-option value="Gerrit">
- <div slot="headline">Gerrit</div>
- </md-select-option>
- <md-select-option value="Other">
- <div slot="headline">Other</div>
- </md-select-option>
- </md-outlined-select>
- ${this.currentConditionPrefix === 'Gerrit'
- ? html`<gr-search-autocomplete
- .placeholder=${'Create condition'}
- .value=${this.currentCondition}
- .projectSuggestions=${this.projectSuggestions}
- .groupSuggestions=${this.groupSuggestions}
- .accountSuggestions=${this.accountSuggestions}
- @text-changed=${this.handleGerritConditionTextChanged}
- ></gr-search-autocomplete>`
- : html`<md-outlined-text-field
- label="Condition"
- .value=${this.currentCondition}
- @input=${(e: InputEvent) =>
- (this.currentCondition = (
- e.target as MdOutlinedTextField
- ).value)}
- ></md-outlined-text-field>`}
- <span> -> </span>
- <md-outlined-select
- label="Action"
- .value=${this.currentAction}
- @change=${(e: Event) => {
- const select = e.target as HTMLSelectElement;
- this.currentAction = select.value;
- }}
- >
- ${this.flowActions.map(
- action => html`
- <md-select-option .value=${action.name}>
- <div slot="headline">${action.name}</div>
- </md-select-option>
- `
- )}
- </md-outlined-select>
- <md-outlined-text-field
- label="Parameters"
- .value=${this.currentParameter}
- @input=${(e: InputEvent) =>
- (this.currentParameter = (e.target as MdOutlinedTextField).value)}
- ></md-outlined-text-field>
- <gr-button aria-label="Add Stage" @click=${this.handleAddStage}
- >Add Stage</gr-button
- >
- </div>
<gr-button
aria-label="Create Flow"
- ?disabled=${this.loading}
- @click=${this.handleCreateFlow}
+ @click=${() => {
+ this.createModal?.showModal();
+ }}
>
Create Flow
</gr-button>
+ ${this.renderCreateFlowDialog()}
+ `;
+ }
+
+ private renderCreateFlowDialog() {
+ return html`
+ <dialog id="createModal" tabindex="-1">
+ <gr-dialog
+ confirm-label="Create"
+ ?disabled=${this.loading}
+ @confirm=${this.handleCreateFlow}
+ @cancel=${() => {
+ this.createModal?.close();
+ }}
+ >
+ <div slot="header">Create new flow</div>
+ <div class="main" slot="main">
+ <div class="raw-flow-container">
+ <gr-autogrow-textarea
+ placeholder="raw flow"
+ label="Raw Flow"
+ .value=${this.flowString}
+ @input=${(e: InputEvent) => {
+ this.flowString = (e.target as HTMLTextAreaElement).value;
+
+ this.parseStagesFromRawFlow(this.flowString);
+ }}
+ ></gr-autogrow-textarea>
+ <gr-copy-clipboard
+ .text=${this.flowString}
+ buttonTitle="Copy raw flow to clipboard"
+ hideinput
+ ></gr-copy-clipboard>
+ </div>
+ <div>${this.renderTable()}</div>
+ <div class="add-stage-row">
+ <md-outlined-select
+ value=${this.currentConditionPrefix}
+ @change=${(e: Event) => {
+ const select = e.target as HTMLSelectElement;
+
+ this.currentConditionPrefix = select.value;
+ }}
+ >
+ <md-select-option value="Gerrit">
+ <div slot="headline">Gerrit</div>
+ </md-select-option>
+ <md-select-option value="Other">
+ <div slot="headline">Other</div>
+ </md-select-option>
+ </md-outlined-select>
+ ${this.currentConditionPrefix === 'Gerrit'
+ ? html`<gr-search-autocomplete
+ .placeholder=${'Create condition'}
+ .value=${this.currentCondition}
+ .projectSuggestions=${this.projectSuggestions}
+ .groupSuggestions=${this.groupSuggestions}
+ .accountSuggestions=${this.accountSuggestions}
+ @text-changed=${this.handleGerritConditionTextChanged}
+ ></gr-search-autocomplete>`
+ : html`<md-outlined-text-field
+ label="Condition"
+ .value=${this.currentCondition}
+ @input=${(e: InputEvent) =>
+ (this.currentCondition = (
+ e.target as MdOutlinedTextField
+ ).value)}
+ ></md-outlined-text-field>`}
+ <span> -> </span>
+ <md-outlined-select
+ label="Action"
+ .value=${this.currentAction}
+ @change=${(e: Event) => {
+ const select = e.target as HTMLSelectElement;
+
+ this.currentAction = select.value;
+ }}
+ >
+ ${this.flowActions.map(
+ action => html`
+ <md-select-option .value=${action.name}>
+ <div slot="headline">${action.name}</div>
+ </md-select-option>
+ `
+ )}
+ </md-outlined-select>
+ <md-outlined-text-field
+ label="Parameters"
+ .value=${this.currentParameter}
+ @input=${(e: InputEvent) =>
+ (this.currentParameter = (
+ e.target as MdOutlinedTextField
+ ).value)}
+ ></md-outlined-text-field>
+ <gr-button aria-label="Add Stage" @click=${this.handleAddStage}
+ >Add Stage</gr-button
+ >
+ </div>
+ </div>
+ </gr-dialog>
+ </dialog>
`;
}
@@ -438,6 +470,7 @@
this.currentAction = '';
this.currentParameter = '';
this.loading = false;
+ this.createModal?.close();
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow_test.ts b/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow_test.ts
index 3d77901..7de5659 100644
--- a/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow_test.ts
@@ -18,6 +18,7 @@
import {getAppContext} from '../../../services/app-context';
import {FlowActionInfo} from '../../../api/rest-api';
import {MdOutlinedSelect} from '@material/web/select/outlined-select';
+import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
suite('gr-create-flow tests', () => {
let element: GrCreateFlow;
@@ -46,32 +47,75 @@
});
test('renders initially', () => {
- assert.isDefined(queryAndAssert(element, 'gr-search-autocomplete'));
+ const createButton = queryAndAssert<GrButton>(
+ element,
+ 'gr-button[aria-label="Create Flow"]'
+ );
+ createButton.click();
+ const createModal = queryAndAssert<HTMLDialogElement>(
+ element,
+ '#createModal'
+ );
+ assert.isTrue(createModal.open);
+
+ const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+ assert.isDefined(queryAndAssert(grDialog, 'gr-search-autocomplete'));
assert.isDefined(
- queryAndAssert(element, 'md-outlined-select[label="Action"]')
+ queryAndAssert(grDialog, 'md-outlined-select[label="Action"]')
);
assert.isDefined(
- queryAndAssert(element, 'md-outlined-text-field[label="Parameters"]')
+ queryAndAssert(grDialog, 'md-outlined-text-field[label="Parameters"]')
);
assert.isDefined(
- queryAndAssert(element, 'gr-button[aria-label="Add Stage"]')
- );
- assert.isDefined(
- queryAndAssert(element, 'gr-button[aria-label="Create Flow"]')
+ queryAndAssert(grDialog, 'gr-button[aria-label="Add Stage"]')
);
});
- test('adds and removes stages', async () => {
- const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+ test('opens and closes dialog', async () => {
+ const createButton = queryAndAssert<GrButton>(
element,
+ 'gr-button[aria-label="Create Flow"]'
+ );
+ const createModal = queryAndAssert<HTMLDialogElement>(
+ element,
+ '#createModal'
+ );
+
+ createButton.click();
+ await element.updateComplete;
+ assert.isTrue(createModal.open);
+
+ const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+ const cancelButton = queryAndAssert<GrButton>(grDialog, '#cancel');
+ cancelButton.click();
+ await element.updateComplete;
+ assert.isFalse(createModal.open);
+ });
+
+ test('adds and removes stages', async () => {
+ const createButton = queryAndAssert<GrButton>(
+ element,
+ 'gr-button[aria-label="Create Flow"]'
+ );
+ createButton.click();
+ await element.updateComplete;
+ const createModal = queryAndAssert<HTMLDialogElement>(
+ element,
+ '#createModal'
+ );
+ const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+ const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+ grDialog,
'gr-search-autocomplete'
);
const actionInput = queryAndAssert<MdOutlinedSelect>(
- element,
+ grDialog,
'md-outlined-select[label="Action"]'
);
const addButton = queryAndAssert<GrButton>(
- element,
+ grDialog,
'gr-button[aria-label="Add Stage"]'
);
@@ -117,7 +161,7 @@
},
]);
- let removeButtons = queryAll<GrButton>(element, 'tr gr-button');
+ let removeButtons = queryAll<GrButton>(grDialog, 'tr gr-button');
assert.lengthOf(removeButtons, 2);
removeButtons[0].click();
@@ -131,19 +175,31 @@
parameterStr: '',
},
]);
- removeButtons = queryAll<GrButton>(element, 'tr gr-button');
+ removeButtons = queryAll<GrButton>(grDialog, 'tr gr-button');
assert.lengthOf(removeButtons, 1);
});
test('creates a flow with one stage', async () => {
const createFlowStub = sinon.stub(flowsModel, 'createFlow');
- const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+ const createButton = queryAndAssert<GrButton>(
element,
+ 'gr-button[aria-label="Create Flow"]'
+ );
+ createButton.click();
+ await element.updateComplete;
+ const createModal = queryAndAssert<HTMLDialogElement>(
+ element,
+ '#createModal'
+ );
+ const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+ const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+ grDialog,
'gr-search-autocomplete'
);
const actionInput = queryAndAssert<MdOutlinedSelect>(
- element,
+ grDialog,
'md-outlined-select[label="Action"]'
);
searchAutocomplete.value = 'single condition';
@@ -152,11 +208,8 @@
actionInput.dispatchEvent(new Event('change'));
await element.updateComplete;
- const createButton = queryAndAssert<GrButton>(
- element,
- 'gr-button[aria-label="Create Flow"]'
- );
- createButton.click();
+ const confirmButton = queryAndAssert<GrButton>(grDialog, '#confirm');
+ confirmButton.click();
await element.updateComplete;
assert.isTrue(createFlowStub.calledOnce);
@@ -168,21 +221,34 @@
action: {name: 'single action'},
},
]);
+ assert.isFalse(createModal.open);
});
test('creates a flow with parameters', async () => {
const createFlowStub = sinon.stub(flowsModel, 'createFlow');
- const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+ const createButton = queryAndAssert<GrButton>(
element,
+ 'gr-button[aria-label="Create Flow"]'
+ );
+ createButton.click();
+ await element.updateComplete;
+ const createModal = queryAndAssert<HTMLDialogElement>(
+ element,
+ '#createModal'
+ );
+ const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+ const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+ grDialog,
'gr-search-autocomplete'
);
const actionInput = queryAndAssert<MdOutlinedSelect>(
- element,
+ grDialog,
'md-outlined-select[label="Action"]'
);
const parametersInput = queryAndAssert<MdOutlinedTextField>(
- element,
+ grDialog,
'md-outlined-text-field[label="Parameters"]'
);
searchAutocomplete.value = 'single condition';
@@ -194,11 +260,8 @@
parametersInput.dispatchEvent(new Event('input'));
await element.updateComplete;
- const createButton = queryAndAssert<GrButton>(
- element,
- 'gr-button[aria-label="Create Flow"]'
- );
- createButton.click();
+ const confirmButton = queryAndAssert<GrButton>(grDialog, '#confirm');
+ confirmButton.click();
await element.updateComplete;
assert.isTrue(createFlowStub.calledOnce);
@@ -210,21 +273,34 @@
action: {name: 'single action', parameters: ['param1', 'param2']},
},
]);
+ assert.isFalse(createModal.open);
});
test('creates a flow with multiple stages', async () => {
const createFlowStub = sinon.stub(flowsModel, 'createFlow');
- const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+ const createButton = queryAndAssert<GrButton>(
element,
+ 'gr-button[aria-label="Create Flow"]'
+ );
+ createButton.click();
+ await element.updateComplete;
+ const createModal = queryAndAssert<HTMLDialogElement>(
+ element,
+ '#createModal'
+ );
+ const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+ const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+ grDialog,
'gr-search-autocomplete'
);
const actionInput = queryAndAssert<MdOutlinedSelect>(
- element,
+ grDialog,
'md-outlined-select[label="Action"]'
);
const addButton = queryAndAssert<GrButton>(
- element,
+ grDialog,
'gr-button[aria-label="Add Stage"]'
);
@@ -244,11 +320,8 @@
addButton.click();
await element.updateComplete;
- const createButton = queryAndAssert<GrButton>(
- element,
- 'gr-button[aria-label="Create Flow"]'
- );
- createButton.click();
+ const confirmButton = queryAndAssert<GrButton>(grDialog, '#confirm');
+ confirmButton.click();
await element.updateComplete;
assert.isTrue(createFlowStub.calledOnce);
@@ -265,21 +338,34 @@
action: {name: 'act-2'},
},
]);
+ assert.isFalse(createModal.open);
});
test('create flow with added stages and current input', async () => {
const createFlowStub = sinon.stub(flowsModel, 'createFlow');
- const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+ const createButton = queryAndAssert<GrButton>(
element,
+ 'gr-button[aria-label="Create Flow"]'
+ );
+ createButton.click();
+ await element.updateComplete;
+ const createModal = queryAndAssert<HTMLDialogElement>(
+ element,
+ '#createModal'
+ );
+ const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+ const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+ grDialog,
'gr-search-autocomplete'
);
const actionInput = queryAndAssert<MdOutlinedSelect>(
- element,
+ grDialog,
'md-outlined-select[label="Action"]'
);
const addButton = queryAndAssert<GrButton>(
- element,
+ grDialog,
'gr-button[aria-label="Add Stage"]'
);
@@ -296,11 +382,8 @@
actionInput.dispatchEvent(new Event('change'));
await element.updateComplete;
- const createButton = queryAndAssert<GrButton>(
- element,
- 'gr-button[aria-label="Create Flow"]'
- );
- createButton.click();
+ const confirmButton = queryAndAssert<GrButton>(grDialog, '#confirm');
+ confirmButton.click();
await element.updateComplete;
assert.isTrue(createFlowStub.calledOnce);
@@ -317,30 +400,43 @@
action: {name: 'act-2'},
},
]);
+ assert.isFalse(createModal.open);
});
test('raw flow textarea is updated', async () => {
- const rawFlowTextarea = queryAndAssert<GrAutogrowTextarea>(
+ const createButton = queryAndAssert<GrButton>(
element,
+ 'gr-button[aria-label="Create Flow"]'
+ );
+ createButton.click();
+ await element.updateComplete;
+ const createModal = queryAndAssert<HTMLDialogElement>(
+ element,
+ '#createModal'
+ );
+ const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+ const rawFlowTextarea = queryAndAssert<GrAutogrowTextarea>(
+ grDialog,
'gr-autogrow-textarea[label="Raw Flow"]'
);
assert.isDefined(rawFlowTextarea);
assert.equal(rawFlowTextarea.value, '');
const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
- element,
+ grDialog,
'gr-search-autocomplete'
);
const actionInput = queryAndAssert<MdOutlinedSelect>(
- element,
+ grDialog,
'md-outlined-select[label="Action"]'
);
const paramsInput = queryAndAssert<MdOutlinedTextField>(
- element,
+ grDialog,
'md-outlined-text-field[label="Parameters"]'
);
const addButton = queryAndAssert<GrButton>(
- element,
+ grDialog,
'gr-button[aria-label="Add Stage"]'
);
@@ -376,7 +472,7 @@
);
// Remove first stage
- const removeButtons = queryAll<GrButton>(element, 'tr gr-button');
+ const removeButtons = queryAll<GrButton>(grDialog, 'tr gr-button');
removeButtons[0].click();
await element.updateComplete;