Merge "Migrate ChangeModel to DI pattern."
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 5418555..ad110df 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2028,6 +2028,14 @@
+
Default is 1 hour.
+[[dashboard]]
+=== Section dashboard
+
+[[dashboard.submitRequirementColumns]]dashboard.submitRequirementColumns::
++
+The list of submit requirement names that should be displayed as separate
+columns in the dashboard.
+
[[download]]
=== Section download
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index bd93b8b..86d7f58 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -2008,6 +2008,10 @@
|`default_theme` |optional|
URL to a default Gerrit UI theme plugin, if available.
Located in `/static/gerrit-theme.js` by default.
+|`submit_requirement_dashboard_columns` ||
+The list of submit requirement names that should be displayed as separate
+columns in the dashboard. If empty, the default is to display all submit
+requirements that are applicable for changes appearing in the dashboard.
|=======================================
[[sshd-info]]
diff --git a/java/com/google/gerrit/entities/KeyUtil.java b/java/com/google/gerrit/entities/KeyUtil.java
index 40fb757..be28689 100644
--- a/java/com/google/gerrit/entities/KeyUtil.java
+++ b/java/com/google/gerrit/entities/KeyUtil.java
@@ -48,12 +48,12 @@
for (char i = 'a'; i <= 'f'; i++) hexb[i] = (byte) ((i - 'a') + 10);
}
- public static String encode(final String e) {
+ public static String encode(final String key) {
final byte[] b;
try {
- b = e.getBytes("UTF-8");
- } catch (UnsupportedEncodingException e1) {
- throw new RuntimeException("No UTF-8 support", e1);
+ b = key.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("No UTF-8 support", e);
}
final StringBuilder r = new StringBuilder(b.length);
@@ -71,20 +71,20 @@
return r.toString();
}
- public static String decode(final String e) {
- if (e.indexOf('%') < 0) {
- return e.replace('+', ' ');
+ public static String decode(final String key) {
+ if (key.indexOf('%') < 0) {
+ return key.replace('+', ' ');
}
- final byte[] b = new byte[e.length()];
+ final byte[] b = new byte[key.length()];
int bPtr = 0;
try {
- for (int i = 0; i < e.length(); ) {
- final char c = e.charAt(i);
- if (c == '%' && i + 2 < e.length()) {
- final int v = (hexb[e.charAt(i + 1)] << 4) | hexb[e.charAt(i + 2)];
+ for (int i = 0; i < key.length(); ) {
+ final char c = key.charAt(i);
+ if (c == '%' && i + 2 < key.length()) {
+ final int v = (hexb[key.charAt(i + 1)] << 4) | hexb[key.charAt(i + 2)];
if (v < 0) {
- throw new IllegalArgumentException(e.substring(i, i + 3));
+ throw new IllegalArgumentException(key.substring(i, i + 3));
}
b[bPtr++] = (byte) v;
i += 3;
@@ -97,12 +97,12 @@
}
}
} catch (ArrayIndexOutOfBoundsException err) {
- throw new IllegalArgumentException("Bad encoding" + e, err);
+ throw new IllegalArgumentException("Bad encoding" + key, err);
}
try {
return new String(b, 0, bPtr, "UTF-8");
- } catch (UnsupportedEncodingException e1) {
- throw new RuntimeException("No UTF-8 support", e1);
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("No UTF-8 support", e);
}
}
}
diff --git a/java/com/google/gerrit/extensions/common/ServerInfo.java b/java/com/google/gerrit/extensions/common/ServerInfo.java
index bc7fcfd..ce65240 100644
--- a/java/com/google/gerrit/extensions/common/ServerInfo.java
+++ b/java/com/google/gerrit/extensions/common/ServerInfo.java
@@ -14,6 +14,8 @@
package com.google.gerrit.extensions.common;
+import java.util.List;
+
/** API response containing values from {@code gerrit.config} as nested objects. */
public class ServerInfo {
public AccountsInfo accounts;
@@ -28,4 +30,5 @@
public UserConfigInfo user;
public ReceiveInfo receive;
public String defaultTheme;
+ public List<String> submitRequirementDashboardColumns;
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index ae11d71..09052a6 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -66,8 +66,10 @@
import com.google.inject.Inject;
import java.nio.file.Files;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
@@ -153,6 +155,7 @@
info.user = getUserInfo();
info.receive = getReceiveInfo();
+ info.submitRequirementDashboardColumns = getSubmitRequirementDashboardColumns();
return Response.ok(info);
}
@@ -373,6 +376,10 @@
return info;
}
+ private List<String> getSubmitRequirementDashboardColumns() {
+ return Arrays.asList(config.getStringList("dashboard", null, "submitRequirementColumns"));
+ }
+
private static Boolean toBoolean(boolean v) {
return v ? v : null;
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index 97288a8..8131352 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -192,6 +192,9 @@
// user
assertThat(i.user.anonymousCowardName).isEqualTo(AnonymousCowardNameProvider.DEFAULT);
+
+ // submit requirement columns in dashboard
+ assertThat(i.submitRequirementDashboardColumns).isEmpty();
}
@Test
@@ -202,6 +205,15 @@
}
@Test
+ @GerritConfig(
+ name = "dashboard.submitRequirementColumns",
+ values = {"Code-Review", "Verified"})
+ public void serverConfigWithMultipleSubmitRequirementColumn() throws Exception {
+ ServerInfo i = gApi.config().server().getInfo();
+ assertThat(i.submitRequirementDashboardColumns).containsExactly("Code-Review", "Verified");
+ }
+
+ @Test
@GerritConfig(name = "change.mergeabilityComputationBehavior", value = "NEVER")
public void mergeabilityComputationBehavior_neverCompute() throws Exception {
ServerInfo i = gApi.config().server().getInfo();
diff --git a/plugins/download-commands b/plugins/download-commands
index 7f73617..71331e1 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 7f736175df9c18e490d7d21719e35a978706ca6e
+Subproject commit 71331e15af5a62ee7b13dee6ebdadf23d7e75a40
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 1727ed9..6676576 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -162,12 +162,14 @@
ParsedChangeInfo,
} from '../../../types/types';
import {
+ ChecksTabState,
CloseFixPreviewEvent,
EditableContentSaveEvent,
EventType,
OpenFixPreviewEvent,
ShowAlertEventDetail,
SwitchTabEvent,
+ SwitchTabEventDetail,
TabState,
} from '../../../types/events';
import {GrButton} from '../../shared/gr-button/gr-button';
@@ -896,7 +898,7 @@
this._selectedTabPluginHeader = '';
}
}
- this._tabState = e.detail.tabState;
+ if (e.detail.tabState) this._tabState = e.detail.tabState;
}
/**
@@ -1356,14 +1358,22 @@
let primaryTab = PrimaryTab.FILES;
if (params?.tab) {
primaryTab = params?.tab as PrimaryTab;
- } else if (params && 'commentId' in params) {
+ } else if (params?.commentId) {
primaryTab = PrimaryTab.COMMENT_THREADS;
}
+ const detail: SwitchTabEventDetail = {
+ tab: primaryTab,
+ };
+ if (primaryTab === PrimaryTab.CHECKS) {
+ const state: ChecksTabState = {};
+ detail.tabState = {checksTab: state};
+ if (params?.filter) state.filter = params?.filter;
+ if (params?.select) state.select = params?.select;
+ if (params?.attempt) state.attempt = params?.attempt;
+ }
this._setActivePrimaryTab(
- new CustomEvent('initActiveTab', {
- detail: {
- tab: primaryTab,
- },
+ new CustomEvent(EventType.SHOW_PRIMARY_TAB, {
+ detail,
})
);
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index 13fb5b3..b4591a0 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -310,13 +310,9 @@
}
</style>
<div class="container loading" hidden$="[[!_loading]]">Loading...</div>
- <!-- TODO(taoalpha): remove on-show-checks-table,
- Gerrit should not have any thing too special for a plugin,
- replace with a generic event: show-primary-tab. -->
<div
id="mainContent"
class="container"
- on-show-checks-table="_setActivePrimaryTab"
hidden$="{{_loading}}"
aria-hidden="[[_changeViewAriaHidden]]"
>
@@ -565,7 +561,7 @@
<h3 class="assistive-tech-only">Comments</h3>
<gr-thread-list
threads="[[_commentThreads]]"
- comment-tab-state="[[_tabState.commentTab]]"
+ comment-tab-state="[[_tabState]]"
only-show-robot-comments-with-human-reply=""
unresolved-only="[[unresolvedOnly]]"
scroll-comment-id="[[scrollCommentId]]"
@@ -577,10 +573,7 @@
if="[[_isTabActive(_constants.PrimaryTab.CHECKS, _activeTabs)]]"
>
<h3 class="assistive-tech-only">Checks</h3>
- <gr-checks-tab
- id="checksTab"
- tab-state="[[_tabState.checksTab]]"
- ></gr-checks-tab>
+ <gr-checks-tab id="checksTab" tab-state="[[_tabState]]"></gr-checks-tab>
</template>
<template
is="dom-if"
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
index a7d28ce..b41147b 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
@@ -36,7 +36,7 @@
} from '../../../utils/comment-util';
import {pluralize} from '../../../utils/string-util';
import {assertIsDefined} from '../../../utils/common-util';
-import {CommentTabState} from '../../../types/events';
+import {CommentTabState, TabState} from '../../../types/events';
import {DropdownItem} from '../../shared/gr-dropdown-list/gr-dropdown-list';
import {GrAccountChip} from '../../shared/gr-account-chip/gr-account-chip';
import {css, html, LitElement, PropertyValues} from 'lit';
@@ -169,7 +169,7 @@
hideDropdown = false;
@property({type: Object, attribute: 'comment-tab-state'})
- commentTabState?: CommentTabState;
+ commentTabState?: TabState;
@property({type: String, attribute: 'scroll-comment-id'})
scrollCommentId?: UrlEncodedCommentId;
@@ -222,7 +222,7 @@
}
private onCommentTabStateUpdate() {
- switch (this.commentTabState) {
+ switch (this.commentTabState?.commentTab) {
case CommentTabState.UNRESOLVED:
this.handleOnlyUnresolved();
break;
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index 5660692..e688c98 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -38,6 +38,7 @@
ErrorMessages,
} from '../../models/checks/checks-model';
import {
+ clearAllFakeRuns,
fakeActions,
fakeLinks,
fakeRun0,
@@ -46,6 +47,7 @@
fakeRun3,
fakeRun4Att,
fakeRun5,
+ setAllFakeRuns,
} from '../../models/checks/checks-fakes';
import {assertIsDefined} from '../../utils/common-util';
import {modifierPressed, whenVisible} from '../../utils/dom-util';
@@ -374,8 +376,12 @@
@query('#filterInput')
filterInput?: HTMLInputElement;
+ /**
+ * We prefer `undefined` over a RegExp with '', because `.source` yields
+ * a strange '(?:)' for ''.
+ */
@state()
- filterRegExp = new RegExp('');
+ filterRegExp?: RegExp;
@property({attribute: false})
runs: CheckRun[] = [];
@@ -535,7 +541,20 @@
protected override updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
+ // This update is done is response to setting this.filterRegExp below, but
+ // this.filterInput not yet being available at that point.
+ if (this.filterInput && !this.filterInput.value && this.filterRegExp) {
+ this.filterInput.value = this.filterRegExp.source;
+ }
if (changedProperties.has('tabState') && this.tabState) {
+ // Note that tabState.select and tabState.attempt are processed by
+ // <gr-checks-tab>.
+ if (
+ this.tabState.filter &&
+ this.tabState.filter !== this.filterRegExp?.source
+ ) {
+ this.filterRegExp = new RegExp(this.tabState.filter, 'i');
+ }
const {statusOrCategory} = this.tabState;
if (
statusOrCategory === RunStatus.RUNNING ||
@@ -684,109 +703,11 @@
onInput() {
assertIsDefined(this.filterInput, 'filter <input> element');
- this.filterRegExp = new RegExp(this.filterInput.value, 'i');
- }
-
- none() {
- this.getChecksModel().updateStateSetResults(
- 'f0',
- [],
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
- this.getChecksModel().updateStateSetResults(
- 'f1',
- [],
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
- this.getChecksModel().updateStateSetResults(
- 'f2',
- [],
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
- this.getChecksModel().updateStateSetResults(
- 'f3',
- [],
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
- this.getChecksModel().updateStateSetResults(
- 'f4',
- [],
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
- this.getChecksModel().updateStateSetResults(
- 'f5',
- [],
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
- }
-
- all() {
- this.getChecksModel().updateStateSetResults(
- 'f0',
- [fakeRun0],
- fakeActions,
- fakeLinks,
- 'ETA: 1 min',
- ChecksPatchset.LATEST
- );
- this.getChecksModel().updateStateSetResults(
- 'f1',
- [fakeRun1],
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
- this.getChecksModel().updateStateSetResults(
- 'f2',
- [fakeRun2],
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
- this.getChecksModel().updateStateSetResults(
- 'f3',
- [fakeRun3],
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
- this.getChecksModel().updateStateSetResults(
- 'f4',
- fakeRun4Att,
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
- this.getChecksModel().updateStateSetResults(
- 'f5',
- [fakeRun5],
- [],
- [],
- undefined,
- ChecksPatchset.LATEST
- );
+ if (this.filterInput.value) {
+ this.filterRegExp = new RegExp(this.filterInput.value, 'i');
+ } else {
+ this.filterRegExp = undefined;
+ }
}
toggle(
@@ -815,7 +736,7 @@
r.status === status ||
(status === RunStatus.RUNNING && r.status === RunStatus.SCHEDULED)
)
- .filter(r => this.filterRegExp.test(r.checkName))
+ .filter(r => !this.filterRegExp || this.filterRegExp.test(r.checkName))
.sort(compareByWorstCategory);
if (runs.length === 0) return;
const expanded = this.isSectionExpanded.get(status) ?? true;
@@ -858,11 +779,7 @@
}
showFilter(): boolean {
- const show = this.runs.length > 10;
- if (!show && this.filterRegExp.source.length > 0) {
- this.filterRegExp = new RegExp('');
- }
- return show;
+ return this.runs.length > 10 || !!this.filterRegExp;
}
renderFakeControls() {
@@ -870,7 +787,9 @@
return html`
<div class="testing">
<div>Toggle fake runs by clicking buttons:</div>
- <gr-button link @click="${this.none}">none</gr-button>
+ <gr-button link @click="${() => setAllFakeRuns(this.getChecksModel())}"
+ >none</gr-button
+ >
<gr-button
link
@click="${() =>
@@ -898,7 +817,11 @@
<gr-button link @click="${() => this.toggle('f5', [fakeRun5])}"
>5</gr-button
>
- <gr-button link @click="${this.all}">all</gr-button>
+ <gr-button
+ link
+ @click="${() => clearAllFakeRuns(this.getChecksModel())}"
+ >all</gr-button
+ >
</div>
`;
}
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts
index 4d54200..e8f2929 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts
@@ -14,13 +14,80 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
import '../../test/common-test-setup-karma';
+import './gr-checks-runs';
import {GrChecksRuns} from './gr-checks-runs';
+import {html} from 'lit';
+import {fixture} from '@open-wc/testing-helpers';
+import {checksModelToken} from '../../models/checks/checks-model';
+import {setAllFakeRuns} from '../../models/checks/checks-fakes';
+import {resolve} from '../../models/dependency';
suite('gr-checks-runs test', () => {
- test('is defined', () => {
- const el = document.createElement('gr-checks-runs');
- assert.instanceOf(el, GrChecksRuns);
+ let element: GrChecksRuns;
+
+ setup(async () => {
+ element = await fixture<GrChecksRuns>(
+ html`<gr-checks-runs></gr-checks-runs>`
+ );
+ const getChecksModel = resolve(element, checksModelToken);
+ setAllFakeRuns(getChecksModel());
+ });
+
+ test('tabState filter', async () => {
+ element.tabState = {filter: 'fff'};
+ await element.updateComplete;
+ assert.equal(element.filterRegExp?.source, 'fff');
+ });
+
+ test('renders', async () => {
+ await element.updateComplete;
+ assert.equal(element.runs.length, 44);
+ expect(element).shadowDom.to.equal(
+ `
+ <h2 class="title">
+ <div class="heading-2">Runs</div>
+ <div class="flex-space"></div>
+ <gr-tooltip-content has-tooltip="" title="Collapse runs panel">
+ <gr-button aria-checked="false" aria-label="Collapse runs panel"
+ class="expandButton" link="" role="switch">
+ <iron-icon class="expandIcon" icon="gr-icons:chevron-left"></iron-icon>
+ </gr-button>
+ </gr-tooltip-content>
+ </h2>
+ <input id="filterInput" placeholder="Filter runs by regular expression" type="text">
+ <div class="expanded running">
+ <div class="sectionHeader">
+ <iron-icon class="expandIcon" icon="gr-icons:expand-less"></iron-icon>
+ <h3 class="heading-3">Running / Scheduled</h3>
+ </div>
+ <div class="sectionRuns">
+ <gr-checks-run></gr-checks-run>
+ <gr-checks-run></gr-checks-run>
+ </div>
+ </div>
+ <div class="completed expanded">
+ <div class="sectionHeader">
+ <iron-icon class="expandIcon" icon="gr-icons:expand-less"></iron-icon>
+ <h3 class="heading-3">Completed</h3>
+ </div>
+ <div class="sectionRuns">
+ <gr-checks-run></gr-checks-run>
+ <gr-checks-run></gr-checks-run>
+ <gr-checks-run></gr-checks-run>
+ </div>
+ </div>
+ <div class="expanded runnable">
+ <div class="sectionHeader">
+ <iron-icon class="expandIcon" icon="gr-icons:expand-less"></iron-icon>
+ <h3 class="heading-3">Not run</h3>
+ </div>
+ <div class="sectionRuns">
+ <gr-checks-run></gr-checks-run>
+ </div>
+ </div>
+ `,
+ {ignoreAttributes: ['tabindex', 'aria-disabled']}
+ );
});
});
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
index 49b7a70..551e71d 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
@@ -28,7 +28,7 @@
import {NumericChangeId, PatchSetNumber} from '../../types/common';
import {ActionTriggeredEvent} from '../../models/checks/checks-util';
import {AttemptSelectedEvent, RunSelectedEvent} from './gr-checks-util';
-import {ChecksTabState} from '../../types/events';
+import {TabState} from '../../types/events';
import {getAppContext} from '../../services/app-context';
import {subscribe} from '../lit/subscription-controller';
import {Deduping} from '../../api/reporting';
@@ -48,7 +48,7 @@
results: CheckResult[] = [];
@property({type: Object})
- tabState?: ChecksTabState;
+ tabState?: TabState;
@state()
checksPatchsetNumber: PatchSetNumber | undefined = undefined;
@@ -144,13 +144,13 @@
.runs="${this.runs}"
.selectedRuns="${this.selectedRuns}"
.selectedAttempts="${this.selectedAttempts}"
- .tabState="${this.tabState}"
+ .tabState="${this.tabState?.checksTab}"
@run-selected="${this.handleRunSelected}"
@attempt-selected="${this.handleAttemptSelected}"
></gr-checks-runs>
<gr-checks-results
class="results"
- .tabState="${this.tabState}"
+ .tabState="${this.tabState?.checksTab}"
.runs="${this.runs}"
.selectedRuns="${this.selectedRuns}"
.selectedAttempts="${this.selectedAttempts}"
@@ -162,11 +162,52 @@
protected override updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
- if (changedProperties.has('tabState')) {
- if (this.tabState) {
- this.selectedRuns = [];
+ if (changedProperties.has('tabState')) this.applyTabState();
+ if (changedProperties.has('runs')) this.applyTabState();
+ }
+
+ /**
+ * Clearing the tabState means that from now on the user interaction counts,
+ * not the content of the URL (which is where tabState is populated from).
+ */
+ private clearTabState() {
+ this.tabState = {};
+ }
+
+ /**
+ * We want to keep applying the tabState to newly incoming check runs until
+ * the user explicitly interacts with the selection or the attempts, which
+ * will result in clearTabState() being called.
+ */
+ private applyTabState() {
+ if (!this.tabState?.checksTab) return;
+ // Note that .filter is processed by <gr-checks-runs>.
+ const {select, filter, attempt} = this.tabState?.checksTab;
+ if (!select) {
+ this.selectedRuns = [];
+ this.selectedAttempts = new Map<string, number>();
+ return;
+ }
+ const regexpSelect = new RegExp(select, 'i');
+ // We do not allow selection of runs that are invisible because of the
+ // filter.
+ const regexpFilter = new RegExp(filter ?? '', 'i');
+ const selectedRuns = this.runs.filter(
+ run =>
+ regexpSelect.test(run.checkName) && regexpFilter.test(run.checkName)
+ );
+ this.selectedRuns = selectedRuns.map(run => run.checkName);
+ const selectedAttempts = new Map<string, number>();
+ if (attempt) {
+ for (const run of selectedRuns) {
+ if (run.isSingleAttempt) continue;
+ const hasAttempt = run.attemptDetails.some(
+ detail => detail.attempt === attempt
+ );
+ if (hasAttempt) selectedAttempts.set(run.checkName, attempt);
}
}
+ this.selectedAttempts = selectedAttempts;
}
handleActionTriggered(action: Action, run?: CheckRun) {
@@ -174,6 +215,7 @@
}
handleRunSelected(e: RunSelectedEvent) {
+ this.clearTabState();
if (e.detail.reset) {
this.selectedRuns = [];
this.selectedAttempts = new Map();
@@ -185,6 +227,7 @@
}
handleAttemptSelected(e: AttemptSelectedEvent) {
+ this.clearTabState();
const {checkName, attempt} = e.detail;
this.selectedAttempts.set(checkName, attempt);
// Force property update.
@@ -192,6 +235,7 @@
}
toggleSelected(checkName: string) {
+ this.clearTabState();
if (this.selectedRuns.includes(checkName)) {
this.selectedRuns = this.selectedRuns.filter(r => r !== checkName);
this.selectedAttempts.set(checkName, undefined);
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab_test.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab_test.ts
index 85183ed..1ac553a 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab_test.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab_test.ts
@@ -14,13 +14,52 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
import '../../test/common-test-setup-karma';
+import {html} from 'lit';
+import './gr-checks-tab';
import {GrChecksTab} from './gr-checks-tab';
+import {fixture} from '@open-wc/testing-helpers';
+import {checksModelToken} from '../../models/checks/checks-model';
+import {fakeRun4_3, setAllFakeRuns} from '../../models/checks/checks-fakes';
+import {resolve} from '../../models/dependency';
+import {Category} from '../../api/checks';
suite('gr-checks-tab test', () => {
- test('is defined', () => {
- const el = document.createElement('gr-checks-tab');
- assert.instanceOf(el, GrChecksTab);
+ let element: GrChecksTab;
+
+ setup(async () => {
+ element = await fixture<GrChecksTab>(html`<gr-checks-tab></gr-checks-tab>`);
+ const getChecksModel = resolve(element, checksModelToken);
+ setAllFakeRuns(getChecksModel());
+ });
+
+ test('renders', async () => {
+ await element.updateComplete;
+ assert.equal(element.runs.length, 44);
+ expect(element).shadowDom.to.equal(`
+ <div class="container">
+ <gr-checks-runs
+ class="runs"
+ collapsed=""
+ >
+ </gr-checks-runs>
+ <gr-checks-results class="results">
+ </gr-checks-results>
+ </div>
+ `);
+ });
+
+ test('select from tab state', async () => {
+ element.tabState = {
+ checksTab: {
+ statusOrCategory: Category.ERROR,
+ filter: 'elim',
+ select: 'fake',
+ attempt: 3,
+ },
+ };
+ await element.updateComplete;
+ assert.equal(element.selectedRuns.length, 39);
+ assert.equal(element.selectedAttempts.get(fakeRun4_3.checkName), 3);
});
});
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
index cfc424d..f8f4787 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
@@ -186,6 +186,12 @@
commentId?: UrlEncodedCommentId;
forceReload?: boolean;
tab?: string;
+ /** regular expression for filtering check runs */
+ filter?: string;
+ /** regular expression for selecting check runs */
+ select?: string;
+ /** selected attempt for selected check runs */
+ attempt?: number;
}
export interface GenerateUrlRepoViewParameters {
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index 2a35494..5c0a2ea 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -1614,6 +1614,17 @@
const tab = ctx.queryMap.get('tab');
if (tab) params.tab = tab;
+ const filter = ctx.queryMap.get('filter');
+ if (filter) params.filter = filter;
+ const select = ctx.queryMap.get('select');
+ if (select) params.select = select;
+ const attempt = ctx.queryMap.get('attempt');
+ if (attempt) {
+ const attemptInt = parseInt(attempt);
+ if (!isNaN(attemptInt) && attemptInt > 0) {
+ params.attempt = attemptInt;
+ }
+ }
this.reporting.setRepoName(params.project);
this.reporting.setChangeId(changeNum);
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
index a7de155..148286e 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
@@ -1395,6 +1395,27 @@
assert.isFalse(redirectStub.called);
assert.isTrue(normalizeRangeStub.called);
});
+
+ test('params', () => {
+ normalizeRangeStub.returns(false);
+ sinon.stub(element, '_generateUrl').returns('foo');
+ const ctx = makeParams(null, '');
+ ctx.queryMap.set('tab', 'checks');
+ ctx.queryMap.set('filter', 'fff');
+ ctx.queryMap.set('select', 'sss');
+ ctx.queryMap.set('attempt', '1');
+ assertDataToParams(ctx, '_handleChangeRoute', {
+ view: GerritView.CHANGE,
+ project: 'foo/bar',
+ changeNum: 1234,
+ basePatchNum: 4,
+ patchNum: 7,
+ attempt: 1,
+ filter: 'fff',
+ select: 'sss',
+ tab: 'checks',
+ });
+ });
});
suite('_handleDiffRoute', () => {
diff --git a/polygerrit-ui/app/elements/gr-app-types.ts b/polygerrit-ui/app/elements/gr-app-types.ts
index 8ff7734..9c0ce9e 100644
--- a/polygerrit-ui/app/elements/gr-app-types.ts
+++ b/polygerrit-ui/app/elements/gr-app-types.ts
@@ -132,6 +132,12 @@
commentId?: UrlEncodedCommentId;
forceReload?: boolean;
tab?: string;
+ /** regular expression for filtering check runs */
+ filter?: string;
+ /** regular expression for selecting check runs */
+ select?: string;
+ /** selected attempt for selected check runs */
+ attempt?: number;
}
export interface AppElementJustRegisteredParams {
diff --git a/polygerrit-ui/app/models/checks/checks-fakes.ts b/polygerrit-ui/app/models/checks/checks-fakes.ts
index 7838b9a..1002b21 100644
--- a/polygerrit-ui/app/models/checks/checks-fakes.ts
+++ b/polygerrit-ui/app/models/checks/checks-fakes.ts
@@ -22,7 +22,7 @@
RunStatus,
TagColor,
} from '../../api/checks';
-import {CheckRun} from './checks-model';
+import {CheckRun, ChecksModel, ChecksPatchset} from './checks-model';
// TODO(brohlfs): Eventually these fakes should be removed. But they have proven
// to be super convenient for testing, debugging and demoing, so I would like to
@@ -427,3 +427,105 @@
isLatestAttempt: true,
attemptDetails: [],
};
+
+export function clearAllFakeRuns(model: ChecksModel) {
+ model.updateStateSetResults(
+ 'f0',
+ [],
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+ model.updateStateSetResults(
+ 'f1',
+ [],
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+ model.updateStateSetResults(
+ 'f2',
+ [],
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+ model.updateStateSetResults(
+ 'f3',
+ [],
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+ model.updateStateSetResults(
+ 'f4',
+ [],
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+ model.updateStateSetResults(
+ 'f5',
+ [],
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+}
+
+export function setAllFakeRuns(model: ChecksModel) {
+ model.updateStateSetResults(
+ 'f0',
+ [fakeRun0],
+ fakeActions,
+ fakeLinks,
+ 'ETA: 1 min',
+ ChecksPatchset.LATEST
+ );
+ model.updateStateSetResults(
+ 'f1',
+ [fakeRun1],
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+ model.updateStateSetResults(
+ 'f2',
+ [fakeRun2],
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+ model.updateStateSetResults(
+ 'f3',
+ [fakeRun3],
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+ model.updateStateSetResults(
+ 'f4',
+ fakeRun4Att,
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+ model.updateStateSetResults(
+ 'f5',
+ [fakeRun5],
+ [],
+ [],
+ undefined,
+ ChecksPatchset.LATEST
+ );
+}
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts
index 4f24535..3a46e60 100644
--- a/polygerrit-ui/app/types/events.ts
+++ b/polygerrit-ui/app/types/events.ts
@@ -240,6 +240,12 @@
export interface ChecksTabState {
statusOrCategory?: RunStatus | Category;
checkName?: string;
+ /** regular expression for filtering runs */
+ filter?: string;
+ /** regular expression for selecting runs */
+ select?: string;
+ /** selected attempt for selected runs */
+ attempt?: number;
}
export type SwitchTabEvent = CustomEvent<SwitchTabEventDetail>;
diff --git a/polygerrit-ui/app/utils/dom-util.ts b/polygerrit-ui/app/utils/dom-util.ts
index 34f0bc1..6f1a27a 100644
--- a/polygerrit-ui/app/utils/dom-util.ts
+++ b/polygerrit-ui/app/utils/dom-util.ts
@@ -277,7 +277,10 @@
) {
const observer = new IntersectionObserver(
(entries: IntersectionObserverEntry[]) => {
- check(entries.length === 1, 'Expected one intersection observer entry.');
+ check(
+ entries.length === 1,
+ `Expected 1 intersection observer entry, but got ${entries.length}.`
+ );
const entry = entries[0];
if (entry.isIntersecting) {
observer.unobserve(entry.target);