Merge "Forward dependency-requests from gr-alert to gr-error-manager"
diff --git a/Documentation/config-groups.txt b/Documentation/config-groups.txt
index 0917515..4abb223 100644
--- a/Documentation/config-groups.txt
+++ b/Documentation/config-groups.txt
@@ -34,7 +34,7 @@
group, there is a ref, stored as a sharded UUID, e.g.
----
- refs/groups/ef/deafbeefdeafbeefdeafbeefdeafbeefdeafbeef
+ refs/groups/de/deafbeefdeafbeefdeafbeefdeafbeefdeafbeef
----
The ref points to commits holding files. The files are
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 8239d62..b1f9912 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -9,14 +9,15 @@
[options="header"]
|=================================================
-|Description | Default Query
-|All > Open | status:open '(or is:open)'
-|All > Merged | status:merged
-|All > Abandoned | status:abandoned
-|My > Watched Changes | is:watched is:open
-|My > Starred Changes | is:starred
-|My > Draft Comments | has:draft
-|Open changes in Foo | status:open project:Foo
+|Description | Default Query
+|Changes > Open | status:open '(or is:open)'
+|Changes > Merged | status:merged
+|Changes > Abandoned | status:abandoned
+|Your > Watched Changes | is:watched is:open
+|Your > Starred Changes | is:starred
+|Your > Draft Comments | has:draft
+|Your > Edits | has:edit
+|Open changes in Foo | status:open project:Foo
|=================================================
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeUpdateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeUpdateTest.java
index e4cb239..0bb0578 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeUpdateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeUpdateTest.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.Change;
@@ -256,10 +257,12 @@
return c;
}
+ @CanIgnoreReturnValue
private AttentionSetUpdate addToAttentionSet(ChangeUpdate update) {
return addToAttentionSet(update, otherUser);
}
+ @CanIgnoreReturnValue
private AttentionSetUpdate addToAttentionSet(ChangeUpdate update, IdentifiedUser user) {
AttentionSetUpdate attentionSetUpdate =
AttentionSetUpdate.createForWrite(
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
index a20a389..62de868 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
@@ -38,13 +38,28 @@
import {menuPageStyles} from '../../../styles/gr-menu-page-styles';
import {pageNavStyles} from '../../../styles/gr-page-nav-styles';
import {sharedStyles} from '../../../styles/shared-styles';
-import {LitElement, PropertyValues, css, html} from 'lit';
+import {LitElement, PropertyValues, css, html, nothing} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {ifDefined} from 'lit/directives/if-defined.js';
import {ValueChangedEvent} from '../../../types/events';
-import {AdminChildView, AdminViewState} from '../../../models/views/admin';
-import {GroupDetailView, GroupViewState} from '../../../models/views/group';
-import {RepoDetailView, RepoViewState} from '../../../models/views/repo';
+import {
+ AdminChildView,
+ adminViewModelToken,
+ AdminViewState,
+} from '../../../models/views/admin';
+import {
+ GroupDetailView,
+ groupViewModelToken,
+ GroupViewState,
+} from '../../../models/views/group';
+import {
+ RepoDetailView,
+ repoViewModelToken,
+ RepoViewState,
+} from '../../../models/views/repo';
+import {resolve} from '../../../models/dependency';
+import {subscribe} from '../../lit/subscription-controller';
+import {merge} from 'rxjs';
const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/;
@@ -57,14 +72,25 @@
parent?: GroupId | RepoName;
}
-// The type is matched to the _showAdminView function from the gr-app-element
-type AdminViewParams = AdminViewState | GroupViewState | RepoViewState;
+type ViewState = AdminViewState | GroupViewState | RepoViewState;
-function getAdminViewParamsDetail(
- params: AdminViewParams
+function isAdminView(viewState?: ViewState): viewState is AdminViewState {
+ return viewState?.view === GerritView.ADMIN;
+}
+
+function isGroupView(viewState?: ViewState): viewState is GroupViewState {
+ return viewState?.view === GerritView.GROUP;
+}
+
+function isRepoView(viewState?: ViewState): viewState is RepoViewState {
+ return viewState?.view === GerritView.REPO;
+}
+
+function getDetailView(
+ state: ViewState
): GroupDetailView | RepoDetailView | undefined {
- if (params.view !== GerritView.ADMIN) {
- return params.detail;
+ if (state.view !== GerritView.ADMIN) {
+ return state.detail;
}
return undefined;
}
@@ -74,10 +100,7 @@
private account?: AccountDetailInfo;
@property({type: Object})
- params?: AdminViewParams;
-
- @property({type: String})
- path?: string;
+ viewState?: ViewState;
@property({type: String})
adminView?: string;
@@ -109,6 +132,29 @@
private readonly restApiService = getAppContext().restApiService;
+ private readonly getAdminViewModel = resolve(this, adminViewModelToken);
+
+ private readonly getGroupViewModel = resolve(this, groupViewModelToken);
+
+ private readonly getRepoViewModel = resolve(this, repoViewModelToken);
+
+ constructor() {
+ super();
+ subscribe(
+ this,
+ () =>
+ merge(
+ this.getAdminViewModel().state$,
+ this.getGroupViewModel().state$,
+ this.getRepoViewModel().state$
+ ),
+ x => {
+ this.viewState = x;
+ if (this.needsReload()) this.reload();
+ }
+ );
+ }
+
override connectedCallback() {
super.connectedCallback();
this.reload();
@@ -186,7 +232,7 @@
}
private renderAdminNavSubsection(item: NavLink) {
- if (!item.subsection) return;
+ if (!item.subsection) return nothing;
return html`
<!--If a section has a subsection, render that.-->
@@ -224,7 +270,7 @@
}
private renderSubsectionLinks() {
- if (!this.subsectionLinks?.length) return;
+ if (!this.subsectionLinks?.length) return nothing;
return html`
<section class="mainHeader">
@@ -244,82 +290,62 @@
}
private renderRepoList() {
- const params = this.params as AdminViewState;
- if (
- !(
- params?.view === GerritView.ADMIN &&
- params?.adminView === AdminChildView.REPOS
- )
- )
- return;
+ if (!isAdminView(this.viewState)) return nothing;
+ if (this.viewState.adminView !== AdminChildView.REPOS) return nothing;
return html`
<div class="main table">
- <gr-repo-list class="table" .params=${params}></gr-repo-list>
+ <gr-repo-list class="table" .params=${this.viewState}></gr-repo-list>
</div>
`;
}
private renderGroupList() {
- const params = this.params as AdminViewState;
- if (
- !(
- params?.view === GerritView.ADMIN &&
- params?.adminView === AdminChildView.GROUPS
- )
- )
- return;
+ if (!isAdminView(this.viewState)) return nothing;
+ if (this.viewState.adminView !== AdminChildView.GROUPS) return nothing;
return html`
<div class="main table">
- <gr-admin-group-list class="table" .params=${params}>
+ <gr-admin-group-list class="table" .params=${this.viewState}>
</gr-admin-group-list>
</div>
`;
}
private renderPluginList() {
- const params = this.params as AdminViewState;
- if (
- !(
- params?.view === GerritView.ADMIN &&
- params?.adminView === AdminChildView.PLUGINS
- )
- )
- return;
+ if (!isAdminView(this.viewState)) return nothing;
+ if (this.viewState.adminView !== AdminChildView.PLUGINS) return nothing;
return html`
<div class="main table">
- <gr-plugin-list class="table" .params=${params}></gr-plugin-list>
+ <gr-plugin-list
+ class="table"
+ .params=${this.viewState}
+ ></gr-plugin-list>
</div>
`;
}
private renderRepoMain() {
- const params = this.params as RepoViewState;
- if (
- !(
- params?.view === GerritView.REPO &&
- (!params?.detail || params?.detail === RepoDetailView.GENERAL)
- )
- )
- return;
+ if (!isRepoView(this.viewState)) return nothing;
+ const detail = this.viewState.detail ?? RepoDetailView.GENERAL;
+ if (detail !== RepoDetailView.GENERAL) return nothing;
return html`
<div class="main breadcrumbs">
- <gr-repo .repo=${params.repo}></gr-repo>
+ <gr-repo .repo=${this.viewState.repo}></gr-repo>
</div>
`;
}
private renderGroup() {
- const params = this.params as GroupViewState;
- if (!(params?.view === GerritView.GROUP && !params?.detail)) return;
+ if (!isGroupView(this.viewState)) return nothing;
+ if (this.viewState.detail !== undefined) return nothing;
return html`
<div class="main breadcrumbs">
<gr-group
- .groupId=${params.groupId}
+ .groupId=${this.viewState.groupId}
@name-changed=${(e: CustomEvent<GroupNameChangedDetail>) => {
this.updateGroupName(e);
}}
@@ -329,122 +355,81 @@
}
private renderGroupMembers() {
- const params = this.params as GroupViewState;
- if (
- !(
- params?.view === GerritView.GROUP &&
- params?.detail === GroupDetailView.MEMBERS
- )
- )
- return;
+ if (!isGroupView(this.viewState)) return nothing;
+ if (this.viewState.detail !== GroupDetailView.MEMBERS) return nothing;
return html`
<div class="main breadcrumbs">
- <gr-group-members .groupId=${params.groupId}></gr-group-members>
+ <gr-group-members .groupId=${this.viewState.groupId}></gr-group-members>
</div>
`;
}
private renderGroupAuditLog() {
- const params = this.params as GroupViewState;
- if (
- !(
- params?.view === GerritView.GROUP &&
- params?.detail === GroupDetailView.LOG
- )
- )
- return;
+ if (!isGroupView(this.viewState)) return nothing;
+ if (this.viewState.detail !== GroupDetailView.LOG) return nothing;
return html`
<div class="main table breadcrumbs">
<gr-group-audit-log
class="table"
- .groupId=${params.groupId}
+ .groupId=${this.viewState.groupId}
></gr-group-audit-log>
</div>
`;
}
private renderRepoDetailList() {
- const params = this.params as RepoViewState;
- if (
- !(
- params?.view === GerritView.REPO &&
- (params?.detail === RepoDetailView.BRANCHES ||
- params?.detail === RepoDetailView.TAGS)
- )
- )
- return;
+ if (!isRepoView(this.viewState)) return nothing;
+ const detail = this.viewState.detail;
+ if (detail !== RepoDetailView.BRANCHES && detail !== RepoDetailView.TAGS) {
+ return nothing;
+ }
return html`
<div class="main table breadcrumbs">
<gr-repo-detail-list
class="table"
- .params=${params}
+ .params=${this.viewState}
></gr-repo-detail-list>
</div>
`;
}
private renderRepoCommands() {
- const params = this.params as RepoViewState;
- if (
- !(
- params?.view === GerritView.REPO &&
- params?.detail === RepoDetailView.COMMANDS
- )
- )
- return;
+ if (!isRepoView(this.viewState)) return nothing;
+ if (this.viewState.detail !== RepoDetailView.COMMANDS) return nothing;
return html`
<div class="main breadcrumbs">
- <gr-repo-commands .repo=${params.repo}></gr-repo-commands>
+ <gr-repo-commands .repo=${this.viewState.repo}></gr-repo-commands>
</div>
`;
}
private renderRepoAccess() {
- const params = this.params as RepoViewState;
- if (
- !(
- params?.view === GerritView.REPO &&
- params?.detail === RepoDetailView.ACCESS
- )
- )
- return;
+ if (!isRepoView(this.viewState)) return nothing;
+ if (this.viewState.detail !== RepoDetailView.ACCESS) return nothing;
return html`
<div class="main breadcrumbs">
- <gr-repo-access
- .path=${this.path}
- .repo=${params.repo}
- ></gr-repo-access>
+ <gr-repo-access .repo=${this.viewState.repo}></gr-repo-access>
</div>
`;
}
private renderRepoDashboards() {
- const params = this.params as RepoViewState;
- if (
- !(
- params?.view === GerritView.REPO &&
- params?.detail === RepoDetailView.DASHBOARDS
- )
- )
- return;
+ if (!isRepoView(this.viewState)) return nothing;
+ if (this.viewState.detail !== RepoDetailView.DASHBOARDS) return nothing;
return html`
<div class="main table breadcrumbs">
- <gr-repo-dashboards .repo=${params.repo}></gr-repo-dashboards>
+ <gr-repo-dashboards .repo=${this.viewState.repo}></gr-repo-dashboards>
</div>
`;
}
override willUpdate(changedProperties: PropertyValues) {
- if (changedProperties.has('params')) {
- this.paramsChanged();
- }
-
if (changedProperties.has('groupId')) {
this.computeGroupName();
}
@@ -516,18 +501,18 @@
}
private computeSelectValue() {
- if (!this.params?.view) return;
- return `${this.params.view}${getAdminViewParamsDetail(this.params) ?? ''}`;
+ if (!this.viewState?.view) return;
+ return `${this.viewState.view}${getDetailView(this.viewState) ?? ''}`;
}
// private but used in test
selectedIsCurrentPage(selected: AdminSubsectionLink) {
- if (!this.params) return false;
+ if (!this.viewState) return false;
return (
selected.parent === (this.repoName ?? this.groupId) &&
- selected.view === this.params.view &&
- selected.detailType === getAdminViewParamsDetail(this.params)
+ selected.view === this.viewState.view &&
+ selected.detailType === getDetailView(this.viewState)
);
}
@@ -548,23 +533,21 @@
GerritNav.navigateToRelativeUrl(selected.url);
}
- private async paramsChanged() {
- if (this.needsReload()) await this.reload();
- }
-
needsReload(): boolean {
- if (!this.params) return false;
+ if (!this.viewState) return false;
let needsReload = false;
const newRepoName =
- this.params.view === GerritView.REPO ? this.params.repo : undefined;
+ this.viewState.view === GerritView.REPO ? this.viewState.repo : undefined;
if (newRepoName !== this.repoName) {
this.repoName = newRepoName;
// Reloads the admin menu.
needsReload = true;
}
const newGroupId =
- this.params.view === GerritView.GROUP ? this.params.groupId : undefined;
+ this.viewState.view === GerritView.GROUP
+ ? this.viewState.groupId
+ : undefined;
if (newGroupId !== this.groupId) {
this.groupId = newGroupId;
// Reloads the admin menu.
@@ -572,8 +555,8 @@
}
if (
this.breadcrumbParentName &&
- (this.params.view !== GerritView.GROUP || !this.params.groupId) &&
- (this.params.view !== GerritView.REPO || !this.params.repo)
+ (this.viewState.view !== GerritView.GROUP || !this.viewState.groupId) &&
+ (this.viewState.view !== GerritView.REPO || !this.viewState.repo)
) {
needsReload = true;
}
@@ -595,39 +578,32 @@
itemView?: GerritView | AdminChildView,
detailType?: GroupDetailView | RepoDetailView
) {
- const params = this.params;
- if (!params) return '';
- // Group params are structured differently from admin params. Compute
+ const viewState = this.viewState;
+ if (!viewState) return '';
+ // Group view state is structured differently than admin view state. Compute
// selected differently for groups.
- // TODO(wyatta): Simplify this when all routes work like group params.
- if (params.view === GerritView.GROUP && itemView === GerritView.GROUP) {
- if (!params.detail && !detailType) {
+ // TODO(wyatta): Simplify this when all routes work like group view state.
+ if (viewState.view === GerritView.GROUP && itemView === GerritView.GROUP) {
+ if (!viewState.detail && !detailType) {
return 'selected';
}
- if (params.detail === detailType) {
+ if (viewState.detail === detailType) {
return 'selected';
}
return '';
}
- if (params.view === GerritView.REPO && itemView === GerritView.REPO) {
- if (!params.detail && !detailType) {
+ if (viewState.view === GerritView.REPO && itemView === GerritView.REPO) {
+ if (!viewState.detail && !detailType) {
return 'selected';
}
- if (params.detail === detailType) {
+ if (viewState.detail === detailType) {
return 'selected';
}
return '';
}
- // TODO(TS): The following condition seems always false, because params
- // never has detailType property. Remove it.
- if (
- (params as unknown as AdminSubsectionLink).detailType &&
- (params as unknown as AdminSubsectionLink).detailType !== detailType
- ) {
- return '';
- }
- return params.view === GerritView.ADMIN && itemView === params.adminView
+ return viewState.view === GerritView.ADMIN &&
+ itemView === viewState.adminView
? 'selected'
: '';
}
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.ts b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.ts
index 3097e62..a40f1de 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.ts
@@ -8,12 +8,7 @@
import {AdminSubsectionLink, GrAdminView} from './gr-admin-view';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
-import {
- mockPromise,
- stubBaseUrl,
- stubElement,
- stubRestApi,
-} from '../../../test/test-utils';
+import {stubBaseUrl, stubElement, stubRestApi} from '../../../test/test-utils';
import {GerritView} from '../../../services/router/router-model';
import {query, queryAll, queryAndAssert} from '../../../test/test-utils';
import {GrRepoList} from '../gr-repo-list/gr-repo-list';
@@ -81,7 +76,7 @@
},
];
- element.params = {
+ element.viewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.REPOS,
};
@@ -215,7 +210,7 @@
assert.isNotOk(element.filteredLinks![2].subsection);
});
- test('Nav is reloaded when repo changes', async () => {
+ test('Needs reload when repo changes', async () => {
stubRestApi('getAccountCapabilities').returns(
Promise.resolve(createAdminCapabilities())
);
@@ -225,16 +220,18 @@
registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
})
);
- const reloadStub = sinon.stub(element, 'reload');
- element.params = {repo: 'Test Repo' as RepoName, view: GerritView.REPO};
+
+ element.viewState = {repo: 'Repo 1' as RepoName, view: GerritView.REPO};
+ assert.isTrue(element.needsReload());
+ await element.reload();
await element.updateComplete;
- assert.equal(reloadStub.callCount, 1);
- element.params = {repo: 'Test Repo 2' as RepoName, view: GerritView.REPO};
+
+ element.viewState = {repo: 'Repo 2' as RepoName, view: GerritView.REPO};
+ assert.isTrue(element.needsReload());
await element.updateComplete;
- assert.equal(reloadStub.callCount, 2);
});
- test('Nav is reloaded when group changes', async () => {
+ test('Needs reload when group changes', async () => {
sinon.stub(element, 'computeGroupName');
stubRestApi('getAccountCapabilities').returns(
Promise.resolve(createAdminCapabilities())
@@ -245,13 +242,11 @@
registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
})
);
- const reloadStub = sinon.stub(element, 'reload');
- element.params = {groupId: '1' as GroupId, view: GerritView.GROUP};
- await element.updateComplete;
- assert.equal(reloadStub.callCount, 1);
+ element.viewState = {groupId: '1' as GroupId, view: GerritView.GROUP};
+ assert.isTrue(element.needsReload());
});
- test('Nav is reloaded when changing from repo to group', async () => {
+ test('Needs reload when changing from repo to group', async () => {
element.repoName = 'Test Repo' as RepoName;
stubRestApi('getAccount').returns(
Promise.resolve({
@@ -266,26 +261,23 @@
await element.updateComplete;
sinon.stub(element, 'computeGroupName');
- const reloadStub = sinon.stub(element, 'reload');
const groupId = '1' as GroupId;
- element.params = {groupId, view: GerritView.GROUP};
+ element.viewState = {groupId, view: GerritView.GROUP};
await element.updateComplete;
- assert.equal(reloadStub.callCount, 1);
+ assert.isTrue(element.needsReload());
assert.equal(element.groupId, groupId);
});
- test('Nav is reloaded when group name changes', async () => {
+ test('Needs reload when group name changes', async () => {
const newName = 'newName' as GroupName;
- const reloadCalled = mockPromise();
sinon.stub(element, 'computeGroupName');
- sinon.stub(element, 'reload').callsFake(() => {
- reloadCalled.resolve();
- return Promise.resolve();
- });
- element.params = {groupId: '1' as GroupId, view: GerritView.GROUP};
+ element.viewState = {groupId: '1' as GroupId, view: GerritView.GROUP};
element.groupName = 'oldName' as GroupName;
+ assert.isTrue(element.needsReload());
+ await element.reload();
await element.updateComplete;
+
queryAndAssert<GrGroup>(element, 'gr-group').dispatchEvent(
new CustomEvent('name-changed', {
detail: {name: newName},
@@ -293,7 +285,6 @@
bubbles: true,
})
);
- await reloadCalled;
assert.equal(element.groupName, newName);
});
@@ -317,7 +308,7 @@
test('Dropdown only triggers navigation on explicit select', async () => {
element.repoName = 'my-repo' as RepoName;
- element.params = {
+ element.viewState = {
repo: 'my-repo' as RepoName,
view: GerritView.REPO,
detail: RepoDetailView.ACCESS,
@@ -488,7 +479,7 @@
test('selectedIsCurrentPage', () => {
element.repoName = 'my-repo' as RepoName;
- element.params = {view: GerritView.REPO, repo: 'my-repo' as RepoName};
+ element.viewState = {view: GerritView.REPO, repo: 'my-repo' as RepoName};
const selected = {
view: GerritView.REPO,
parent: 'my-repo' as RepoName,
@@ -563,7 +554,7 @@
});
test('repo list', async () => {
- element.params = {
+ element.viewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.REPOS,
openCreateModal: false,
@@ -575,7 +566,7 @@
});
test('repo', async () => {
- element.params = {
+ element.viewState = {
view: GerritView.REPO,
repo: 'foo' as RepoName,
};
@@ -588,7 +579,7 @@
});
test('repo access', async () => {
- element.params = {
+ element.viewState = {
view: GerritView.REPO,
detail: RepoDetailView.ACCESS,
repo: 'foo' as RepoName,
@@ -602,7 +593,7 @@
});
test('repo dashboards', async () => {
- element.params = {
+ element.viewState = {
view: GerritView.REPO,
detail: RepoDetailView.DASHBOARDS,
repo: 'foo' as RepoName,
@@ -637,7 +628,7 @@
});
test('group list', async () => {
- element.params = {
+ element.viewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.GROUPS,
openCreateModal: false,
@@ -649,12 +640,12 @@
});
test('internal group', async () => {
- element.params = {
+ element.viewState = {
view: GerritView.GROUP,
groupId: '1234' as GroupId,
};
element.groupName = 'foo' as GroupName;
- await element.reload();
+ if (element.needsReload()) await element.reload();
await element.updateComplete;
const subsectionItems = queryAll<HTMLLIElement>(
element,
@@ -674,12 +665,12 @@
id: 'external-id',
})
);
- element.params = {
+ element.viewState = {
view: GerritView.GROUP,
groupId: '1234' as GroupId,
};
element.groupName = 'foo' as GroupName;
- await element.reload();
+ if (element.needsReload()) await element.reload();
await element.updateComplete;
const subsectionItems = queryAll<HTMLLIElement>(
element,
@@ -693,13 +684,13 @@
});
test('group members', async () => {
- element.params = {
+ element.viewState = {
view: GerritView.GROUP,
detail: GroupDetailView.MEMBERS,
groupId: '1234' as GroupId,
};
element.groupName = 'foo' as GroupName;
- await element.reload();
+ if (element.needsReload()) await element.reload();
await element.updateComplete;
const selected = queryAndAssert(element, 'gr-page-nav .selected');
assert.isOk(selected);
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
index ad97bea..d47926b 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
@@ -64,9 +64,6 @@
@property({type: String})
repo?: RepoName;
- @property({type: String})
- path?: string;
-
// private but used in test
@state() canUpload?: boolean = false; // restAPI can return undefined
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
index 66569bb..937276d 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -8,7 +8,6 @@
import '../gr-user-header/gr-user-header';
import {page} from '../../../utils/page-wrapper-utils';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {AppElementParams} from '../../gr-app-types';
import {
AccountDetailInfo,
AccountId,
@@ -20,12 +19,17 @@
import {ChangeStarToggleStarDetail} from '../../shared/gr-change-star/gr-change-star';
import {fireAlert, fireEvent, fireTitleChange} from '../../../utils/event-util';
import {getAppContext} from '../../../services/app-context';
-import {GerritView} from '../../../services/router/router-model';
import {sharedStyles} from '../../../styles/shared-styles';
-import {LitElement, PropertyValues, html, css} from 'lit';
-import {customElement, property, state, query} from 'lit/decorators.js';
+import {LitElement, PropertyValues, html, css, nothing} from 'lit';
+import {customElement, state, query} from 'lit/decorators.js';
import {ValueChangedEvent} from '../../../types/events';
-import {createSearchUrl} from '../../../models/views/search';
+import {
+ createSearchUrl,
+ searchViewModelToken,
+ SearchViewState,
+} from '../../../models/views/search';
+import {resolve} from '../../../models/dependency';
+import {subscribe} from '../../lit/subscription-controller';
const LOOKUP_QUERY_PATTERNS: RegExp[] = [
/^\s*i?[0-9a-f]{7,40}\s*$/i, // CHANGE_ID
@@ -52,14 +56,29 @@
@query('#nextArrow') protected nextArrow?: HTMLAnchorElement;
- @property({type: Object})
- params?: AppElementParams;
+ private _viewState?: SearchViewState;
- @property({type: Object})
- account: AccountDetailInfo | null = null;
+ @state()
+ get viewState() {
+ return this._viewState;
+ }
- @property({type: Object})
- preferences?: PreferencesInput;
+ set viewState(viewState: SearchViewState | undefined) {
+ if (this._viewState === viewState) return;
+ const oldViewState = this._viewState;
+ this._viewState = viewState;
+ this.viewStateChanged();
+ this.requestUpdate('viewState', oldViewState);
+ }
+
+ // private but used in test
+ @state() account?: AccountDetailInfo;
+
+ // private but used in test
+ @state() loggedIn = false;
+
+ // private but used in test
+ @state() preferences?: PreferencesInput;
// private but used in test
@state() changesPerPage?: number;
@@ -88,20 +107,41 @@
private reporting = getAppContext().reportingService;
+ private userModel = getAppContext().userModel;
+
+ private readonly getViewModel = resolve(this, searchViewModelToken);
+
constructor() {
super();
this.addEventListener('next-page', () => this.handleNextPage());
this.addEventListener('previous-page', () => this.handlePreviousPage());
this.addEventListener('reload', () => this.reload());
- }
-
- override connectedCallback() {
- super.connectedCallback();
- this.loadPreferences();
- }
-
- override disconnectedCallback() {
- super.disconnectedCallback();
+ subscribe(
+ this,
+ () => this.getViewModel().state$,
+ x => (this.viewState = x)
+ );
+ subscribe(
+ this,
+ () => this.userModel.account$,
+ x => (this.account = x)
+ );
+ subscribe(
+ this,
+ () => this.userModel.loggedIn$,
+ x => (this.loggedIn = x)
+ );
+ subscribe(
+ this,
+ () => this.userModel.preferences$,
+ x => {
+ this.preferences = x;
+ if (this.changesPerPage !== x.changes_per_page) {
+ this.changesPerPage = x.changes_per_page;
+ this.viewStateChanged();
+ }
+ }
+ );
}
static override get styles() {
@@ -148,19 +188,18 @@
}
override render() {
- const loggedIn = !!(this.account && Object.keys(this.account).length > 0);
// In case of an internal reload we want the ChangeList section components
// to remain in the DOM so that the Bulk Actions Model associated with them
// is not recreated after the reload resulting in user selections being lost
return html`
<div class="loading" ?hidden=${!this.loading}>Loading...</div>
<div ?hidden=${this.loading}>
- ${this.renderRepoHeader()} ${this.renderUserHeader(loggedIn)}
+ ${this.renderRepoHeader()} ${this.renderUserHeader()}
<gr-change-list
.account=${this.account}
.changes=${this.changes}
.preferences=${this.preferences}
- .showStar=${loggedIn}
+ .showStar=${this.loggedIn}
.selectedIndex=${this.selectedIndex}
@selected-index-changed=${(e: ValueChangedEvent<number>) => {
this.selectedIndex = e.detail.value;
@@ -176,25 +215,25 @@
}
private renderRepoHeader() {
- if (!this.repo) return;
+ if (!this.repo) return nothing;
return html` <gr-repo-header .repo=${this.repo}></gr-repo-header> `;
}
- private renderUserHeader(loggedIn: boolean) {
- if (!this.userId) return;
+ private renderUserHeader() {
+ if (!this.userId) return nothing;
return html`
<gr-user-header
.userId=${this.userId}
showDashboardLink
- .loggedIn=${loggedIn}
+ .loggedIn=${this.loggedIn}
></gr-user-header>
`;
}
private renderChangeListViewNav() {
- if (this.loading || !this.changes || !this.changes.length) return;
+ if (this.loading || !this.changes || !this.changes.length) return nothing;
return html`
<nav>
@@ -205,7 +244,7 @@
}
private renderPrevArrow() {
- if (this.offset === 0) return;
+ if (this.offset === 0) return nothing;
return html`
<a id="prevArrow" href=${this.computeNavLink(-1)}>
@@ -215,13 +254,9 @@
}
private renderNextArrow() {
- if (
- !(
- this.changes?.length &&
- this.changes[this.changes.length - 1]._more_changes
- )
- )
- return;
+ const changesCount = this.changes?.length ?? 0;
+ if (changesCount === 0) return nothing;
+ if (!this.changes?.[changesCount - 1]._more_changes) return nothing;
return html`
<a id="nextArrow" href=${this.computeNavLink(1)}>
@@ -231,10 +266,6 @@
}
override willUpdate(changedProperties: PropertyValues) {
- if (changedProperties.has('params')) {
- this.paramsChanged();
- }
-
if (changedProperties.has('changes')) {
this.changesChanged();
}
@@ -249,60 +280,39 @@
});
}
- private paramsChanged() {
- const value = this.params;
- if (!value || value.view !== GerritView.SEARCH) return;
- const offset = isNaN(Number(value.offset)) ? 0 : Number(value.offset);
+ // private, but visible for testing
+ viewStateChanged() {
+ if (!this.viewState) return;
- if (this.query !== (value.query ?? '')) {
- this.selectedIndex = 0;
- }
+ let offset = Number(this.viewState.offset);
+ if (isNaN(offset)) offset = 0;
+ const query = this.viewState.query ?? '';
+ if (this.query !== query) this.selectedIndex = 0;
this.loading = true;
- this.query = value.query ?? '';
+ this.query = query;
this.offset = offset;
// NOTE: This method may be called before attachment. Fire title-change
// in an async so that attachment to the DOM can take place first.
setTimeout(() => fireTitleChange(this, this.query));
- this.restApiService
- .getPreferences()
- .then(prefs => {
- if (!prefs) {
- throw new Error('getPreferences returned undefined');
- }
- this.changesPerPage = prefs.changes_per_page;
- return this.getChanges();
- })
- .then(changes => {
- changes = changes || [];
- if (this.query && changes.length === 1) {
- for (const queryPattern of LOOKUP_QUERY_PATTERNS) {
- if (this.query.match(queryPattern)) {
- // "Back"/"Forward" buttons work correctly only with
- // opt_redirect options
- GerritNav.navigateToChange(changes[0], {
- redirect: true,
- });
- return;
- }
+ return this.getChanges().then(changes => {
+ changes = changes || [];
+ if (this.query && changes.length === 1) {
+ for (const queryPattern of LOOKUP_QUERY_PATTERNS) {
+ if (this.query.match(queryPattern)) {
+ // "Back"/"Forward" buttons work correctly only with
+ // opt_redirect options
+ GerritNav.navigateToChange(changes[0], {
+ redirect: true,
+ });
+ return;
}
}
- this.changes = changes;
- this.loading = false;
- });
- }
-
- private loadPreferences() {
- return this.restApiService.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- this.restApiService.getPreferences().then(preferences => {
- this.preferences = preferences;
- });
- } else {
- this.preferences = {};
}
+ this.changes = changes;
+ this.loading = false;
});
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
index e360899..f88f668 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
@@ -32,11 +32,14 @@
let element: GrChangeListView;
setup(async () => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('getChanges').returns(Promise.resolve([]));
- stubRestApi('getAccountDetails').returns(Promise.resolve(undefined));
- stubRestApi('getAccountStatus').returns(Promise.resolve(undefined));
element = await fixture(html`<gr-change-list-view></gr-change-list-view>`);
+ element.viewState = {
+ view: GerritView.SEARCH,
+ query: 'test-query',
+ offset: '0',
+ };
+ await element.updateComplete;
});
teardown(async () => {
@@ -57,12 +60,7 @@
<div class="loading" hidden="">Loading...</div>
<div>
<gr-change-list> </gr-change-list>
- <nav>
- Page
- <a href="/q/" id="prevArrow">
- <gr-icon icon="chevron_left" aria-label="Older"></gr-icon>
- </a>
- </nav>
+ <nav>Page 1</nav>
</div>
`
);
@@ -295,7 +293,7 @@
promise.resolve();
});
- element.params = {
+ element.viewState = {
view: GerritView.SEARCH,
query: CHANGE_ID,
offset: '',
@@ -314,7 +312,7 @@
promise.resolve();
});
- element.params = {view: GerritView.SEARCH, query: '1', offset: ''};
+ element.viewState = {view: GerritView.SEARCH, query: '1', offset: ''};
await promise;
});
@@ -329,7 +327,7 @@
promise.resolve();
});
- element.params = {
+ element.viewState = {
view: GerritView.SEARCH,
query: COMMIT_HASH,
offset: '',
@@ -341,7 +339,7 @@
sinon.stub(element, 'getChanges').returns(Promise.resolve([]));
const stub = sinon.stub(GerritNav, 'navigateToChange');
- element.params = {
+ element.viewState = {
view: GerritView.SEARCH,
query: CHANGE_ID,
offset: '',
@@ -355,7 +353,7 @@
sinon.stub(element, 'getChanges').returns(Promise.resolve(undefined));
const stub = sinon.stub(GerritNav, 'navigateToChange');
- element.params = {
+ element.viewState = {
view: GerritView.SEARCH,
query: CHANGE_ID,
offset: '',
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index 88bf943..3af856f 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -41,18 +41,22 @@
firePageError,
fireTitleChange,
} from '../../../utils/event-util';
-import {GerritView} from '../../../services/router/router-model';
import {RELOAD_DASHBOARD_INTERVAL_MS} from '../../../constants/constants';
import {ChangeListSection} from '../gr-change-list/gr-change-list';
import {a11yStyles} from '../../../styles/gr-a11y-styles';
import {sharedStyles} from '../../../styles/shared-styles';
-import {LitElement, PropertyValues, html, css} from 'lit';
+import {LitElement, html, css, nothing} from 'lit';
import {customElement, property, state, query} from 'lit/decorators.js';
import {assertIsDefined} from '../../../utils/common-util';
import {Shortcut} from '../../../services/shortcuts/shortcuts-config';
import {ShortcutController} from '../../lit/shortcut-controller';
-import {DashboardViewState} from '../../../models/views/dashboard';
+import {
+ dashboardViewModelToken,
+ DashboardViewState,
+} from '../../../models/views/dashboard';
import {createSearchUrl} from '../../../models/views/search';
+import {subscribe} from '../../lit/subscription-controller';
+import {resolve} from '../../../models/dependency';
const PROJECT_PLACEHOLDER_PATTERN = /\${project}/g;
@@ -79,13 +83,13 @@
@query('#confirmDeleteOverlay') protected confirmDeleteOverlay?: GrOverlay;
@property({type: Object})
- account: AccountDetailInfo | null = null;
+ account?: AccountDetailInfo;
@property({type: Object})
preferences?: PreferencesInput;
- @property({type: Object})
- params?: DashboardViewState;
+ @state()
+ viewState?: DashboardViewState;
// private but used in test
@state() results?: ChangeListSection[];
@@ -103,12 +107,29 @@
private readonly restApiService = getAppContext().restApiService;
+ private readonly userModel = getAppContext().userModel;
+
+ private readonly getViewModel = resolve(this, dashboardViewModelToken);
+
private lastVisibleTimestampMs = 0;
private readonly shortcuts = new ShortcutController(this);
constructor() {
super();
+ subscribe(
+ this,
+ () => this.userModel.account$,
+ x => (this.account = x)
+ );
+ subscribe(
+ this,
+ () => this.getViewModel().state$,
+ x => {
+ this.viewState = x;
+ this.reload();
+ }
+ );
this.addEventListener('reload', () => this.reload());
this.shortcuts.addAbstract(Shortcut.UP_TO_DASHBOARD, () => this.reload());
}
@@ -184,6 +205,7 @@
}
override render() {
+ if (!this.viewState) return nothing;
return html`
${this.renderBanner()} ${this.renderContent()}
<gr-overlay id="confirmDeleteOverlay" with-backdrop>
@@ -271,17 +293,15 @@
private renderUserHeader() {
if (
- !this.params ||
- this.params.view !== GerritView.DASHBOARD ||
- !!this.params.project ||
- !this.params.user ||
- this.params.user === 'self'
+ !!this.viewState?.project ||
+ !this.viewState?.user ||
+ this.viewState?.user === 'self'
) {
return;
}
return html`
- <gr-user-header .userId=${this.params?.user}></gr-user-header>
+ <gr-user-header .userId=${this.viewState?.user}></gr-user-header>
`;
}
@@ -297,12 +317,6 @@
`;
}
- override updated(changedProperties: PropertyValues) {
- if (changedProperties.has('params')) {
- this.paramsChanged();
- }
- }
-
private loadPreferences() {
return this.restApiService.getLoggedIn().then(loggedIn => {
if (loggedIn) {
@@ -354,26 +368,15 @@
return 'Dashboard for ' + user;
}
- private isViewActive(params: DashboardViewState) {
- return params.view === GerritView.DASHBOARD;
- }
-
- // private but used in test
- paramsChanged() {
- return this.reload();
- }
-
/**
* Reloads the element.
*
* private but used in test
*/
reload() {
- if (!this.params || !this.isViewActive(this.params)) {
- return Promise.resolve();
- }
+ if (!this.viewState) return Promise.resolve();
this.loading = true;
- const {project, dashboard, title, user, sections} = this.params;
+ const {project, dashboard, title, user, sections} = this.viewState;
const dashboardPromise: Promise<UserDashboard | undefined> = project
? this.getProjectDashboard(project, dashboard)
: Promise.resolve(
@@ -476,7 +479,7 @@
* And then we want to emphasize the changes where the waiting time is larger.
*/
private maybeSortResults(name: string, results: ChangeInfo[]) {
- const userId = this.account && this.account._account_id;
+ const userId = this.account?._account_id;
const sortedResults = [...results];
if (name === YOUR_TURN.name && userId) {
sortedResults.sort((c1, c2) => {
@@ -544,9 +547,7 @@
*/
maybeShowDraftsBanner() {
this.showDraftsBanner = false;
- if (!(this.params?.user === 'self')) {
- return;
- }
+ if (!(this.viewState?.user === 'self')) return;
if (!this.results) {
throw new Error('this.results must be set. restAPI returned undefined');
@@ -555,16 +556,12 @@
const draftSection = this.results.find(
section => section.query === 'has:draft'
);
- if (!draftSection || !draftSection.results.length) {
- return;
- }
+ if (!draftSection || !draftSection.results.length) return;
const closedChanges = draftSection.results.filter(
change => !changeIsOpen(change)
);
- if (!closedChanges.length) {
- return;
- }
+ if (!closedChanges.length) return;
this.showDraftsBanner = true;
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
index 92afae1..7889e20 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
@@ -41,7 +41,6 @@
suite('gr-dashboard-view tests', () => {
let element: GrDashboardView;
- let paramsChangedPromise: Promise<any>;
let getChangesStub: SinonStubbedMember<
RestApiService['getChangesForMultipleQueries']
>;
@@ -54,30 +53,25 @@
registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
})
);
- stubRestApi('getAccountStatus').returns(Promise.resolve(undefined));
element = await fixture<GrDashboardView>(html`
<gr-dashboard-view></gr-dashboard-view>
`);
await element.updateComplete;
- let resolver: (value?: any) => void;
- paramsChangedPromise = new Promise(resolve => {
- resolver = resolve;
- });
- const paramsChanged = element.paramsChanged.bind(element);
- sinon
- .stub(element, 'paramsChanged')
- .callsFake(() => paramsChanged().then(() => resolver()));
});
test('render', async () => {
- const sections = [
- {name: 'test1', query: 'test1', hideIfEmpty: true},
- {name: 'test2', query: 'test2', hideIfEmpty: true},
- ];
+ element.viewState = {
+ view: GerritView.DASHBOARD,
+ user: 'self',
+ sections: [
+ {name: 'test1', query: 'test1', hideIfEmpty: true},
+ {name: 'test2', query: 'test2', hideIfEmpty: true},
+ ],
+ };
getChangesStub.returns(Promise.resolve([[createChange()]]));
- await element.fetchDashboardChanges({sections}, false);
+ await element.reload();
element.loading = false;
stubFlags('isEnabled').returns(true);
element.requestUpdate();
@@ -125,14 +119,18 @@
suite('bulk actions', () => {
setup(async () => {
- const sections = [
- {name: 'test1', query: 'test1', hideIfEmpty: true},
- {name: 'test2', query: 'test2', hideIfEmpty: true},
- ];
+ element.viewState = {
+ view: GerritView.DASHBOARD,
+ user: 'user',
+ sections: [
+ {name: 'test1', query: 'test1', hideIfEmpty: true},
+ {name: 'test2', query: 'test2', hideIfEmpty: true},
+ ],
+ };
getChangesStub.returns(Promise.resolve([[createChange()]]));
- await element.fetchDashboardChanges({sections}, false);
- element.loading = false;
stubFlags('isEnabled').returns(true);
+ await element.reload();
+ element.loading = false;
element.requestUpdate();
await element.updateComplete;
});
@@ -151,11 +149,6 @@
getChangesStub.restore();
getChangesStub.returns(Promise.resolve([[createChange()]]));
- element.params = {
- view: GerritView.DASHBOARD,
- user: 'notself',
- dashboard: '' as DashboardId,
- };
await element.reload();
await element.updateComplete;
assert.isTrue(checkbox.checked);
@@ -163,9 +156,20 @@
});
suite('drafts banner functionality', () => {
+ setup(async () => {
+ element.viewState = {
+ view: GerritView.DASHBOARD,
+ user: 'self',
+ sections: [
+ {name: 'test1', query: 'test1', hideIfEmpty: true},
+ {name: 'test2', query: 'test2', hideIfEmpty: true},
+ ],
+ };
+ });
+
suite('maybeShowDraftsBanner', () => {
test('not dashboard/self', () => {
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
user: 'notself',
dashboard: '' as DashboardId,
@@ -176,7 +180,7 @@
test('no drafts at all', () => {
element.results = [];
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
user: 'self',
dashboard: '' as DashboardId,
@@ -190,7 +194,7 @@
element.results = [
{countLabel: '', name: '', query: 'has:draft', results: [openChange]},
];
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
user: 'self',
dashboard: '' as DashboardId,
@@ -210,7 +214,7 @@
},
];
assert.isFalse(changeIsOpen(element.results[0].results[0]));
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
user: 'self',
dashboard: '' as DashboardId,
@@ -314,27 +318,10 @@
});
});
- suite('_isViewActive', () => {
- test('nothing happens when user param is falsy', async () => {
- element.params = undefined;
- await element.updateComplete;
- assert.equal(getChangesStub.callCount, 0);
- });
-
- test('content is refreshed when user param is updated', async () => {
- element.params = {
- view: GerritView.DASHBOARD,
- user: 'self',
- dashboard: '' as DashboardId,
- };
- await paramsChangedPromise;
- assert.isTrue(getChangesStub.called);
- });
- });
-
suite('selfOnly sections', () => {
test('viewing self dashboard includes selfOnly sections', async () => {
- element.params = {
+ element.account = undefined;
+ element.viewState = {
view: GerritView.DASHBOARD,
user: 'self',
dashboard: '' as DashboardId,
@@ -343,13 +330,13 @@
{name: '', query: '2', selfOnly: true},
],
};
- await paramsChangedPromise;
+ await element.reload();
assert.isTrue(getChangesStub.calledWith(undefined, ['1', '2']));
});
test('viewing dashboard when logged in includes owner:self query', async () => {
element.account = createAccountDetailWithId(1);
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
user: 'self',
dashboard: '' as DashboardId,
@@ -358,14 +345,14 @@
{name: '', query: '2', selfOnly: true},
],
};
- await paramsChangedPromise;
+ await element.reload();
assert.isTrue(
getChangesStub.calledWith(undefined, ['1', '2', 'owner:self limit:1'])
);
});
test("viewing another user's dashboard omits selfOnly sections", async () => {
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
user: 'user',
dashboard: '' as DashboardId,
@@ -374,13 +361,13 @@
{name: '', query: '2', selfOnly: true},
],
};
- await paramsChangedPromise;
+ await element.reload();
assert.isTrue(getChangesStub.calledWith(undefined, ['1']));
});
});
test('suffixForDashboard is included in getChanges query', async () => {
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
dashboard: '' as DashboardId,
sections: [
@@ -388,7 +375,7 @@
{name: '', query: '2', suffixForDashboard: 'suffix'},
],
};
- await paramsChangedPromise;
+ await element.reload();
assert.isTrue(getChangesStub.calledWith(undefined, ['1', '2 suffix']));
});
@@ -520,6 +507,9 @@
});
test('showNewUserHelp', async () => {
+ element.viewState = {
+ view: GerritView.DASHBOARD,
+ };
element.loading = false;
element.showNewUserHelp = false;
await element.updateComplete;
@@ -547,11 +537,11 @@
});
test('gr-user-header', async () => {
- element.params = undefined;
+ element.viewState = undefined;
await element.updateComplete;
assert.isNotOk(query(element, 'gr-user-header'));
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
dashboard: '' as DashboardId,
user: 'self',
@@ -560,7 +550,7 @@
assert.isNotOk(query(element, 'gr-user-header'));
element.loading = false;
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
dashboard: '' as DashboardId,
user: 'user',
@@ -568,7 +558,7 @@
await element.updateComplete;
assert.isOk(query(element, 'gr-user-header'));
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
dashboard: '' as DashboardId,
project: 'p' as RepoName,
@@ -593,16 +583,16 @@
assert.strictEqual((e as PageErrorEvent).detail.response, response);
promise.resolve();
});
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
dashboard: 'dashboard' as DashboardId,
project: 'project' as RepoName,
user: '',
};
- await Promise.all([paramsChangedPromise, promise]);
+ await Promise.all([element.reload(), promise]);
});
- test('params change triggers dashboardDisplayed()', async () => {
+ test('viewState change triggers dashboardDisplayed()', async () => {
stubRestApi('getDashboard').returns(
Promise.resolve({
id: '' as DashboardId,
@@ -618,13 +608,13 @@
);
getChangesStub.returns(Promise.resolve([]));
const dashboardDisplayedStub = stubReporting('dashboardDisplayed');
- element.params = {
+ element.viewState = {
view: GerritView.DASHBOARD,
dashboard: 'dashboard' as DashboardId,
project: 'project' as RepoName,
user: '',
};
- await paramsChangedPromise;
+ await element.reload();
assert.isTrue(dashboardDisplayedStub.calledOnce);
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts
index 7485a6d..6fde9ca 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts
@@ -54,7 +54,6 @@
import {ParsedChangeInfo} from '../../../types/types';
import {GrLinkedChip} from '../../shared/gr-linked-chip/gr-linked-chip';
import {GrButton} from '../../shared/gr-button/gr-button';
-import {GrRouter} from '../../core/gr-router/gr-router';
import {nothing} from 'lit';
import {fixture, html, assert} from '@open-wc/testing';
import {EventType} from '../../../types/events';
@@ -296,10 +295,9 @@
});
test('weblinks are visible when other weblinks', async () => {
- const router = new GrRouter();
sinon
.stub(GerritNav, '_generateWeblinks')
- .callsFake(router.generateWeblinks.bind(router));
+ .callsFake(() => [{name: 'test-name'}]);
element.commitInfo = {
...createCommitInfoWithRequiredCommit(),
@@ -309,22 +307,12 @@
const webLinks = element.webLinks!;
assert.isFalse(webLinks.hasAttribute('hidden'));
assert.equal(element.computeWebLinks().length, 1);
- // With two non-gitiles weblinks, there are two returned.
- element.commitInfo = {
- ...createCommitInfoWithRequiredCommit(),
- web_links: [
- {...createWebLinkInfo(), name: 'test', url: '#'},
- {...createWebLinkInfo(), name: 'test2', url: '#'},
- ],
- };
- assert.equal(element.computeWebLinks().length, 2);
});
test('weblinks are visible when gitiles and other weblinks', async () => {
- const router = new GrRouter();
sinon
.stub(GerritNav, '_generateWeblinks')
- .callsFake(router.generateWeblinks.bind(router));
+ .callsFake(() => [{name: 'test-name'}]);
element.commitInfo = {
...createCommitInfoWithRequiredCommit(),
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 2bd217a..d076439 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
@@ -181,7 +181,11 @@
import {getBaseUrl, prependOrigin} from '../../../utils/url-util';
import {CopyLink, GrCopyLinks} from '../gr-copy-links/gr-copy-links';
import {KnownExperimentId} from '../../../services/flags/flags';
-import {ChangeViewState, createChangeUrl} from '../../../models/views/change';
+import {
+ changeViewModelToken,
+ ChangeViewState,
+ createChangeUrl,
+} from '../../../models/views/change';
import {rootUrl} from '../../../utils/url-util';
import {createEditUrl} from '../../../models/views/edit';
@@ -275,23 +279,19 @@
@query('gr-copy-links') private copyLinksDropdown?: GrCopyLinks;
- /**
- * URL params passed from the router.
- * Use params getter/setter.
- */
- private _params?: ChangeViewState;
+ private _viewState?: ChangeViewState;
@property({type: Object})
- get params() {
- return this._params;
+ get viewState() {
+ return this._viewState;
}
- set params(params: ChangeViewState | undefined) {
- if (this._params === params) return;
- const oldParams = this._params;
- this._params = params;
- this.paramsChanged();
- this.requestUpdate('params', oldParams);
+ set viewState(viewState: ChangeViewState | undefined) {
+ if (this._viewState === viewState) return;
+ const oldViewState = this._viewState;
+ this._viewState = viewState;
+ this.viewStateChanged();
+ this.requestUpdate('viewState', oldViewState);
}
@property({type: String})
@@ -433,11 +433,11 @@
// Private but used in tests.
getEditMode() {
- if (!this.patchRange || !this.params) {
+ if (!this.patchRange || !this.viewState) {
return false;
}
- if (this.params.edit) {
+ if (this.viewState.edit) {
return true;
}
@@ -554,6 +554,8 @@
private readonly getFilesModel = resolve(this, filesModelToken);
+ private readonly getViewModel = resolve(this, changeViewModelToken);
+
private readonly getShortcutsService = resolve(this, shortcutsServiceToken);
private replyRefitTask?: DelayedTask;
@@ -693,6 +695,11 @@
private setupSubscriptions() {
subscribe(
this,
+ () => this.getViewModel().state$,
+ s => (this.viewState = s)
+ );
+ subscribe(
+ this,
() => this.getChecksModel().aPluginHasRegistered$,
b => {
this.showChecksTab = b;
@@ -2094,25 +2101,25 @@
*/
private isChangeObsolete() {
// While this.changeNum is undefined the change view is fresh and has just
- // not updated it to params.changeNum yet. Not obsolete in that case.
+ // not updated it to viewState.changeNum yet. Not obsolete in that case.
if (this.changeNum === undefined) return false;
- // this.params reflects the current state of the URL. If this.changeNum
+ // this.viewState reflects the current state of the URL. If this.changeNum
// does not match it anymore, then this view must be considered obsolete.
- return this.changeNum !== this.params?.changeNum;
+ return this.changeNum !== this.viewState?.changeNum;
}
// Private but used in tests.
- hasPatchRangeChanged(value: ChangeViewState) {
+ hasPatchRangeChanged(viewState: ChangeViewState) {
if (!this.patchRange) return false;
- if (this.patchRange.basePatchNum !== value.basePatchNum) return true;
- return this.hasPatchNumChanged(value);
+ if (this.patchRange.basePatchNum !== viewState.basePatchNum) return true;
+ return this.hasPatchNumChanged(viewState);
}
// Private but used in tests.
- hasPatchNumChanged(value: ChangeViewState) {
+ hasPatchNumChanged(viewState: ChangeViewState) {
if (!this.patchRange) return false;
- if (value.patchNum !== undefined) {
- return this.patchRange.patchNum !== value.patchNum;
+ if (viewState.patchNum !== undefined) {
+ return this.patchRange.patchNum !== viewState.patchNum;
} else {
// value.patchNum === undefined specifies the latest patchset
return (
@@ -2122,8 +2129,8 @@
}
// Private but used in tests.
- paramsChanged() {
- if (this.params?.view !== GerritView.CHANGE) {
+ viewStateChanged() {
+ if (this.viewState === undefined) {
this.initialLoadComplete = false;
querySelectorAll(this, 'gr-overlay').forEach(overlay =>
(overlay as GrOverlay).close()
@@ -2138,24 +2145,24 @@
return;
}
- if (this.params.changeNum && this.params.project) {
+ if (this.viewState.changeNum && this.viewState.project) {
this.restApiService.setInProjectLookup(
- this.params.changeNum,
- this.params.project
+ this.viewState.changeNum,
+ this.viewState.project
);
}
- if (this.params.basePatchNum === undefined)
- this.params.basePatchNum = PARENT;
+ if (this.viewState.basePatchNum === undefined)
+ this.viewState.basePatchNum = PARENT;
- const patchChanged = this.hasPatchRangeChanged(this.params);
- let patchNumChanged = this.hasPatchNumChanged(this.params);
+ const patchChanged = this.hasPatchRangeChanged(this.viewState);
+ let patchNumChanged = this.hasPatchNumChanged(this.viewState);
this.patchRange = {
- patchNum: this.params.patchNum,
- basePatchNum: this.params.basePatchNum,
+ patchNum: this.viewState.patchNum,
+ basePatchNum: this.viewState.basePatchNum,
};
- this.scrollCommentId = this.params.commentId;
+ this.scrollCommentId = this.viewState.commentId;
const patchKnown =
!this.patchRange.patchNum ||
@@ -2163,7 +2170,7 @@
ps => ps.num === this.patchRange!.patchNum
);
// _allPatchsets does not know value.patchNum so force a reload.
- const forceReload = this.params.forceReload || !patchKnown;
+ const forceReload = this.viewState.forceReload || !patchKnown;
// If changeNum is defined that means the change has already been
// rendered once before so a full reload is not required.
@@ -2176,7 +2183,7 @@
patchNumChanged = true;
}
if (patchChanged) {
- // We need to collapse all diffs when params change so that a non
+ // We need to collapse all diffs when viewState changes so that a non
// existing diff is not requested. See Issue 125270 for more details.
this.fileList?.resetFileState();
this.fileList?.collapseAllDiffs();
@@ -2198,8 +2205,8 @@
return;
}
- // We need to collapse all diffs when params change so that a non existing
- // diff is not requested. See Issue 125270 for more details.
+ // We need to collapse all diffs when viewState changes so that a non
+ // existing diff is not requested. See Issue 125270 for more details.
this.updateComplete.then(() => {
assertIsDefined(this.fileList);
this.fileList?.collapseAllDiffs();
@@ -2218,7 +2225,7 @@
}
this.initialLoadComplete = false;
- this.changeNum = this.params.changeNum;
+ this.changeNum = this.viewState.changeNum;
this.loadData(true).then(() => {
this.performPostLoadTasks();
});
@@ -2232,9 +2239,9 @@
private initActiveTab() {
let tab = Tab.FILES;
- if (this.params?.tab) {
- tab = this.params?.tab as Tab;
- } else if (this.params?.commentId) {
+ if (this.viewState?.tab) {
+ tab = this.viewState?.tab as Tab;
+ } else if (this.viewState?.commentId) {
tab = Tab.COMMENT_THREADS;
}
const detail: SwitchTabEventDetail = {
@@ -2243,8 +2250,8 @@
if (tab === Tab.CHECKS) {
const state: ChecksTabState = {};
detail.tabState = {checksTab: state};
- if (this.params?.filter) state.filter = this.params.filter;
- if (this.params?.attempt) state.attempt = this.params.attempt;
+ if (this.viewState?.filter) state.filter = this.viewState.filter;
+ if (this.viewState?.attempt) state.attempt = this.viewState.attempt;
}
this.setActiveTab(
@@ -2340,7 +2347,7 @@
private maybeShowReplyDialog() {
if (!this.loggedIn) return;
- if (this.params?.openReplyDialog) {
+ if (this.viewState?.openReplyDialog) {
this.openReplyDialog(FocusTarget.ANY);
}
}
@@ -2750,7 +2757,7 @@
private async untilModelLoaded() {
// NOTE: Wait until this page is connected before determining whether the
- // model is loaded. This can happen when params are changed when setting up
+ // model is loaded. This can happen when viewState changes when setting up
// this view. It's unclear whether this issue is related to Polymer
// specifically.
if (!this.isConnected) {
@@ -3224,6 +3231,7 @@
controls.openDeleteDialog(path);
break;
case GrEditConstants.Actions.OPEN.id:
+ assertIsDefined(this.patchRange.patchNum, 'patchset number');
GerritNav.navigateToRelativeUrl(
createEditUrl({
changeNum: this.change._number,
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index 8bb937f..0e6fb71 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -367,7 +367,7 @@
element = await fixture<GrChangeView>(
html`<gr-change-view></gr-change-view>`
);
- element.params = {
+ element.viewState = {
view: GerritView.CHANGE,
changeNum: TEST_NUMERIC_CHANGE_ID,
project: 'gerrit' as RepoName,
@@ -730,9 +730,9 @@
assert.equal(element.activeTab, Tab.FILES);
// view is required
element.changeNum = undefined;
- element.params = {
+ element.viewState = {
...createChangeViewState(),
- ...element.params,
+ ...element.viewState,
tab: Tab.COMMENT_THREADS,
};
await element.updateComplete;
@@ -742,9 +742,9 @@
test('invalid param change should not switch primary tab', async () => {
assert.equal(element.activeTab, Tab.FILES);
// view is required
- element.params = {
+ element.viewState = {
...createChangeViewState(),
- ...element.params,
+ ...element.viewState,
tab: 'random',
};
await element.updateComplete;
@@ -1054,8 +1054,8 @@
) as GrRelatedChangesList;
sinon.stub(relatedChanges, 'reload');
sinon.stub(element, 'loadData').returns(Promise.resolve());
- sinon.spy(element, 'paramsChanged');
- element.params = createChangeViewState();
+ sinon.spy(element, 'viewStateChanged');
+ element.viewState = createChangeViewState();
});
});
@@ -1549,7 +1549,7 @@
await element.updateComplete;
element.changeNum = 2 as NumericChangeId;
- element.params = {
+ element.viewState = {
...createChangeViewState(),
changeNum: 2 as NumericChangeId,
};
@@ -1574,7 +1574,7 @@
patchNum: 1 as RevisionPatchSetNum,
};
element.changeNum = undefined;
- element.params = value;
+ element.viewState = value;
await element.updateComplete;
assert.isTrue(reloadStub.calledOnce);
@@ -1590,7 +1590,7 @@
value.basePatchNum = 1 as BasePatchSetNum;
value.patchNum = 2 as RevisionPatchSetNum;
- element.params = {...value};
+ element.viewState = {...value};
await element.updateComplete;
await waitEventLoop();
assert.equal(element.fileList.selectedIndex, 0);
@@ -1619,7 +1619,7 @@
view: GerritView.CHANGE,
patchNum: 1 as RevisionPatchSetNum,
};
- element.params = value;
+ element.viewState = value;
await element.updateComplete;
element.initialLoadComplete = true;
@@ -1633,7 +1633,7 @@
value.basePatchNum = 1 as BasePatchSetNum;
value.patchNum = 2 as RevisionPatchSetNum;
- element.params = {...value};
+ element.viewState = {...value};
await element.updateComplete;
assert.isTrue(reloadPortedCommentsStub.calledOnce);
assert.isTrue(reloadPortedDraftsStub.calledOnce);
@@ -1646,13 +1646,13 @@
.callsFake(() => Promise.resolve());
const collapseStub = sinon.stub(element.fileList, 'collapseAllDiffs');
const value: ChangeViewState = createChangeViewState();
- element.params = value;
+ element.viewState = value;
// change already loaded
assert.isOk(element.changeNum);
await element.updateComplete;
assert.isFalse(reloadStub.calledOnce);
element.initialLoadComplete = true;
- element.params = {...value};
+ element.viewState = {...value};
await element.updateComplete;
assert.isFalse(reloadStub.calledTwice);
assert.isFalse(collapseStub.calledTwice);
@@ -1667,7 +1667,7 @@
.stub(element, 'loadData')
.callsFake(() => Promise.resolve());
const collapseStub = sinon.stub(element.fileList, 'collapseAllDiffs');
- element.params = {...createChangeViewState(), forceReload: true};
+ element.viewState = {...createChangeViewState(), forceReload: true};
await element.updateComplete;
assert.isTrue(getChangeStub.called);
assert.isTrue(loadDataStub.called);
@@ -1681,12 +1681,12 @@
element.addEventListener('recreate-change-view', recreateSpy);
const value: ChangeViewState = createChangeViewState();
- element.params = value;
+ element.viewState = value;
await element.updateComplete;
assert.isFalse(recreateSpy.calledOnce);
value.changeNum = 555111333 as NumericChangeId;
- element.params = {...value};
+ element.viewState = {...value};
await element.updateComplete;
assert.isTrue(recreateSpy.calledOnce);
});
@@ -1701,7 +1701,7 @@
Promise.resolve({...createMergeable(), mergeable: true})
);
- element.params = createChangeViewState();
+ element.viewState = createChangeViewState();
element.getChangeModel().setState({
loadingStatus: LoadingStatus.LOADED,
change: {
@@ -2019,8 +2019,8 @@
test('header class computation', () => {
assert.equal(element.computeHeaderClass(), 'header');
- assertIsDefined(element.params);
- element.params.edit = true;
+ assertIsDefined(element.viewState);
+ element.viewState.edit = true;
assert.equal(element.computeHeaderClass(), 'header editMode');
});
@@ -2038,8 +2038,8 @@
});
test('computeEditMode', async () => {
- const callCompute = async (params: ChangeViewState) => {
- element.params = params;
+ const callCompute = async (viewState: ChangeViewState) => {
+ element.viewState = viewState;
await element.updateComplete;
return element.getEditMode();
};
@@ -2256,30 +2256,30 @@
element.change.current_revision = '1' as CommitId;
element.change = {...element.change};
- const params = createChangeViewState();
+ const viewState = createChangeViewState();
- assert.isFalse(element.hasPatchRangeChanged(params));
- assert.isFalse(element.hasPatchNumChanged(params));
+ assert.isFalse(element.hasPatchRangeChanged(viewState));
+ assert.isFalse(element.hasPatchNumChanged(viewState));
- params.basePatchNum = PARENT;
+ viewState.basePatchNum = PARENT;
// undefined means navigate to latest patchset
- params.patchNum = undefined;
+ viewState.patchNum = undefined;
element.patchRange = {
patchNum: 2 as RevisionPatchSetNum,
basePatchNum: PARENT,
};
- assert.isTrue(element.hasPatchRangeChanged(params));
- assert.isTrue(element.hasPatchNumChanged(params));
+ assert.isTrue(element.hasPatchRangeChanged(viewState));
+ assert.isTrue(element.hasPatchNumChanged(viewState));
element.patchRange = {
patchNum: 4 as RevisionPatchSetNum,
basePatchNum: PARENT,
};
- assert.isFalse(element.hasPatchRangeChanged(params));
- assert.isFalse(element.hasPatchNumChanged(params));
+ assert.isFalse(element.hasPatchRangeChanged(viewState));
+ assert.isFalse(element.hasPatchNumChanged(viewState));
});
suite('handleEditTap', () => {
@@ -2485,7 +2485,7 @@
assert.isFalse(changeFullyLoadedStub.called);
});
- test('report changeDisplayed on paramsChanged', async () => {
+ test('report changeDisplayed on viewStateChanged', async () => {
stubRestApi('getChangeOrEditFiles').resolves({
'a-file.js': {},
});
@@ -2499,7 +2499,7 @@
);
// reset so reload is triggered
element.changeNum = undefined;
- element.params = {
+ element.viewState = {
...createChangeViewState(),
changeNum: TEST_NUMERIC_CHANGE_ID,
project: TEST_PROJECT_NAME,
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
index ea4b202..92fc2bb 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
@@ -13,7 +13,6 @@
createServerInfo,
} from '../../../test/test-data-generators';
import {CommitId, RepoName} from '../../../types/common';
-import {GrRouter} from '../../core/gr-router/gr-router';
import {fixture, html, assert} from '@open-wc/testing';
import {waitEventLoop} from '../../../test/test-utils';
@@ -61,10 +60,9 @@
});
test('use web link when available', () => {
- const router = new GrRouter();
- sinon
- .stub(GerritNav, '_generateWeblinks')
- .callsFake(router.generateWeblinks.bind(router));
+ sinon.stub(GerritNav, 'getPatchSetWeblink').callsFake(() => {
+ return {name: 'test-name', url: 'test-url'};
+ });
element.change = {...createChange(), labels: {}, project: '' as RepoName};
element.commitInfo = {
@@ -75,65 +73,6 @@
element.serverConfig = createServerInfo();
assert.isOk(element._showWebLink);
- assert.equal(element._webLink, 'link-url');
- });
-
- test('does not relativize web links that begin with scheme', () => {
- const router = new GrRouter();
- sinon
- .stub(GerritNav, '_generateWeblinks')
- .callsFake(router.generateWeblinks.bind(router));
-
- element.change = {...createChange(), labels: {}, project: '' as RepoName};
- element.commitInfo = {
- ...createCommit(),
- commit: 'commitsha' as CommitId,
- web_links: [{name: 'gitweb', url: 'https://link-url'}],
- };
- element.serverConfig = createServerInfo();
-
- assert.isOk(element._showWebLink);
- assert.equal(element._webLink, 'https://link-url');
- });
-
- test('ignore web links that are neither gitweb nor gitiles', () => {
- const router = new GrRouter();
- sinon
- .stub(GerritNav, '_generateWeblinks')
- .callsFake(router.generateWeblinks.bind(router));
-
- element.change = {...createChange(), project: 'project-name' as RepoName};
- element.commitInfo = {
- ...createCommit(),
- commit: 'commit-sha' as CommitId,
- web_links: [
- {
- name: 'ignore',
- url: 'ignore',
- },
- {
- name: 'gitiles',
- url: 'https://link-url',
- },
- ],
- };
- element.serverConfig = createServerInfo();
-
- assert.isOk(element._showWebLink);
- assert.equal(element._webLink, 'https://link-url');
-
- // Remove gitiles link.
- element.commitInfo = {
- ...createCommit(),
- commit: 'commit-sha' as CommitId,
- web_links: [
- {
- name: 'ignore',
- url: 'ignore',
- },
- ],
- };
- assert.isNotOk(element._showWebLink);
- assert.isNotOk(element._webLink);
+ assert.equal(element._webLink, 'test-url');
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index 3ddcae4..81a29e3 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -1779,12 +1779,7 @@
}
computeHasNewAttention(account?: AccountInfo) {
- return !!(
- account &&
- ((account._account_id &&
- this.newAttentionSet?.has(account._account_id)) ||
- (account.email && this.newAttentionSet?.has(account.email)))
- );
+ return !!(account && this.newAttentionSet?.has(getUserId(account)));
}
computeNewAttention() {
@@ -1813,7 +1808,7 @@
const newAttention = new Set(this.currentAttentionSet);
for (const user of this.mentionedUsersInUnresolvedDrafts) {
- newAttention.add(user.email!);
+ newAttention.add(getUserId(user));
}
if (this.change.status === ChangeStatus.NEW) {
@@ -1942,9 +1937,7 @@
}
findAccountById(userId: UserId) {
- return this.allAccounts().find(
- r => r._account_id === userId || r.email === userId
- );
+ return this.allAccounts().find(r => getUserId(r) === userId);
}
allAccounts() {
@@ -2148,7 +2141,7 @@
private alreadyExists(ccs: AccountInput[], user: AccountInfoInput) {
return ccs
.filter(cc => isAccount(cc))
- .some(cc => (cc as AccountInfoInput).email === user.email);
+ .some(cc => getUserId(cc) === getUserId(user));
}
private isAlreadyReviewerOrCC(user: AccountInfo) {
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 bac054f..8e46786 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -35,7 +35,7 @@
} from '../../../types/common';
import {AppElement, AppElementParams} from '../../gr-app-types';
import {LocationChangeEventDetail} from '../../../types/events';
-import {GerritView} from '../../../services/router/router-model';
+import {GerritView, RouterModel} from '../../../services/router/router-model';
import {firePageError} from '../../../utils/event-util';
import {windowLocationReload} from '../../../utils/dom-util';
import {
@@ -50,13 +50,46 @@
LATEST_ATTEMPT,
stringToAttemptChoice,
} from '../../../models/checks/checks-util';
-import {AdminChildView} from '../../../models/views/admin';
-import {AgreementViewState} from '../../../models/views/agreement';
-import {RepoDetailView} from '../../../models/views/repo';
-import {GroupDetailView} from '../../../models/views/group';
-import {DiffViewState} from '../../../models/views/diff';
-import {ChangeViewState} from '../../../models/views/change';
-import {EditViewState} from '../../../models/views/edit';
+import {
+ AdminChildView,
+ AdminViewModel,
+ AdminViewState,
+} from '../../../models/views/admin';
+import {
+ AgreementViewModel,
+ AgreementViewState,
+} from '../../../models/views/agreement';
+import {
+ RepoDetailView,
+ RepoViewModel,
+ RepoViewState,
+} from '../../../models/views/repo';
+import {
+ GroupDetailView,
+ GroupViewModel,
+ GroupViewState,
+} from '../../../models/views/group';
+import {DiffViewModel, DiffViewState} from '../../../models/views/diff';
+import {ChangeViewModel, ChangeViewState} from '../../../models/views/change';
+import {EditViewModel, EditViewState} from '../../../models/views/edit';
+import {
+ DashboardViewModel,
+ DashboardViewState,
+} from '../../../models/views/dashboard';
+import {
+ SettingsViewModel,
+ SettingsViewState,
+} from '../../../models/views/settings';
+import {define} from '../../../models/dependency';
+import {Finalizable} from '../../../services/registry';
+import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
+import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
+import {
+ DocumentationViewModel,
+ DocumentationViewState,
+} from '../../../models/views/documentation';
+import {PluginViewModel, PluginViewState} from '../../../models/views/plugin';
+import {SearchViewModel, SearchViewState} from '../../../models/views/search';
const RoutePattern = {
ROOT: '/',
@@ -261,7 +294,9 @@
type QueryStringItem = [string, string]; // [key, value]
-export class GrRouter {
+export const routerToken = define<GrRouter>('router');
+
+export class GrRouter implements Finalizable {
readonly _app = app;
_isRedirecting?: boolean;
@@ -270,11 +305,25 @@
// and for first navigation in app after loaded from server (true).
_isInitialLoad = true;
- private readonly reporting = getAppContext().reportingService;
+ constructor(
+ private readonly reporting: ReportingService,
+ private readonly routerModel: RouterModel,
+ private readonly restApiService: RestApiService,
+ private readonly adminViewModel: AdminViewModel,
+ private readonly agreementViewModel: AgreementViewModel,
+ private readonly changeViewModel: ChangeViewModel,
+ private readonly dashboardViewModel: DashboardViewModel,
+ private readonly diffViewModel: DiffViewModel,
+ private readonly documentationViewModel: DocumentationViewModel,
+ private readonly editViewModel: EditViewModel,
+ private readonly groupViewModel: GroupViewModel,
+ private readonly pluginViewModel: PluginViewModel,
+ private readonly repoViewModel: RepoViewModel,
+ private readonly searchViewModel: SearchViewModel,
+ private readonly settingsViewModel: SettingsViewModel
+ ) {}
- private readonly routerModel = getAppContext().routerModel;
-
- private readonly restApiService = getAppContext().restApiService;
+ finalize(): void {}
start() {
if (!this._app) {
@@ -283,15 +332,15 @@
this.startRouter();
}
- setParams(params: AppElementParams) {
+ setState(state: AppElementParams) {
this.routerModel.updateState({
- view: params.view,
- changeNum: 'changeNum' in params ? params.changeNum : undefined,
- patchNum: 'patchNum' in params ? params.patchNum ?? undefined : undefined,
+ view: state.view,
+ changeNum: 'changeNum' in state ? state.changeNum : undefined,
+ patchNum: 'patchNum' in state ? state.patchNum ?? undefined : undefined,
basePatchNum:
- 'basePatchNum' in params ? params.basePatchNum ?? undefined : undefined,
+ 'basePatchNum' in state ? state.basePatchNum ?? undefined : undefined,
});
- this.appElement().params = params;
+ this.appElement().params = state;
}
private appElement(): AppElement {
@@ -905,7 +954,7 @@
this.mapRoute(
RoutePattern.NEW_AGREEMENTS,
'handleNewAgreementsRoute',
- ctx => this.handleNewAgreementsRoute(ctx),
+ () => this.handleNewAgreementsRoute(),
true
);
@@ -1067,10 +1116,12 @@
this.redirect('/q/owner:' + encodeURIComponent(data.params[0]));
}
} else {
- this.setParams({
+ const state: DashboardViewState = {
view: GerritView.DASHBOARD,
user: data.params[0],
- });
+ };
+ this.setState(state);
+ this.dashboardViewModel.updateState(state);
}
});
}
@@ -1118,13 +1169,14 @@
});
if (sections.length > 0) {
- // Custom dashboard view.
- this.setParams({
+ const state: DashboardViewState = {
view: GerritView.DASHBOARD,
user: 'self',
sections,
title,
- });
+ };
+ this.setState(state);
+ this.dashboardViewModel.updateState(state);
return Promise.resolve();
}
@@ -1135,11 +1187,13 @@
handleProjectDashboardRoute(data: PageContextWithQueryMap) {
const project = data.params[0] as RepoName;
- this.setParams({
+ const state: DashboardViewState = {
view: GerritView.DASHBOARD,
project,
dashboard: decodeURIComponent(data.params[1]) as DashboardId,
- });
+ };
+ this.setState(state);
+ this.dashboardViewModel.updateState(state);
this.reporting.setRepoName(project);
}
@@ -1156,53 +1210,65 @@
}
handleGroupRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: GroupViewState = {
view: GerritView.GROUP,
groupId: data.params[0] as GroupId,
- });
+ };
+ this.setState(state);
+ this.groupViewModel.updateState(state);
}
handleGroupAuditLogRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: GroupViewState = {
view: GerritView.GROUP,
detail: GroupDetailView.LOG,
groupId: data.params[0] as GroupId,
- });
+ };
+ this.setState(state);
+ this.groupViewModel.updateState(state);
}
handleGroupMembersRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: GroupViewState = {
view: GerritView.GROUP,
detail: GroupDetailView.MEMBERS,
groupId: data.params[0] as GroupId,
- });
+ };
+ this.setState(state);
+ this.groupViewModel.updateState(state);
}
handleGroupListOffsetRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: AdminViewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.GROUPS,
offset: data.params[1] || 0,
filter: null,
openCreateModal: data.hash === 'create',
- });
+ };
+ this.setState(state);
+ this.adminViewModel.updateState(state);
}
handleGroupListFilterOffsetRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: AdminViewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.GROUPS,
offset: data.params['offset'],
filter: data.params['filter'],
- });
+ };
+ this.setState(state);
+ this.adminViewModel.updateState(state);
}
handleGroupListFilterRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: AdminViewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.GROUPS,
filter: data.params['filter'] || null,
- });
+ };
+ this.setState(state);
+ this.adminViewModel.updateState(state);
}
handleProjectsOldRoute(data: PageContextWithQueryMap) {
@@ -1219,127 +1285,153 @@
handleRepoCommandsRoute(data: PageContextWithQueryMap) {
const repo = data.params[0] as RepoName;
- this.setParams({
+ const state: RepoViewState = {
view: GerritView.REPO,
detail: RepoDetailView.COMMANDS,
repo,
- });
+ };
+ this.setState(state);
+ this.repoViewModel.updateState(state);
this.reporting.setRepoName(repo);
}
handleRepoGeneralRoute(data: PageContextWithQueryMap) {
const repo = data.params[0] as RepoName;
- this.setParams({
+ const state: RepoViewState = {
view: GerritView.REPO,
detail: RepoDetailView.GENERAL,
repo,
- });
+ };
+ this.setState(state);
+ this.repoViewModel.updateState(state);
this.reporting.setRepoName(repo);
}
handleRepoAccessRoute(data: PageContextWithQueryMap) {
const repo = data.params[0] as RepoName;
- this.setParams({
+ const state: RepoViewState = {
view: GerritView.REPO,
detail: RepoDetailView.ACCESS,
repo,
- });
+ };
+ this.setState(state);
+ this.repoViewModel.updateState(state);
this.reporting.setRepoName(repo);
}
handleRepoDashboardsRoute(data: PageContextWithQueryMap) {
const repo = data.params[0] as RepoName;
- this.setParams({
+ const state: RepoViewState = {
view: GerritView.REPO,
detail: RepoDetailView.DASHBOARDS,
repo,
- });
+ };
+ this.setState(state);
+ this.repoViewModel.updateState(state);
this.reporting.setRepoName(repo);
}
handleBranchListOffsetRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: RepoViewState = {
view: GerritView.REPO,
detail: RepoDetailView.BRANCHES,
repo: data.params[0] as RepoName,
offset: data.params[2] || 0,
filter: null,
- });
+ };
+ this.setState(state);
+ this.repoViewModel.updateState(state);
}
handleBranchListFilterOffsetRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: RepoViewState = {
view: GerritView.REPO,
detail: RepoDetailView.BRANCHES,
repo: data.params['repo'] as RepoName,
offset: data.params['offset'],
filter: data.params['filter'],
- });
+ };
+ this.setState(state);
+ this.repoViewModel.updateState(state);
}
handleBranchListFilterRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: RepoViewState = {
view: GerritView.REPO,
detail: RepoDetailView.BRANCHES,
repo: data.params['repo'] as RepoName,
filter: data.params['filter'] || null,
- });
+ };
+ this.setState(state);
+ this.repoViewModel.updateState(state);
}
handleTagListOffsetRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: RepoViewState = {
view: GerritView.REPO,
detail: RepoDetailView.TAGS,
repo: data.params[0] as RepoName,
offset: data.params[2] || 0,
filter: null,
- });
+ };
+ this.setState(state);
+ this.repoViewModel.updateState(state);
}
handleTagListFilterOffsetRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: RepoViewState = {
view: GerritView.REPO,
detail: RepoDetailView.TAGS,
repo: data.params['repo'] as RepoName,
offset: data.params['offset'],
filter: data.params['filter'],
- });
+ };
+ this.setState(state);
+ this.repoViewModel.updateState(state);
}
handleTagListFilterRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: RepoViewState = {
view: GerritView.REPO,
detail: RepoDetailView.TAGS,
repo: data.params['repo'] as RepoName,
filter: data.params['filter'] || null,
- });
+ };
+ this.setState(state);
+ this.repoViewModel.updateState(state);
}
handleRepoListOffsetRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: AdminViewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.REPOS,
offset: data.params[1] || 0,
filter: null,
openCreateModal: data.hash === 'create',
- });
+ };
+ this.setState(state);
+ this.adminViewModel.updateState(state);
}
handleRepoListFilterOffsetRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: AdminViewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.REPOS,
offset: data.params['offset'],
filter: data.params['filter'],
- });
+ };
+ this.setState(state);
+ this.adminViewModel.updateState(state);
}
handleRepoListFilterRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: AdminViewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.REPOS,
filter: data.params['filter'] || null,
- });
+ };
+ this.setState(state);
+ this.adminViewModel.updateState(state);
}
handleCreateProjectRoute(_: PageContextWithQueryMap) {
@@ -1359,54 +1451,66 @@
}
handlePluginListOffsetRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: AdminViewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.PLUGINS,
offset: data.params[1] || 0,
filter: null,
- });
+ };
+ this.setState(state);
+ this.adminViewModel.updateState(state);
}
handlePluginListFilterOffsetRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: AdminViewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.PLUGINS,
offset: data.params['offset'],
filter: data.params['filter'],
- });
+ };
+ this.setState(state);
+ this.adminViewModel.updateState(state);
}
handlePluginListFilterRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: AdminViewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.PLUGINS,
filter: data.params['filter'] || null,
- });
+ };
+ this.setState(state);
+ this.adminViewModel.updateState(state);
}
handlePluginListRoute(_: PageContextWithQueryMap) {
- this.setParams({
+ const state: AdminViewState = {
view: GerritView.ADMIN,
adminView: AdminChildView.PLUGINS,
- });
+ };
+ this.setState(state);
+ this.adminViewModel.updateState(state);
}
handleQueryRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: SearchViewState = {
view: GerritView.SEARCH,
query: data.params[0],
offset: data.params[2],
- });
+ };
+ this.setState(state);
+ this.searchViewModel.updateState(state);
}
handleChangeIdQueryRoute(data: PageContextWithQueryMap) {
// TODO(pcc): This will need to indicate that this was a change ID query if
// standard queries gain the ability to search places like commit messages
// for change IDs.
- this.setParams({
+ const state: SearchViewState = {
view: GerritView.SEARCH,
query: data.params[0],
- });
+ };
+ this.setState(state);
+ this.searchViewModel.updateState(state);
}
handleQueryLegacySuffixRoute(ctx: PageContextWithQueryMap) {
@@ -1420,7 +1524,7 @@
handleChangeRoute(ctx: PageContextWithQueryMap) {
// Parameter order is based on the regex group number matched.
const changeNum = Number(ctx.params[1]) as NumericChangeId;
- const params: ChangeViewState = {
+ const state: ChangeViewState = {
project: ctx.params[0] as RepoName,
changeNum,
basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
@@ -1429,7 +1533,7 @@
};
if (ctx.queryMap.has('forceReload')) {
- params.forceReload = true;
+ state.forceReload = true;
history.replaceState(
null,
'',
@@ -1438,7 +1542,7 @@
}
if (ctx.queryMap.has('openReplyDialog')) {
- params.openReplyDialog = true;
+ state.openReplyDialog = true;
history.replaceState(
null,
'',
@@ -1447,53 +1551,56 @@
}
const tab = ctx.queryMap.get('tab');
- if (tab) params.tab = tab;
+ if (tab) state.tab = tab;
const filter = ctx.queryMap.get('filter');
- if (filter) params.filter = filter;
+ if (filter) state.filter = filter;
const attempt = stringToAttemptChoice(ctx.queryMap.get('attempt'));
- if (attempt && attempt !== LATEST_ATTEMPT) params.attempt = attempt;
+ if (attempt && attempt !== LATEST_ATTEMPT) state.attempt = attempt;
- assertIsDefined(params.project, 'project');
- this.reporting.setRepoName(params.project);
+ assertIsDefined(state.project, 'project');
+ this.reporting.setRepoName(state.project);
this.reporting.setChangeId(changeNum);
- this.normalizePatchRangeParams(params);
- this.setParams(params);
+ this.normalizePatchRangeParams(state);
+ this.setState(state);
+ this.changeViewModel.updateState(state);
}
handleCommentRoute(ctx: PageContextWithQueryMap) {
const changeNum = Number(ctx.params[1]) as NumericChangeId;
- const params: DiffViewState = {
+ const state: DiffViewState = {
project: ctx.params[0] as RepoName,
changeNum,
commentId: ctx.params[2] as UrlEncodedCommentId,
view: GerritView.DIFF,
commentLink: true,
};
- this.reporting.setRepoName(params.project ?? '');
+ this.reporting.setRepoName(state.project ?? '');
this.reporting.setChangeId(changeNum);
- this.normalizePatchRangeParams(params);
- this.setParams(params);
+ this.normalizePatchRangeParams(state);
+ this.setState(state);
+ this.diffViewModel.updateState(state);
}
handleCommentsRoute(ctx: PageContextWithQueryMap) {
const changeNum = Number(ctx.params[1]) as NumericChangeId;
- const params: ChangeViewState = {
+ const state: ChangeViewState = {
project: ctx.params[0] as RepoName,
changeNum,
commentId: ctx.params[2] as UrlEncodedCommentId,
view: GerritView.CHANGE,
};
- assertIsDefined(params.project);
- this.reporting.setRepoName(params.project);
+ assertIsDefined(state.project);
+ this.reporting.setRepoName(state.project);
this.reporting.setChangeId(changeNum);
- this.normalizePatchRangeParams(params);
- this.setParams(params);
+ this.normalizePatchRangeParams(state);
+ this.setState(state);
+ this.changeViewModel.updateState(state);
}
handleDiffRoute(ctx: PageContextWithQueryMap) {
const changeNum = Number(ctx.params[1]) as NumericChangeId;
// Parameter order is based on the regex group number matched.
- const params: DiffViewState = {
+ const state: DiffViewState = {
project: ctx.params[0] as RepoName,
changeNum,
basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
@@ -1503,13 +1610,14 @@
};
const address = this.parseLineAddress(ctx.hash);
if (address) {
- params.leftSide = address.leftSide;
- params.lineNum = address.lineNum;
+ state.leftSide = address.leftSide;
+ state.lineNum = address.lineNum;
}
- this.reporting.setRepoName(params.project ?? '');
+ this.reporting.setRepoName(state.project ?? '');
this.reporting.setChangeId(changeNum);
- this.normalizePatchRangeParams(params);
- this.setParams(params);
+ this.normalizePatchRangeParams(state);
+ this.setState(state);
+ this.diffViewModel.updateState(state);
}
handleChangeLegacyRoute(ctx: PageContextWithQueryMap) {
@@ -1537,7 +1645,7 @@
// Parameter order is based on the regex group number matched.
const project = ctx.params[0] as RepoName;
const changeNum = Number(ctx.params[1]) as NumericChangeId;
- const params: EditViewState = {
+ const state: EditViewState = {
project,
changeNum,
// for edit view params, patchNum cannot be undefined
@@ -1546,8 +1654,9 @@
lineNum: Number(ctx.hash),
view: GerritView.EDIT,
};
- this.normalizePatchRangeParams(params);
- this.setParams(params);
+ this.normalizePatchRangeParams(state);
+ this.setState(state);
+ this.editViewModel.updateState(state);
this.reporting.setRepoName(project);
this.reporting.setChangeId(changeNum);
}
@@ -1556,7 +1665,7 @@
// Parameter order is based on the regex group number matched.
const project = ctx.params[0] as RepoName;
const changeNum = Number(ctx.params[1]) as NumericChangeId;
- const params: ChangeViewState = {
+ const state: ChangeViewState = {
project,
changeNum,
patchNum: convertToPatchSetNum(ctx.params[3]) as RevisionPatchSetNum,
@@ -1565,16 +1674,16 @@
tab: ctx.queryMap.get('tab') ?? '',
};
if (ctx.queryMap.has('forceReload')) {
- params.forceReload = true;
+ state.forceReload = true;
history.replaceState(
null,
'',
location.href.replace(/[?&]forceReload=true/, '')
);
}
- this.normalizePatchRangeParams(params);
- this.setParams(params);
-
+ this.normalizePatchRangeParams(state);
+ this.setState(state);
+ this.changeViewModel.updateState(state);
this.reporting.setRepoName(project);
this.reporting.setChangeId(changeNum);
}
@@ -1583,10 +1692,12 @@
this.redirect('/settings/#Agreements');
}
- handleNewAgreementsRoute(data: PageContextWithQueryMap) {
- data.params['view'] = GerritView.AGREEMENTS;
- // TODO(TS): create valid object
- this.setParams(data.params as unknown as AgreementViewState);
+ handleNewAgreementsRoute() {
+ const state: AgreementViewState = {
+ view: GerritView.AGREEMENTS,
+ };
+ this.setState(state);
+ this.agreementViewModel.updateState(state);
}
handleSettingsLegacyRoute(data: PageContextWithQueryMap) {
@@ -1594,18 +1705,22 @@
// The parameter parsing replaces all '+' with a space,
// undo that to have valid tokens.
const token = data.params[0].replace(/ /g, '+');
- this.setParams({
+ const state: SettingsViewState = {
view: GerritView.SETTINGS,
emailToken: token,
- });
+ };
+ this.setState(state);
+ this.settingsViewModel.updateState(state);
}
handleSettingsRoute(_: PageContextWithQueryMap) {
- this.setParams({view: GerritView.SETTINGS});
+ const state: SettingsViewState = {view: GerritView.SETTINGS};
+ this.setState(state);
+ this.settingsViewModel.updateState(state);
}
handleRegisterRoute(ctx: PageContextWithQueryMap) {
- this.setParams({justRegistered: true});
+ this.setState({justRegistered: true});
let path = ctx.params[0] || '/';
// Prevent redirect looping.
@@ -1640,17 +1755,22 @@
}
handlePluginScreen(ctx: PageContextWithQueryMap) {
- const view = GerritView.PLUGIN_SCREEN;
- const plugin = ctx.params[0];
- const screen = ctx.params[1];
- this.setParams({view, plugin, screen});
+ const state: PluginViewState = {
+ view: GerritView.PLUGIN_SCREEN,
+ plugin: ctx.params[0],
+ screen: ctx.params[1],
+ };
+ this.setState(state);
+ this.pluginViewModel.updateState(state);
}
handleDocumentationSearchRoute(data: PageContextWithQueryMap) {
- this.setParams({
+ const state: DocumentationViewState = {
view: GerritView.DOCUMENTATION_SEARCH,
filter: data.params['filter'] || null,
- });
+ };
+ this.setState(state);
+ this.documentationViewModel.updateState(state);
}
handleDocumentationSearchRedirectRoute(data: PageContextWithQueryMap) {
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
index da57994..42659ca 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
@@ -16,6 +16,7 @@
import {
GrRouter,
PageContextWithQueryMap,
+ routerToken,
_testOnly_RoutePattern,
} from './gr-router';
import {GerritView} from '../../../services/router/router-model';
@@ -42,12 +43,15 @@
import {EditViewState} from '../../../models/views/edit';
import {ChangeViewState} from '../../../models/views/change';
import {PatchRangeParams} from '../../../utils/url-util';
+import {DependencyRequestEvent} from '../../../models/dependency';
suite('gr-router tests', () => {
let router: GrRouter;
setup(() => {
- router = new GrRouter();
+ document.dispatchEvent(
+ new DependencyRequestEvent(routerToken, x => (router = x))
+ );
});
test('firstCodeBrowserWeblink', () => {
@@ -343,7 +347,7 @@
suite('route handlers', () => {
let redirectStub: sinon.SinonStub;
- let setParamsStub: sinon.SinonStub;
+ let setStateStub: sinon.SinonStub;
let handlePassThroughRoute: sinon.SinonStub;
// Simple route handlers are direct mappings from parsed route data to a
@@ -355,7 +359,7 @@
params: AppElementParams
) {
(router as any)[methodName](data);
- assert.deepEqual(setParamsStub.lastCall.args[0], params);
+ assert.deepEqual(setStateStub.lastCall.args[0], params);
}
function createPageContext(): PageContextWithQueryMap {
@@ -376,7 +380,7 @@
setup(() => {
redirectStub = sinon.stub(router, 'redirect');
- setParamsStub = sinon.stub(router, 'setParams');
+ setStateStub = sinon.stub(router, 'setState');
handlePassThroughRoute = sinon.stub(router, 'handlePassThroughRoute');
});
@@ -400,10 +404,9 @@
});
test('handleNewAgreementsRoute', () => {
- const params = createPageContext();
- router.handleNewAgreementsRoute(params);
- assert.isTrue(setParamsStub.calledOnce);
- assert.equal(setParamsStub.lastCall.args[0].view, GerritView.AGREEMENTS);
+ router.handleNewAgreementsRoute();
+ assert.isTrue(setStateStub.calledOnce);
+ assert.equal(setStateStub.lastCall.args[0].view, GerritView.AGREEMENTS);
});
test('handleSettingsLegacyRoute', () => {
@@ -518,24 +521,24 @@
const ctx = {...createPageContext(), params: {0: '/foo/bar'}};
router.handleRegisterRoute(ctx);
assert.isTrue(redirectStub.calledWithExactly('/foo/bar'));
- assert.isTrue(setParamsStub.calledOnce);
- assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
+ assert.isTrue(setStateStub.calledOnce);
+ assert.isTrue(setStateStub.lastCall.args[0].justRegistered);
});
test('no param', () => {
const ctx = createPageContext();
router.handleRegisterRoute(ctx);
assert.isTrue(redirectStub.calledWithExactly('/'));
- assert.isTrue(setParamsStub.calledOnce);
- assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
+ assert.isTrue(setStateStub.calledOnce);
+ assert.isTrue(setStateStub.lastCall.args[0].justRegistered);
});
test('prevent redirect', () => {
const ctx = {...createPageContext(), params: {0: '/register'}};
router.handleRegisterRoute(ctx);
assert.isTrue(redirectStub.calledWithExactly('/'));
- assert.isTrue(setParamsStub.calledOnce);
- assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
+ assert.isTrue(setStateStub.calledOnce);
+ assert.isTrue(setStateStub.lastCall.args[0].justRegistered);
});
});
@@ -663,7 +666,7 @@
return router.handleDashboardRoute(data).then(() => {
assert.isTrue(redirectToLoginStub.calledOnce);
assert.isFalse(redirectStub.called);
- assert.isFalse(setParamsStub.called);
+ assert.isFalse(setStateStub.called);
});
});
@@ -676,7 +679,7 @@
};
return router.handleDashboardRoute(data).then(() => {
assert.isFalse(redirectToLoginStub.called);
- assert.isFalse(setParamsStub.called);
+ assert.isFalse(setStateStub.called);
assert.isTrue(redirectStub.calledOnce);
assert.equal(redirectStub.lastCall.args[0], '/q/owner:foo');
});
@@ -691,8 +694,8 @@
return router.handleDashboardRoute(data).then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(redirectStub.called);
- assert.isTrue(setParamsStub.calledOnce);
- assert.deepEqual(setParamsStub.lastCall.args[0], {
+ assert.isTrue(setStateStub.calledOnce);
+ assert.deepEqual(setStateStub.lastCall.args[0], {
view: GerritView.DASHBOARD,
user: 'foo',
});
@@ -714,7 +717,7 @@
params: {0: ''},
};
return router.handleCustomDashboardRoute(data, '').then(() => {
- assert.isFalse(setParamsStub.called);
+ assert.isFalse(setStateStub.called);
assert.isTrue(redirectStub.called);
assert.equal(redirectStub.lastCall.args[0], '/dashboard/self');
});
@@ -730,8 +733,8 @@
.handleCustomDashboardRoute(data, '?a=b&c&d=e')
.then(() => {
assert.isFalse(redirectStub.called);
- assert.isTrue(setParamsStub.calledOnce);
- assert.deepEqual(setParamsStub.lastCall.args[0], {
+ assert.isTrue(setStateStub.calledOnce);
+ assert.deepEqual(setStateStub.lastCall.args[0], {
view: GerritView.DASHBOARD,
user: 'self',
sections: [
@@ -754,8 +757,8 @@
.then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(redirectStub.called);
- assert.isTrue(setParamsStub.calledOnce);
- assert.deepEqual(setParamsStub.lastCall.args[0], {
+ assert.isTrue(setStateStub.calledOnce);
+ assert.deepEqual(setStateStub.lastCall.args[0], {
view: GerritView.DASHBOARD,
user: 'self',
sections: [{name: 'a', query: 'b'}],
@@ -775,8 +778,8 @@
.then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(redirectStub.called);
- assert.isTrue(setParamsStub.calledOnce);
- assert.deepEqual(setParamsStub.lastCall.args[0], {
+ assert.isTrue(setStateStub.calledOnce);
+ assert.deepEqual(setStateStub.lastCall.args[0], {
view: GerritView.DASHBOARD,
user: 'self',
sections: [{name: 'a', query: 'is:open b'}],
@@ -1353,7 +1356,7 @@
router.handleDiffEditRoute(ctx);
assert.isFalse(redirectStub.called);
- assert.deepEqual(setParamsStub.lastCall.args[0], appParams);
+ assert.deepEqual(setStateStub.lastCall.args[0], appParams);
});
test('handleDiffEditRoute with lineNum', () => {
@@ -1379,7 +1382,7 @@
router.handleDiffEditRoute(ctx);
assert.isFalse(redirectStub.called);
- assert.deepEqual(setParamsStub.lastCall.args[0], appParams);
+ assert.deepEqual(setStateStub.lastCall.args[0], appParams);
});
test('handleChangeEditRoute', () => {
@@ -1404,7 +1407,7 @@
router.handleChangeEditRoute(ctx);
assert.isFalse(redirectStub.called);
- assert.deepEqual(setParamsStub.lastCall.args[0], appParams);
+ assert.deepEqual(setStateStub.lastCall.args[0], appParams);
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 2c75741c..464102a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -82,7 +82,6 @@
getPatchRangeForCommentUrl,
isInBaseOfPatchRange,
} from '../../../utils/comment-util';
-import {AppElementParams} from '../../gr-app-types';
import {
EventType,
OpenFixPreviewEvent,
@@ -118,7 +117,11 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {ifDefined} from 'lit/directives/if-defined.js';
import {when} from 'lit/directives/when.js';
-import {createDiffUrl, DiffViewState} from '../../../models/views/diff';
+import {
+ createDiffUrl,
+ diffViewModelToken,
+ DiffViewState,
+} from '../../../models/views/diff';
import {createChangeUrl} from '../../../models/views/change';
import {createEditUrl} from '../../../models/views/edit';
@@ -172,23 +175,19 @@
@query('#diffPreferencesDialog')
diffPreferencesDialog?: GrOverlay;
- /**
- * URL params passed from the router.
- * Use params getter/setter.
- */
- private _params?: AppElementParams;
+ private _viewState: DiffViewState | undefined;
- @property({type: Object})
- get params() {
- return this._params;
+ @state()
+ get viewState(): DiffViewState | undefined {
+ return this._viewState;
}
- set params(params: AppElementParams | undefined) {
- if (this._params === params) return;
- const oldParams = this._params;
- this._params = params;
- this.paramsChanged();
- this.requestUpdate('params', oldParams);
+ set viewState(viewState: DiffViewState | undefined) {
+ if (this._viewState === viewState) return;
+ const oldViewState = this._viewState;
+ this._viewState = viewState;
+ this.viewStateChanged();
+ this.requestUpdate('viewState', oldViewState);
}
// Private but used in tests.
@@ -310,6 +309,8 @@
private readonly getConfigModel = resolve(this, configModelToken);
+ private readonly getViewModel = resolve(this, diffViewModelToken);
+
private throttledToggleFileReviewed?: (e: KeyboardEvent) => void;
@state()
@@ -323,6 +324,11 @@
super();
this.setupKeyboardShortcuts();
this.setupSubscriptions();
+ subscribe(
+ this,
+ () => this.getViewModel().state$,
+ x => (this.viewState = x)
+ );
}
private setupKeyboardShortcuts() {
@@ -1525,10 +1531,10 @@
initPatchRange() {
let leftSide = false;
if (!this.change) return;
- if (this.params?.view !== GerritView.DIFF) return;
- if (this.params?.commentId) {
+ if (this.viewState?.view !== GerritView.DIFF) return;
+ if (this.viewState?.commentId) {
const comment = this.changeComments?.findCommentById(
- this.params.commentId
+ this.viewState.commentId
);
if (!comment) {
fireAlert(this, 'comment not found');
@@ -1544,24 +1550,24 @@
this.focusLineNum = comment.line;
} else {
- if (this.params.path) {
- this.getChangeModel().updatePath(this.params.path);
+ if (this.viewState.path) {
+ this.getChangeModel().updatePath(this.viewState.path);
}
- if (this.params.patchNum) {
+ if (this.viewState.patchNum) {
this.patchRange = {
- patchNum: this.params.patchNum,
- basePatchNum: this.params.basePatchNum || PARENT,
+ patchNum: this.viewState.patchNum,
+ basePatchNum: this.viewState.basePatchNum || PARENT,
};
}
- if (this.params.lineNum) {
- this.focusLineNum = this.params.lineNum;
- leftSide = !!this.params.leftSide;
+ if (this.viewState.lineNum) {
+ this.focusLineNum = this.viewState.lineNum;
+ leftSide = !!this.viewState.leftSide;
}
}
assertIsDefined(this.patchRange, 'patchRange');
this.initLineOfInterestAndCursor(leftSide);
- if (this.params?.commentId) {
+ if (this.viewState?.commentId) {
// url is of type /comment/{commentId} which isn't meaningful
this.updateUrlToDiffUrl(this.focusLineNum, leftSide);
}
@@ -1601,12 +1607,9 @@
}
// Private but used in tests.
- paramsChanged() {
- if (this.params?.view !== GerritView.DIFF) {
- return;
- }
-
- const params = this.params;
+ viewStateChanged() {
+ if (this.viewState === undefined) return;
+ const viewState = this.viewState;
// The diff view is kept in the background once created. If the user
// scrolls in the change page, the scrolling is reflected in the diff view
@@ -1617,11 +1620,14 @@
// Everything in the diff view is tied to the change. It seems better to
// force the re-creation of the diff view when the change number changes.
- const changeChanged = this.changeNum !== params.changeNum;
+ const changeChanged = this.changeNum !== viewState.changeNum;
if (this.changeNum !== undefined && changeChanged) {
fireEvent(this, EventType.RECREATE_DIFF_VIEW);
return;
- } else if (this.changeNum !== undefined && this.isSameDiffLoaded(params)) {
+ } else if (
+ this.changeNum !== undefined &&
+ this.isSameDiffLoaded(viewState)
+ ) {
// changeNum has not changed, so check if there are changes in patchRange
// path. If no changes then we can simply render the view as is.
this.reporting.reportInteraction('diff-view-re-rendered');
@@ -1641,20 +1647,23 @@
this.commitRange = undefined;
this.focusLineNum = undefined;
- if (params.changeNum && params.project) {
- this.restApiService.setInProjectLookup(params.changeNum, params.project);
+ if (viewState.changeNum && viewState.project) {
+ this.restApiService.setInProjectLookup(
+ viewState.changeNum,
+ viewState.project
+ );
}
- this.changeNum = params.changeNum;
+ this.changeNum = viewState.changeNum;
this.classList.remove('hideComments');
// When navigating away from the page, there is a possibility that the
// patch number is no longer a part of the URL (say when navigating to
// the top-level change info view) and therefore undefined in `params`.
// If route is of type /comment/<commentId>/ then no patchNum is present
- if (!params.patchNum && !params.commentLink) {
+ if (!viewState.patchNum && !viewState.commentLink) {
this.reporting.error(
- new Error(`Invalid diff view URL, no patchNum found: ${this.params}`)
+ new Error(`Invalid diff view URL, no patchNum found: ${this.viewState}`)
);
return;
}
@@ -1682,7 +1691,7 @@
})
.then(() => {
const fileUnchanged = this.isFileUnchanged(this.diff);
- if (fileUnchanged && params.commentLink) {
+ if (fileUnchanged && viewState.commentLink) {
assertIsDefined(this.change, 'change');
assertIsDefined(this.path, 'path');
assertIsDefined(this.patchRange, 'patchRange');
@@ -1709,7 +1718,7 @@
);
return;
}
- if (params.commentLink) {
+ if (viewState.commentLink) {
this.displayToasts();
}
// If the blame was loaded for a previous file and user navigates to
@@ -2115,7 +2124,7 @@
this.path,
this.patchRange.basePatchNum as RevisionPatchSetNum,
PARENT,
- this.params?.view === GerritView.DIFF && this.params?.commentLink
+ this.viewState?.view === GerritView.DIFF && this.viewState?.commentLink
? this.focusLineNum
: undefined
);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
index 9a6089f..46e04db 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
@@ -43,7 +43,6 @@
BasePatchSetNum,
CommentInfo,
CommitId,
- DashboardId,
EDIT,
FileInfo,
NumericChangeId,
@@ -155,14 +154,14 @@
sinon.restore();
});
- test('params change triggers diffViewDisplayed()', () => {
+ test('viewState change triggers diffViewDisplayed()', () => {
const diffViewDisplayedStub = stubReporting('diffViewDisplayed');
assertIsDefined(element.diffHost);
sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
sinon.stub(element, 'initPatchRange');
sinon.stub(element, 'fetchFiles');
- const paramsChangedSpy = sinon.spy(element, 'paramsChanged');
- element.params = {
+ const viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
+ element.viewState = {
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
patchNum: 2 as RevisionPatchSetNum,
@@ -171,7 +170,7 @@
};
element.path = '/COMMIT_MSG';
element.patchRange = createPatchRange();
- return paramsChangedSpy.returnValues[0]?.then(() => {
+ return viewStateChangedSpy.returnValues[0]?.then(() => {
assert.isTrue(diffViewDisplayedStub.calledOnce);
});
});
@@ -179,7 +178,7 @@
suite('comment route', () => {
let initLineOfInterestAndCursorStub: SinonStub;
let replaceStateStub: SinonStub;
- let paramsChangedSpy: SinonSpy;
+ let viewStateChangedSpy: SinonSpy;
setup(() => {
initLineOfInterestAndCursorStub = sinon.stub(
element,
@@ -190,7 +189,7 @@
stubReporting('diffViewDisplayed');
assertIsDefined(element.diffHost);
sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
- paramsChangedSpy = sinon.spy(element, 'paramsChanged');
+ viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
element.getChangeModel().setState({
change: {
...createParsedChange(),
@@ -214,7 +213,7 @@
portedDrafts: {},
discardedDrafts: [],
});
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
commentLink: true,
@@ -226,7 +225,7 @@
...createParsedChange(),
revisions: createRevisions(11),
};
- return paramsChangedSpy.returnValues[0].then(() => {
+ return viewStateChangedSpy.returnValues[0].then(() => {
assert.isTrue(
initLineOfInterestAndCursorStub.calledWithExactly(true)
);
@@ -238,17 +237,17 @@
});
});
- test('params change causes blame to load if it was set to true', () => {
+ test('viewState change causes blame to load if it was set to true', () => {
// Blame loads for subsequent files if it was loaded for one file
element.isBlameLoaded = true;
stubReporting('diffViewDisplayed');
const loadBlameStub = sinon.stub(element, 'loadBlame');
assertIsDefined(element.diffHost);
sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
- const paramsChangedSpy = sinon.spy(element, 'paramsChanged');
+ const viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
sinon.stub(element, 'initPatchRange');
sinon.stub(element, 'fetchFiles');
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
patchNum: 2 as RevisionPatchSetNum,
@@ -257,7 +256,7 @@
};
element.path = '/COMMIT_MSG';
element.patchRange = createPatchRange();
- return paramsChangedSpy.returnValues[0]!.then(() => {
+ return viewStateChangedSpy.returnValues[0]!.then(() => {
assert.isTrue(element.isBlameLoaded);
assert.isTrue(loadBlameStub.calledOnce);
});
@@ -283,7 +282,7 @@
assertIsDefined(element.diffHost);
sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
sinon.stub(element, 'isFileUnchanged').returns(true);
- const paramsChangedSpy = sinon.spy(element, 'paramsChanged');
+ const viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
element.getChangeModel().setState({
change: {
...createParsedChange(),
@@ -291,7 +290,7 @@
},
loadingStatus: LoadingStatus.LOADED,
});
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
path: '/COMMIT_MSG',
@@ -302,7 +301,7 @@
...createParsedChange(),
revisions: createRevisions(11),
};
- return paramsChangedSpy.returnValues[0]?.then(() => {
+ return viewStateChangedSpy.returnValues[0]?.then(() => {
assert.isTrue(
diffNavStub.lastCall.calledWithExactly(
element.change!,
@@ -335,7 +334,7 @@
assertIsDefined(element.diffHost);
sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
sinon.stub(element, 'isFileUnchanged').returns(true);
- const paramsChangedSpy = sinon.spy(element, 'paramsChanged');
+ const viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
element.getChangeModel().setState({
change: {
...createParsedChange(),
@@ -343,7 +342,7 @@
},
loadingStatus: LoadingStatus.LOADED,
});
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
path: '/COMMIT_MSG',
@@ -354,7 +353,7 @@
...createParsedChange(),
revisions: createRevisions(11),
};
- return paramsChangedSpy.returnValues[0]!.then(() => {
+ return viewStateChangedSpy.returnValues[0]!.then(() => {
assert.isFalse(diffNavStub.called);
});
});
@@ -410,7 +409,7 @@
sinon.stub(element, 'loadBlame');
assertIsDefined(element.diffHost);
sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
- const paramsChangedSpy = sinon.spy(element, 'paramsChanged');
+ const viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
element.change = undefined;
element.getChangeModel().setState({
change: {
@@ -425,14 +424,14 @@
};
sinon.stub(element, 'isFileUnchanged').returns(false);
const toastStub = sinon.stub(element, 'displayDiffBaseAgainstLeftToast');
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
project: 'p' as RepoName,
commentId: 'c1' as UrlEncodedCommentId,
commentLink: true,
};
- await paramsChangedSpy.returnValues[0];
+ await viewStateChangedSpy.returnValues[0];
assert.isTrue(toastStub.called);
});
@@ -887,9 +886,12 @@
patchNum: 3 as RevisionPatchSetNum,
basePatchNum: 1 as BasePatchSetNum,
};
- element.params = {
- view: GerritView.DASHBOARD,
- dashboard: 'id' as DashboardId,
+ element.viewState = {
+ view: GerritView.DIFF,
+ changeNum: 42 as NumericChangeId,
+ patchNum: 3 as RevisionPatchSetNum,
+ basePatchNum: 1 as BasePatchSetNum,
+ path: 'foo',
};
await element.updateComplete;
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
@@ -910,8 +912,8 @@
patchNum: 3 as RevisionPatchSetNum,
basePatchNum: 1 as BasePatchSetNum,
};
- sinon.stub(element, 'paramsChanged');
- element.params = {
+ sinon.stub(element, 'viewStateChanged');
+ element.viewState = {
commentLink: true,
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
@@ -1411,7 +1413,7 @@
assert.isTrue(overlayOpenStub.called);
});
- suite('url params', () => {
+ suite('url parameters', () => {
setup(() => {
sinon.stub(element, 'fetchFiles');
});
@@ -1804,14 +1806,14 @@
const callCount = saveReviewedStub.callCount;
- element.params = {
- view: GerritView.CHANGE,
+ element.viewState = {
+ view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
project: 'test' as RepoName,
};
await element.updateComplete;
- // saveReviewedState observer observes params, but should not fire when
+ // saveReviewedState observer observes viewState, but should not fire when
// view !== GerritView.DIFF.
assert.equal(saveReviewedStub.callCount, callCount);
});
@@ -1833,13 +1835,13 @@
assert.isFalse(saveReviewedStub.called);
});
- test('hash is determined from params', async () => {
+ test('hash is determined from viewState', async () => {
assertIsDefined(element.diffHost);
sinon.stub(element.diffHost, 'reload');
const initLineStub = sinon.stub(element, 'initLineOfInterestAndCursor');
element.loggedIn = true;
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
patchNum: 2 as RevisionPatchSetNum,
@@ -1927,7 +1929,7 @@
});
test('uses the patchNum and basePatchNum ', async () => {
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
patchNum: 4 as RevisionPatchSetNum,
@@ -1944,7 +1946,7 @@
});
test('uses the parent when there is no base patch num ', async () => {
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
patchNum: 5 as RevisionPatchSetNum,
@@ -1964,11 +1966,11 @@
assertIsDefined(element.cursor);
assert.isNotOk(element.cursor.initialLineNumber);
- // Does nothing when params specify no cursor address:
+ // Does nothing when viewState specify no cursor address:
element.initCursor(false);
assert.isNotOk(element.cursor.initialLineNumber);
- // Does nothing when params specify side but no number:
+ // Does nothing when viewState specify side but no number:
element.initCursor(true);
assert.isNotOk(element.cursor.initialLineNumber);
@@ -2081,7 +2083,7 @@
suite('initPatchRange', () => {
setup(async () => {
getDiffRestApiStub.returns(Promise.resolve(createDiff()));
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
patchNum: 3 as RevisionPatchSetNum,
@@ -2496,7 +2498,7 @@
const navigateToDiffStub = sinon.stub(GerritNav, 'navigateToDiff');
// Load file1
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
patchNum: 1 as RevisionPatchSetNum,
changeNum: 101 as NumericChangeId,
@@ -2521,7 +2523,7 @@
assert.isTrue(navigateToDiffStub.calledOnce);
// This is to mock the param change triggered by above navigate
- element.params = {
+ element.viewState = {
view: GerritView.DIFF,
patchNum: 1 as RevisionPatchSetNum,
changeNum: 101 as NumericChangeId,
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
index d2717fc..6dbdca6 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
@@ -10,28 +10,39 @@
import {getAppContext} from '../../../services/app-context';
import {sharedStyles} from '../../../styles/shared-styles';
import {tableStyles} from '../../../styles/gr-table-styles';
-import {LitElement, PropertyValues, html} from 'lit';
-import {customElement, property, state} from 'lit/decorators.js';
-import {DocumentationViewState} from '../../../models/views/documentation';
+import {LitElement, html} from 'lit';
+import {customElement, state} from 'lit/decorators.js';
+import {resolve} from '../../../models/dependency';
+import {subscribe} from '../../lit/subscription-controller';
+import {documentationViewModelToken} from '../../../models/views/documentation';
@customElement('gr-documentation-search')
export class GrDocumentationSearch extends LitElement {
- /**
- * URL params passed from the router.
- */
- @property({type: Object})
- params?: DocumentationViewState;
-
// private but used in test
@state() documentationSearches?: DocResult[];
// private but used in test
@state() loading = true;
- @state() private filter = '';
+ // private but used in test
+ @state() filter = '';
private readonly restApiService = getAppContext().restApiService;
+ private readonly getViewModel = resolve(this, documentationViewModelToken);
+
+ constructor() {
+ super();
+ subscribe(
+ this,
+ () => this.getViewModel().state$,
+ x => {
+ this.filter = x?.filter ?? '';
+ if (x !== undefined) this.getDocumentationSearches();
+ }
+ );
+ }
+
override connectedCallback() {
super.connectedCallback();
fireTitleChange(this, 'Documentation Search');
@@ -80,21 +91,9 @@
`;
}
- override willUpdate(changedProperties: PropertyValues) {
- if (changedProperties.has('params')) {
- this.paramsChanged();
- }
- }
-
- // private but used in test
- paramsChanged() {
+ getDocumentationSearches() {
+ const filter = this.filter;
this.loading = true;
- this.filter = this.params?.filter ?? '';
-
- return this.getDocumentationSearches(this.filter);
- }
-
- private getDocumentationSearches(filter: string) {
this.documentationSearches = [];
return this.restApiService
.getDocumentationSearches(filter)
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
index 483db10..0092193 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
@@ -10,7 +10,6 @@
import {queryAndAssert, stubRestApi} from '../../../test/test-utils';
import {DocResult} from '../../../types/common';
import {fixture, html, assert} from '@open-wc/testing';
-import {GerritView} from '../../../services/router/router-model';
function documentationGenerator(counter: number) {
return {
@@ -44,7 +43,7 @@
stubRestApi('getDocumentationSearches').returns(
Promise.resolve(documentationSearches)
);
- await element.paramsChanged();
+ await element.getDocumentationSearches();
await element.updateComplete;
});
@@ -327,8 +326,8 @@
const stub = stubRestApi('getDocumentationSearches').returns(
Promise.resolve(documentationSearches)
);
- element.params = {view: GerritView.DOCUMENTATION_SEARCH, filter: 'test'};
- await element.paramsChanged();
+ element.filter = 'test';
+ await element.getDocumentationSearches();
assert.isTrue(stub.lastCall.calledWithExactly('test'));
});
});
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts
index 83bb4cf..6ef5406 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts
@@ -423,6 +423,7 @@
this.closeDialog(this.openDialog);
return;
}
+ assertIsDefined(this.patchNum, 'patchset number');
const url = createEditUrl({
changeNum: this.change._number,
project: this.change.project,
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index c0b3760..4cbde8f 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -11,11 +11,8 @@
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {computeTruncatedPath} from '../../../utils/path-list-util';
import {
- PatchSetNum,
EditPreferencesInfo,
Base64FileContent,
- NumericChangeId,
- EDIT,
PatchSetNumber,
} from '../../../types/common';
import {ParsedChangeInfo} from '../../../types/types';
@@ -31,11 +28,10 @@
import {LitElement, PropertyValues, html, css, nothing} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {subscribe} from '../../lit/subscription-controller';
-import {GerritView} from '../../../services/router/router-model';
import {resolve} from '../../../models/dependency';
import {changeModelToken} from '../../../models/change/change-model';
import {ShortcutController} from '../../lit/shortcut-controller';
-import {EditViewState} from '../../../models/views/edit';
+import {editViewModelToken, EditViewState} from '../../../models/views/edit';
const RESTORED_MESSAGE = 'Content restored from a previous edit.';
const SAVING_MESSAGE = 'Saving changes...';
@@ -61,21 +57,12 @@
*/
@property({type: Object})
- params?: EditViewState;
+ viewState?: EditViewState;
// private but used in test
@state() change?: ParsedChangeInfo;
// private but used in test
- @state() changeNum?: NumericChangeId;
-
- // private but used in test
- @state() patchNum?: PatchSetNum;
-
- // private but used in test
- @state() path?: string;
-
- // private but used in test
@state() type?: string;
// private but used in test
@@ -92,9 +79,8 @@
@state() private editPrefs?: EditPreferencesInfo;
- @state() private lineNum?: number;
-
- @state() private latestPatchsetNumber?: PatchSetNumber;
+ // private but used in test
+ @state() latestPatchsetNumber?: PatchSetNumber;
private readonly restApiService = getAppContext().restApiService;
@@ -106,6 +92,8 @@
private readonly getChangeModel = resolve(this, changeModelToken);
+ private readonly getEditViewModel = resolve(this, editViewModelToken);
+
private readonly shortcuts = new ShortcutController(this);
// Tests use this so needs to be non private
@@ -119,8 +107,14 @@
subscribe(
this,
() => this.userModel.editPreferences$,
- editPreferences => {
- this.editPrefs = editPreferences;
+ editPreferences => (this.editPrefs = editPreferences)
+ );
+ subscribe(
+ this,
+ () => this.getEditViewModel().state$,
+ state => {
+ this.viewState = state;
+ this.viewStateChanged();
}
);
subscribe(
@@ -207,6 +201,7 @@
}
override render() {
+ if (!this.viewState) return;
return html` ${this.renderHeader()} ${this.renderEndpoint()} `;
}
@@ -220,7 +215,7 @@
<span class="separator"></span>
<gr-editable-label
labelText="File path"
- .value=${this.path}
+ .value=${this.viewState?.path}
placeholder="File path..."
@changed=${this.handlePathChanged}
></gr-editable-label>
@@ -254,7 +249,7 @@
}
private renderEditingOldPatchsetWarning() {
- const patchset = this.params?.patchNum;
+ const patchset = this.viewState?.patchNum;
if (patchset === this.latestPatchsetNumber) return nothing;
return html`<span class="warning"> (Old Patchset)</span>`;
}
@@ -277,7 +272,7 @@
></gr-endpoint-param>
<gr-endpoint-param
name="lineNum"
- .value=${this.lineNum}
+ .value=${this.viewState?.lineNum}
></gr-endpoint-param>
<gr-default-editor
id="file"
@@ -289,58 +284,40 @@
}
override willUpdate(changedProperties: PropertyValues) {
- if (changedProperties.has('params')) {
- this.paramsChanged();
- }
-
if (changedProperties.has('change')) {
this.navigateToChangeIfEdit();
}
-
if (changedProperties.has('change') || changedProperties.has('type')) {
this.navigateToChangeIfEditType();
}
}
get storageKey() {
- return `c${this.changeNum}_ps${this.patchNum}_${this.path}`;
+ return `c${this.viewState?.changeNum}_ps${this.viewState?.patchNum}_${this.viewState?.path}`;
}
// private but used in test
- paramsChanged() {
- if (!this.params) return;
-
- if (this.params.view !== GerritView.EDIT) {
- return;
- }
-
- this.changeNum = this.params.changeNum;
- this.path = this.params.path;
- this.patchNum = this.params.patchNum || (EDIT as PatchSetNum);
- this.lineNum =
- typeof this.params.lineNum === 'string'
- ? Number(this.params.lineNum)
- : this.params.lineNum;
+ viewStateChanged() {
+ if (!this.viewState) return;
// NOTE: This may be called before attachment (e.g. while parentElement is
// null). Fire title-change in an async so that, if attachment to the DOM
// has been queued, the event can bubble up to the handler in gr-app.
setTimeout(() => {
- if (!this.params) return;
- const title = `Editing ${computeTruncatedPath(this.params.path)}`;
+ if (!this.viewState) return;
+ const title = `Editing ${computeTruncatedPath(this.viewState.path)}`;
fireTitleChange(this, title);
});
const promises = [];
-
- assertIsDefined(this.changeNum, 'change number');
- assertIsDefined(this.path, 'path');
- promises.push(this.getChangeDetail(this.changeNum));
- promises.push(this.getFileData(this.changeNum, this.path, this.patchNum));
+ promises.push(this.getChangeDetail());
+ promises.push(this.getFileData());
return Promise.all(promises);
}
- private async getChangeDetail(changeNum: NumericChangeId) {
+ private async getChangeDetail() {
+ const changeNum = this.viewState?.changeNum;
+ assertIsDefined(changeNum, 'change number');
this.change = await this.restApiService.getChangeDetail(changeNum);
}
@@ -364,16 +341,17 @@
// private but used in test
async handlePathChanged(e: CustomEvent<string>): Promise<void> {
- // TODO(TS) could be cleaned up, it was added for type requirements
- if (this.changeNum === undefined || !this.path) {
- throw new Error('changeNum or path undefined');
- }
- const path = e.detail;
- if (path === this.path) return;
+ const changeNum = this.viewState?.changeNum;
+ const currentPath = this.viewState?.path;
+ assertIsDefined(changeNum, 'change number');
+ assertIsDefined(currentPath, 'path');
+
+ const newPath = e.detail;
+ if (newPath === currentPath) return;
const res = await this.restApiService.renameFileInChangeEdit(
- this.changeNum,
- this.path,
- path
+ changeNum,
+ currentPath,
+ newPath
);
if (!res?.ok) return;
@@ -383,22 +361,22 @@
// private but used in test
viewEditInChangeView() {
- if (this.change)
- GerritNav.navigateToChange(this.change, {
- isEdit: true,
- forceReload: true,
- });
+ if (!this.change) return;
+ GerritNav.navigateToChange(this.change, {
+ isEdit: true,
+ forceReload: true,
+ });
}
// private but used in test
- getFileData(
- changeNum: NumericChangeId,
- path: string,
- patchNum?: PatchSetNum
- ) {
- if (patchNum === undefined) {
- return Promise.reject(new Error('patchNum undefined'));
- }
+ getFileData() {
+ const changeNum = this.viewState?.changeNum;
+ const patchNum = this.viewState?.patchNum;
+ const path = this.viewState?.path;
+ assertIsDefined(changeNum, 'change number');
+ assertIsDefined(patchNum, 'patchset number');
+ assertIsDefined(path, 'path');
+
const storedContent = this.storage.getEditableContentItem(this.storageKey);
return this.restApiService
@@ -431,16 +409,18 @@
// private but used in test
saveEdit() {
- if (this.changeNum === undefined || !this.path) {
- return Promise.reject(new Error('changeNum or path undefined'));
- }
+ const changeNum = this.viewState?.changeNum;
+ const path = this.viewState?.path;
+ assertIsDefined(changeNum, 'change number');
+ assertIsDefined(path, 'path');
+
this.saving = true;
this.showAlert(SAVING_MESSAGE);
this.storage.eraseEditableContentItem(this.storageKey);
if (!this.newContent)
return Promise.reject(new Error('new content undefined'));
return this.restApiService
- .saveChangeEdit(this.changeNum, this.path, this.newContent)
+ .saveChangeEdit(changeNum, path, this.newContent)
.then(res => {
this.saving = false;
this.showAlert(res.ok ? SAVED_MESSAGE : SAVE_FAILED_MSG);
@@ -464,9 +444,7 @@
return true;
}
- if (this.saving) {
- return true;
- }
+ if (this.saving) return true;
return this.content === this.newContent;
}
@@ -483,9 +461,9 @@
};
private handlePublishTap = () => {
- assertIsDefined(this.changeNum, 'changeNum');
+ const changeNum = this.viewState?.changeNum;
+ assertIsDefined(changeNum, 'change number');
- const changeNum = this.changeNum;
this.saveEdit().then(() => {
const handleError: ErrorCallback = response => {
this.showAlert(PUBLISH_FAILED_MSG);
@@ -528,9 +506,7 @@
// private but used in test
handleSaveShortcut() {
- if (!this.computeSaveDisabled()) {
- this.saveEdit();
- }
+ if (!this.computeSaveDisabled()) this.saveEdit();
}
}
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
index 30d935c..8bc788c 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
@@ -15,7 +15,12 @@
stubRestApi,
stubStorage,
} from '../../../test/test-utils';
-import {EDIT, NumericChangeId, PatchSetNum} from '../../../types/common';
+import {
+ EDIT,
+ NumericChangeId,
+ PatchSetNumber,
+ RevisionPatchSetNum,
+} from '../../../types/common';
import {
createChangeViewChange,
createEditViewState,
@@ -41,6 +46,11 @@
saveFileStub = stubRestApi('saveChangeEdit');
changeDetailStub = stubRestApi('getChangeDetail');
navigateStub = sinon.stub(element, 'viewEditInChangeView');
+ element.viewState = {
+ ...createEditViewState(),
+ patchNum: 1 as PatchSetNumber,
+ };
+ element.latestPatchsetNumber = 1 as PatchSetNumber;
await element.updateComplete;
});
@@ -58,7 +68,7 @@
labeltext="File path"
placeholder="File path..."
tabindex="0"
- title="File path..."
+ title="${element.viewState?.path}"
>
</gr-editable-label>
</span>
@@ -112,8 +122,8 @@
);
});
- suite('paramsChanged', () => {
- test('good params proceed', async () => {
+ suite('viewStateChanged', () => {
+ test('good view state proceed', async () => {
changeDetailStub.returns(Promise.resolve({}));
const fileStub = sinon.stub(element, 'getFileData').callsFake(() => {
element.content = 'text';
@@ -122,20 +132,14 @@
return Promise.resolve();
});
- element.params = {...createEditViewState()};
- const promises = element.paramsChanged();
+ element.viewState = {...createEditViewState()};
+ const promises = element.viewStateChanged();
await element.updateComplete;
const changeNum = 42 as NumericChangeId;
- assert.equal(element.changeNum, changeNum);
- assert.equal(element.path, 'foo/bar.baz');
assert.deepEqual(changeDetailStub.lastCall.args[0], changeNum);
- assert.deepEqual(fileStub.lastCall.args, [
- changeNum,
- 'foo/bar.baz',
- EDIT as PatchSetNum,
- ]);
+ assert.isTrue(fileStub.called);
return promises?.then(() => {
assert.equal(element.content, 'text');
@@ -146,8 +150,7 @@
});
test('edit file path', () => {
- element.changeNum = 42 as NumericChangeId;
- element.path = 'foo/bar.baz';
+ element.viewState = {...createEditViewState()};
savePathStub.onFirstCall().returns(Promise.resolve({}));
savePathStub.onSecondCall().returns(Promise.resolve({ok: true}));
@@ -197,8 +200,7 @@
const newText = 'file text changed';
setup(async () => {
- element.changeNum = 42 as NumericChangeId;
- element.path = 'foo/bar.baz';
+ element.viewState = {...createEditViewState()};
element.content = originalText;
element.newContent = originalText;
await element.updateComplete;
@@ -363,30 +365,38 @@
content: 'new content',
})
);
+ element.viewState = {
+ ...createEditViewState(),
+ changeNum: 1 as NumericChangeId,
+ patchNum: EDIT,
+ path: 'test/path',
+ };
// Ensure no data is set with a bad response.
- return element
- .getFileData(1 as NumericChangeId, 'test/path', EDIT as PatchSetNum)
- .then(() => {
- assert.equal(element.newContent, 'new content');
- assert.equal(element.content, 'new content');
- assert.equal(element.type, 'text/javascript');
- });
+ return element.getFileData().then(() => {
+ assert.equal(element.newContent, 'new content');
+ assert.equal(element.content, 'new content');
+ assert.equal(element.type, 'text/javascript');
+ });
});
test('!res.ok', () => {
stubRestApi('getFileContent').returns(
Promise.resolve(new Response(null, {status: 500}))
);
+ element.viewState = {
+ ...createEditViewState(),
+ changeNum: 1 as NumericChangeId,
+ patchNum: EDIT,
+ path: 'test/path',
+ };
// Ensure no data is set with a bad response.
- return element
- .getFileData(1 as NumericChangeId, 'test/path', EDIT as PatchSetNum)
- .then(() => {
- assert.equal(element.newContent, '');
- assert.equal(element.content, '');
- assert.equal(element.type, '');
- });
+ return element.getFileData().then(() => {
+ assert.equal(element.newContent, '');
+ assert.equal(element.content, '');
+ assert.equal(element.type, '');
+ });
});
test('content is undefined', () => {
@@ -397,28 +407,36 @@
type: 'text/javascript' as ResponseType,
})
);
+ element.viewState = {
+ ...createEditViewState(),
+ changeNum: 1 as NumericChangeId,
+ patchNum: EDIT,
+ path: 'test/path',
+ };
- return element
- .getFileData(1 as NumericChangeId, 'test/path', EDIT as PatchSetNum)
- .then(() => {
- assert.equal(element.newContent, '');
- assert.equal(element.content, '');
- assert.equal(element.type, 'text/javascript');
- });
+ return element.getFileData().then(() => {
+ assert.equal(element.newContent, '');
+ assert.equal(element.content, '');
+ assert.equal(element.type, 'text/javascript');
+ });
});
test('content and type is undefined', () => {
stubRestApi('getFileContent').returns(
Promise.resolve({...new Response(), ok: true})
);
+ element.viewState = {
+ ...createEditViewState(),
+ changeNum: 1 as NumericChangeId,
+ patchNum: EDIT,
+ path: 'test/path',
+ };
- return element
- .getFileData(1 as NumericChangeId, 'test/path', EDIT as PatchSetNum)
- .then(() => {
- assert.equal(element.newContent, '');
- assert.equal(element.content, '');
- assert.equal(element.type, '');
- });
+ return element.getFileData().then(() => {
+ assert.equal(element.newContent, '');
+ assert.equal(element.content, '');
+ assert.equal(element.type, '');
+ });
});
});
@@ -438,7 +456,6 @@
element.change = createChangeViewChange();
navigateStub.restore();
const navStub = sinon.stub(GerritNav, 'navigateToChange');
- element.patchNum = EDIT;
element.viewEditInChangeView();
assert.equal(navStub.lastCall.args[1]!.patchNum, undefined);
assert.equal(navStub.lastCall.args[1]!.isEdit, true);
@@ -500,20 +517,24 @@
content: 'old content',
})
);
+ element.viewState = {
+ ...createEditViewState(),
+ changeNum: 1 as NumericChangeId,
+ patchNum: 1 as RevisionPatchSetNum,
+ path: 'test',
+ };
const alertStub = sinon.stub();
element.addEventListener(EventType.SHOW_ALERT, alertStub);
- return element
- .getFileData(1 as NumericChangeId, 'test', 1 as PatchSetNum)
- .then(async () => {
- await element.updateComplete;
+ return element.getFileData().then(async () => {
+ await element.updateComplete;
- assert.isTrue(alertStub.called);
- assert.equal(element.newContent, 'pending edit');
- assert.equal(element.content, 'old content');
- assert.equal(element.type, 'text/javascript');
- });
+ assert.isTrue(alertStub.called);
+ assert.equal(element.newContent, 'pending edit');
+ assert.equal(element.content, 'old content');
+ assert.equal(element.type, 'text/javascript');
+ });
});
test('local edit exists, is same as remote edit', () => {
@@ -528,26 +549,33 @@
content: 'pending edit',
})
);
+ element.viewState = {
+ ...createEditViewState(),
+ changeNum: 1 as NumericChangeId,
+ patchNum: 1 as RevisionPatchSetNum,
+ path: 'test',
+ };
const alertStub = sinon.stub();
element.addEventListener(EventType.SHOW_ALERT, alertStub);
- return element
- .getFileData(1 as NumericChangeId, 'test', 1 as PatchSetNum)
- .then(async () => {
- await element.updateComplete;
+ return element.getFileData().then(async () => {
+ await element.updateComplete;
- assert.isFalse(alertStub.called);
- assert.equal(element.newContent, 'pending edit');
- assert.equal(element.content, 'pending edit');
- assert.equal(element.type, 'text/javascript');
- });
+ assert.isFalse(alertStub.called);
+ assert.equal(element.newContent, 'pending edit');
+ assert.equal(element.content, 'pending edit');
+ assert.equal(element.type, 'text/javascript');
+ });
});
test('storage key computation', () => {
- element.changeNum = 1 as NumericChangeId;
- element.patchNum = 1 as PatchSetNum;
- element.path = 'test';
+ element.viewState = {
+ ...createEditViewState(),
+ changeNum: 1 as NumericChangeId,
+ patchNum: 1 as RevisionPatchSetNum,
+ path: 'test',
+ };
assert.equal(element.storageKey, 'c1_ps1_test');
});
});
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index 52ff3db..354d596 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -31,7 +31,7 @@
import {getBaseUrl} from '../utils/url-util';
import {GerritNav} from './core/gr-navigation/gr-navigation';
import {getAppContext} from '../services/app-context';
-import {GrRouter} from './core/gr-router/gr-router';
+import {routerToken} from './core/gr-router/gr-router';
import {AccountDetailInfo} from '../types/common';
import {
constructServerErrorMsg,
@@ -49,7 +49,6 @@
import {
DialogChangeEventDetail,
EventType,
- LocationChangeEvent,
PageErrorEventDetail,
RpcLogEvent,
TitleChangeEventDetail,
@@ -101,7 +100,7 @@
@query('#keyboardShortcuts') keyboardShortcuts?: GrOverlay;
- @query('gr-settings-view') settingdView?: GrSettingsView;
+ @query('gr-settings-view') settingsView?: GrSettingsView;
@property({type: Object})
params?: AppElementParams;
@@ -110,33 +109,13 @@
@state() private version?: string;
- @state() private showChangeListView?: boolean;
-
- @state() private showDashboardView?: boolean;
-
- @state() private showChangeView?: boolean;
-
- @state() private showDiffView?: boolean;
-
- @state() private showSettingsView?: boolean;
-
- @state() private showAdminView?: boolean;
-
- @state() private showCLAView?: boolean;
-
- @state() private showEditorView?: boolean;
-
- @state() private showPluginScreen?: boolean;
-
- @state() private showDocumentationSearch?: boolean;
+ @state() private view?: GerritView;
@state() private lastError?: ErrorInfo;
// private but used in test
@state() lastSearchPage?: string;
- @state() private path?: string;
-
@state() private settingsUrl?: string;
@state() private mobileSearch = false;
@@ -167,7 +146,7 @@
@state() private themeEndpoint = 'app-theme-light';
- readonly router = new GrRouter();
+ readonly getRouter = resolve(this, routerToken);
private reporting = getAppContext().reportingService;
@@ -181,8 +160,11 @@
private readonly userModel = getAppContext().userModel;
+ private readonly routerModel = getAppContext().routerModel;
+
constructor() {
super();
+
document.addEventListener(EventType.PAGE_ERROR, e => {
this.handlePageError(e);
});
@@ -192,8 +174,8 @@
this.addEventListener(EventType.DIALOG_CHANGE, e => {
this.handleDialogChange(e as CustomEvent<DialogChangeEventDetail>);
});
- this.addEventListener(EventType.LOCATION_CHANGE, e =>
- this.handleLocationChange(e)
+ this.addEventListener(EventType.LOCATION_CHANGE, () =>
+ this.handleLocationChange()
);
this.addEventListener(EventType.RECREATE_CHANGE_VIEW, () =>
this.handleRecreateView()
@@ -229,6 +211,14 @@
this.applyTheme();
}
);
+ subscribe(
+ this,
+ () => this.routerModel.routerView$,
+ view => {
+ this.view = view;
+ if (view) this.errorView?.classList.remove('show');
+ }
+ );
prefersDarkColorScheme().addEventListener('change', () => {
if (this.theme === AppTheme.AUTO) {
@@ -244,7 +234,7 @@
this.updateLoginUrl();
this.reporting.appStarted();
- this.router.start();
+ this.getRouter().start();
this.restApiService.getAccount().then(account => {
this.account = account;
@@ -440,26 +430,16 @@
private renderChangeListView() {
return cache(
- this.showChangeListView
- ? html`
- <gr-change-list-view
- .params=${this.params}
- .account=${this.account}
- ></gr-change-list-view>
- `
+ this.view === GerritView.SEARCH
+ ? html` <gr-change-list-view></gr-change-list-view> `
: nothing
);
}
private renderDashboardView() {
return cache(
- this.showDashboardView
- ? html`
- <gr-dashboard-view
- .account=${this.account}
- .params=${this.params}
- ></gr-dashboard-view>
- `
+ this.view === GerritView.DASHBOARD
+ ? html`<gr-dashboard-view></gr-dashboard-view>`
: nothing
);
}
@@ -469,22 +449,21 @@
this.updateComplete.then(() => (this.invalidateChangeViewCache = false));
return nothing;
}
- return cache(this.showChangeView ? this.changeViewTemplate() : nothing);
+ return cache(
+ this.view === GerritView.CHANGE ? this.changeViewTemplate() : nothing
+ );
}
// Template as not to create duplicates, for renderChangeView() only.
private changeViewTemplate() {
return html`
- <gr-change-view
- .params=${this.params}
- .backPage=${this.lastSearchPage}
- ></gr-change-view>
+ <gr-change-view .backPage=${this.lastSearchPage}></gr-change-view>
`;
}
private renderEditorView() {
- if (!this.showEditorView) return nothing;
- return html`<gr-editor-view .params=${this.params}></gr-editor-view>`;
+ if (this.view !== GerritView.EDIT) return nothing;
+ return html`<gr-editor-view></gr-editor-view>`;
}
private renderDiffView() {
@@ -492,18 +471,19 @@
this.updateComplete.then(() => (this.invalidateDiffViewCache = false));
return nothing;
}
- return cache(this.showDiffView ? this.diffViewTemplate() : nothing);
+ return cache(
+ this.view === GerritView.DIFF ? this.diffViewTemplate() : nothing
+ );
}
private diffViewTemplate() {
- return html`<gr-diff-view .params=${this.params}></gr-diff-view>`;
+ return html`<gr-diff-view></gr-diff-view>`;
}
private renderSettingsView() {
- if (!this.showSettingsView) return nothing;
+ if (this.view !== GerritView.SETTINGS) return nothing;
return html`
<gr-settings-view
- .params=${this.params}
@account-detail-update=${this.handleAccountDetailUpdate}
>
</gr-settings-view>
@@ -511,35 +491,36 @@
}
private renderAdminView() {
- if (!this.showAdminView) return nothing;
- return html`<gr-admin-view
- .path=${this.path}
- .params=${this.params}
- ></gr-admin-view>`;
+ if (
+ this.view !== GerritView.ADMIN &&
+ this.view !== GerritView.GROUP &&
+ this.view !== GerritView.REPO
+ )
+ return nothing;
+ return html`<gr-admin-view></gr-admin-view>`;
}
private renderPluginScreen() {
- if (!this.showPluginScreen) return nothing;
+ if (this.view !== GerritView.PLUGIN_SCREEN) return nothing;
+ const pluginViewState = this.params as PluginViewState;
return html`
<gr-endpoint-decorator .name=${this.computePluginScreenName()}>
<gr-endpoint-param
name="token"
- .value=${(this.params as PluginViewState).screen}
+ .value=${pluginViewState.screen}
></gr-endpoint-param>
</gr-endpoint-decorator>
`;
}
private renderCLAView() {
- if (!this.showCLAView) return nothing;
+ if (this.view !== GerritView.AGREEMENTS) return nothing;
return html`<gr-cla-view></gr-cla-view>`;
}
private renderDocumentationSearch() {
- if (!this.showDocumentationSearch) return nothing;
- return html`
- <gr-documentation-search .params=${this.params}></gr-documentation-search>
- `;
+ if (this.view !== GerritView.DOCUMENTATION_SEARCH) return nothing;
+ return html`<gr-documentation-search></gr-documentation-search>`;
}
private renderKeyboardShortcutsDialog() {
@@ -579,7 +560,6 @@
if (changedProperties.has('params')) {
this.viewChanged();
-
this.paramsChanged();
}
}
@@ -606,29 +586,6 @@
}
private async viewChanged() {
- const view = this.params?.view;
- this.errorView?.classList.remove('show');
- this.showChangeListView = view === GerritView.SEARCH;
- this.showDashboardView = view === GerritView.DASHBOARD;
- this.showChangeView = view === GerritView.CHANGE;
- this.showDiffView = view === GerritView.DIFF;
- this.showSettingsView = view === GerritView.SETTINGS;
- // showAdminView must be in sync with the gr-admin-view AdminViewParams type
- this.showAdminView =
- view === GerritView.ADMIN ||
- view === GerritView.GROUP ||
- view === GerritView.REPO;
- this.showCLAView = view === GerritView.AGREEMENTS;
- this.showEditorView = view === GerritView.EDIT;
- const isPluginScreen = view === GerritView.PLUGIN_SCREEN;
- this.showPluginScreen = false;
- // Navigation within plugin screens does not restamp gr-endpoint-decorator
- // because showPluginScreen value does not change. To force restamp,
- // change showPluginScreen value between true and false.
- if (isPluginScreen) {
- setTimeout(() => (this.showPluginScreen = true), 1);
- }
- this.showDocumentationSearch = view === GerritView.DOCUMENTATION_SEARCH;
if (
this.params &&
isAppElementJustRegisteredParams(this.params) &&
@@ -665,19 +622,7 @@
}
private handlePageError(e: CustomEvent<PageErrorEventDetail>) {
- const props = [
- 'showChangeListView',
- 'showDashboardView',
- 'showChangeView',
- 'showDiffView',
- 'showSettingsView',
- 'showAdminView',
- ];
- for (const showProp of props) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (this as any)[showProp as any] = false;
- }
-
+ this.view = undefined;
this.errorView?.classList.add('show');
const response = e.detail.response;
const err: ErrorInfo = {
@@ -705,15 +650,8 @@
}
}
- private handleLocationChange(e: LocationChangeEvent) {
+ private handleLocationChange() {
this.updateLoginUrl();
-
- const hash = e.detail.hash.substring(1);
- let pathname = e.detail.pathname;
- if (pathname.startsWith('/c/') && Number(hash) > 0) {
- pathname += '@' + hash;
- }
- this.path = pathname;
}
private updateLoginUrl() {
@@ -791,10 +729,7 @@
private handleAccountDetailUpdate() {
this.mainHeader?.reload();
- if (this.params?.view === GerritView.SETTINGS) {
- assertIsDefined(this.settingdView, 'settingdView');
- this.settingdView.reloadAccountDetail();
- }
+ this.settingsView?.reloadAccountDetail();
}
private handleRegistrationDialogClose() {
@@ -827,9 +762,11 @@
}
private computePluginScreenName() {
- if (this.params?.view !== GerritView.PLUGIN_SCREEN) return '';
- if (!this.params.plugin || !this.params.screen) return '';
- return `${this.params.plugin}-screen-${this.params.screen}`;
+ if (this.view !== GerritView.PLUGIN_SCREEN) return '';
+ if (this.params === undefined) return '';
+ const pluginViewState = this.params as PluginViewState;
+ if (!pluginViewState.plugin || !pluginViewState.screen) return '';
+ return `${pluginViewState.plugin}-screen-${pluginViewState.screen}`;
}
private logWelcome() {
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index 470d847..1c5d30d 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -23,7 +23,6 @@
import '../gr-ssh-editor/gr-ssh-editor';
import '../gr-watched-projects-editor/gr-watched-projects-editor';
import {getDocsBaseUrl} from '../../../utils/url-util';
-import {AppElementParams} from '../../gr-app-types';
import {GrAccountInfo} from '../gr-account-info/gr-account-info';
import {GrWatchedProjectsEditor} from '../gr-watched-projects-editor/gr-watched-projects-editor';
import {GrGroupList} from '../gr-group-list/gr-group-list';
@@ -35,7 +34,6 @@
import {GrEmailEditor} from '../gr-email-editor/gr-email-editor';
import {fireAlert, fireTitleChange} from '../../../utils/event-util';
import {getAppContext} from '../../../services/app-context';
-import {GerritView} from '../../../services/router/router-model';
import {
ColumnNames,
DateFormat,
@@ -48,13 +46,7 @@
} from '../../../constants/constants';
import {BindValueChangeEvent, ValueChangedEvent} from '../../../types/events';
import {LitElement, css, html, nothing} from 'lit';
-import {
- customElement,
- property,
- query,
- queryAsync,
- state,
-} from 'lit/decorators.js';
+import {customElement, query, queryAsync, state} from 'lit/decorators.js';
import {sharedStyles} from '../../../styles/shared-styles';
import {paperStyles} from '../../../styles/gr-paper-styles';
import {fontStyles} from '../../../styles/gr-font-styles';
@@ -64,6 +56,8 @@
import {formStyles} from '../../../styles/gr-form-styles';
import {KnownExperimentId} from '../../../services/flags/flags';
import {subscribe} from '../../lit/subscription-controller';
+import {resolve} from '../../../models/dependency';
+import {settingsViewModelToken} from '../../../models/views/settings';
const GERRIT_DOCS_BASE_URL =
'https://gerrit-review.googlesource.com/' + 'Documentation';
@@ -146,8 +140,6 @@
@state() prefs: PreferencesInput = {};
- @property({type: Object}) params?: AppElementParams;
-
@state() private accountInfoChanged = false;
// private but used in test
@@ -189,6 +181,9 @@
@state() private emailsChanged = false;
// private but used in test
+ @state() emailToken?: string;
+
+ // private but used in test
@state() showNumber?: boolean;
// private but used in test
@@ -200,10 +195,20 @@
private readonly flagsService = getAppContext().flagsService;
+ private readonly getViewModel = resolve(this, settingsViewModelToken);
+
constructor() {
super();
subscribe(
this,
+ () => this.getViewModel().emailToken$,
+ x => {
+ this.emailToken = x;
+ this.confirmEmail();
+ }
+ );
+ subscribe(
+ this,
() => this.userModel.preferences$,
prefs => {
if (!prefs) {
@@ -223,6 +228,15 @@
);
}
+ // private, but used in tests
+ async confirmEmail() {
+ if (!this.emailToken) return;
+ const message = await this.restApiService.confirmEmail(this.emailToken);
+ if (message) fireAlert(this, message);
+ this.getViewModel().clearToken();
+ this.emailEditor.loadData();
+ }
+
override connectedCallback() {
super.connectedCallback();
// Polymer 2: anchor tag won't work on shadow DOM
@@ -266,24 +280,7 @@
})
);
- if (
- this.params &&
- this.params.view === GerritView.SETTINGS &&
- this.params.emailToken
- ) {
- promises.push(
- this.restApiService
- .confirmEmail(this.params.emailToken)
- .then(message => {
- if (message) {
- fireAlert(this, message);
- }
- this.emailEditor.loadData();
- })
- );
- } else {
- promises.push(this.emailEditor.loadData());
- }
+ promises.push(this.emailEditor.loadData());
this._testOnly_loadingPromise = Promise.all(promises).then(() => {
this.loading = false;
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
index 5e0c381..72c19b4 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
@@ -6,7 +6,6 @@
import '../../../test/common-test-setup';
import './gr-settings-view';
import {GrSettingsView} from './gr-settings-view';
-import {GerritView} from '../../../services/router/router-model';
import {queryAll, stubRestApi, waitEventLoop} from '../../../test/test-utils';
import {
AuthInfo,
@@ -34,7 +33,6 @@
import {GrSelect} from '../../shared/gr-select/gr-select';
import {fixture, html, assert} from '@open-wc/testing';
import {EventType} from '../../../types/events';
-import {SettingsViewState} from '../../../models/views/settings';
suite('gr-settings-view tests', () => {
let element: GrSettingsView;
@@ -755,9 +753,6 @@
test('emails are loaded without emailToken', () => {
const emailEditorLoadDataStub = sinon.stub(element.emailEditor, 'loadData');
- element.params = {
- view: GerritView.SETTINGS,
- } as SettingsViewState;
element.firstUpdated();
assert.isTrue(emailEditorLoadDataStub.calledOnce);
});
@@ -861,8 +856,8 @@
})
);
- element.params = {view: GerritView.SETTINGS, emailToken: 'foo'};
- element.firstUpdated();
+ element.emailToken = 'foo';
+ element.confirmEmail();
});
test('it is used to confirm email via rest API', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
index acf4d13..f33f7d8 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
@@ -8,10 +8,9 @@
import {votingStyles} from '../../../styles/gr-voting-styles';
import {css, html, LitElement, nothing, PropertyValues} from 'lit';
import {customElement, property} from 'lit/decorators.js';
-import {getEventPath, Key} from '../../../utils/dom-util';
+import {addShortcut, getEventPath, Key} from '../../../utils/dom-util';
import {getAppContext} from '../../../services/app-context';
import {classMap} from 'lit/directives/class-map.js';
-import {ShortcutController} from '../../lit/shortcut-controller';
declare global {
interface HTMLElementTagNameMap {
@@ -56,8 +55,6 @@
@property({type: Boolean, reflect: true})
disabled: boolean | null = null;
- private readonly shortcuts = new ShortcutController(this);
-
static override get styles() {
return [
votingStyles,
@@ -211,8 +208,8 @@
super();
this.initialTabindex = this.getAttribute('tabindex') || '0';
this.addEventListener('click', e => this._handleAction(e));
- this.shortcuts.addLocal({key: Key.ENTER}, () => this.click());
- this.shortcuts.addLocal({key: Key.SPACE}, () => this.click());
+ addShortcut(this, {key: Key.ENTER}, () => this.click());
+ addShortcut(this, {key: Key.SPACE}, () => this.click());
}
override updated(changedProperties: PropertyValues) {
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
index 3887ee5b..eab6638 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
@@ -34,7 +34,7 @@
export const MERGE_CONFLICT_TOOLTIP =
'This change has merge conflicts. ' +
- 'Download the patch and run "git rebase". ' +
+ 'Rebase on the upstream branch (e.g. "git pull --rebase"). ' +
'Upload a new patchset after resolving all merge conflicts.';
export const GIT_CONFLICT_TOOLTIP =
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index f5e8a1a..cc76150 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -501,6 +501,9 @@
.draft gr-account-label {
width: unset;
}
+ .draft gr-formatted-text.message {
+ margin-bottom: var(--spacing-m);
+ }
.portedMessage {
margin: 0 var(--spacing-m);
}
@@ -723,7 +726,6 @@
class="message"
.content=${this.comment?.message}
.config=${this.commentLinks}
- ?noTrailingMargin=${!isDraftOrUnsaved(this.comment)}
></gr-formatted-text>
`;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index 2e4383e..5686c6b 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -141,10 +141,7 @@
</div>
</div>
<div class="body">
- <gr-formatted-text
- class="message"
- notrailingmargin=""
- ></gr-formatted-text>
+ <gr-formatted-text class="message"></gr-formatted-text>
</div>
</div>
`
@@ -178,10 +175,7 @@
</div>
<div class="body">
<div class="robotId"></div>
- <gr-formatted-text
- class="message"
- notrailingmargin=""
- ></gr-formatted-text>
+ <gr-formatted-text class="message"></gr-formatted-text>
<div class="robotActions">
<gr-icon
icon="link"
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
index 988f970..3ea9c5d 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
@@ -612,7 +612,7 @@
.filter(account => account.email)
.map(account => {
return {
- text: account.email,
+ text: `${account.name ?? ''} <${account.email}>`,
dataValue: account.email,
};
});
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
index 93560ab..76c5033 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
@@ -137,8 +137,8 @@
test('mention selector opens when previous char is \n', async () => {
stubRestApi('getSuggestedAccounts').returns(
Promise.resolve([
- createAccountWithEmail('abc@google.com'),
- createAccountWithEmail('abcdef@google.com'),
+ {...createAccountWithEmail('abc@google.com'), name: 'A'},
+ {...createAccountWithEmail('abcdef@google.com'), name: 'B'},
])
);
element.textarea!.focus();
@@ -151,6 +151,17 @@
await waitUntil(() => element.suggestions.length > 0);
await element.updateComplete;
+ assert.deepEqual(element.suggestions, [
+ {
+ dataValue: 'abc@google.com',
+ text: 'A <abc@google.com>',
+ },
+ {
+ dataValue: 'abcdef@google.com',
+ text: 'B <abcdef@google.com>',
+ },
+ ]);
+
assert.isTrue(element.emojiSuggestions!.isHidden);
assert.isFalse(element.mentionsSuggestions!.isHidden);
});
diff --git a/polygerrit-ui/app/models/accounts-model/accounts-model.ts b/polygerrit-ui/app/models/accounts-model/accounts-model.ts
index 74eb813..b994614 100644
--- a/polygerrit-ui/app/models/accounts-model/accounts-model.ts
+++ b/polygerrit-ui/app/models/accounts-model/accounts-model.ts
@@ -25,8 +25,6 @@
});
}
- finalize() {}
-
private updateStateAccount(id: UserId, account?: AccountDetailInfo) {
const current = {...this.subject$.getValue()};
if (!account) return;
diff --git a/polygerrit-ui/app/models/browser/browser-model.ts b/polygerrit-ui/app/models/browser/browser-model.ts
index cd5b753..9fd3f79 100644
--- a/polygerrit-ui/app/models/browser/browser-model.ts
+++ b/polygerrit-ui/app/models/browser/browser-model.ts
@@ -66,6 +66,4 @@
setScreenWidth(screenWidth: number) {
this.subject$.next({...this.subject$.getValue(), screenWidth});
}
-
- finalize() {}
}
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
index c40a339..2ebb820 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
@@ -309,6 +309,4 @@
}) ?? []
);
}
-
- finalize() {}
}
diff --git a/polygerrit-ui/app/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts
index 057e818..138c3ca 100644
--- a/polygerrit-ui/app/models/change/change-model.ts
+++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -316,7 +316,7 @@
];
}
- finalize() {
+ override finalize() {
for (const s of this.subscriptions) {
s.unsubscribe();
}
diff --git a/polygerrit-ui/app/models/change/files-model.ts b/polygerrit-ui/app/models/change/files-model.ts
index e26167c..d15ea8d 100644
--- a/polygerrit-ui/app/models/change/files-model.ts
+++ b/polygerrit-ui/app/models/change/files-model.ts
@@ -199,7 +199,7 @@
});
}
- finalize() {
+ override finalize() {
for (const s of this.subscriptions) {
s.unsubscribe();
}
diff --git a/polygerrit-ui/app/models/checks/checks-model.ts b/polygerrit-ui/app/models/checks/checks-model.ts
index 28dec3c..dd73879 100644
--- a/polygerrit-ui/app/models/checks/checks-model.ts
+++ b/polygerrit-ui/app/models/checks/checks-model.ts
@@ -477,7 +477,7 @@
this.reporting.reportInteraction(Interaction.CHECKS_STATS, stats);
}
- finalize() {
+ override finalize() {
document.removeEventListener('reload', this.reloadListener);
document.removeEventListener(
'visibilitychange',
diff --git a/polygerrit-ui/app/models/comments/comments-model.ts b/polygerrit-ui/app/models/comments/comments-model.ts
index 3372b26..9957c66 100644
--- a/polygerrit-ui/app/models/comments/comments-model.ts
+++ b/polygerrit-ui/app/models/comments/comments-model.ts
@@ -410,7 +410,7 @@
document.addEventListener('reload', this.reloadListener);
}
- finalize() {
+ override finalize() {
document.removeEventListener('reload', this.reloadListener);
for (const s of this.subscriptions) {
s.unsubscribe();
diff --git a/polygerrit-ui/app/models/config/config-model.ts b/polygerrit-ui/app/models/config/config-model.ts
index 6f8b066..83b6103 100644
--- a/polygerrit-ui/app/models/config/config-model.ts
+++ b/polygerrit-ui/app/models/config/config-model.ts
@@ -86,7 +86,7 @@
this.subject$.next({...current, serverConfig});
}
- finalize() {
+ override finalize() {
for (const s of this.subscriptions) {
s.unsubscribe();
}
diff --git a/polygerrit-ui/app/models/model.ts b/polygerrit-ui/app/models/model.ts
index 3854c08..9301dc4 100644
--- a/polygerrit-ui/app/models/model.ts
+++ b/polygerrit-ui/app/models/model.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {BehaviorSubject, Observable} from 'rxjs';
+import {Finalizable} from '../services/registry';
/**
* A Model stores a value <T> and controls changes to that value via `subject$`
@@ -18,7 +19,7 @@
*
* Any new subscriber will immediately receive the current value.
*/
-export abstract class Model<T> {
+export abstract class Model<T> implements Finalizable {
protected subject$: BehaviorSubject<T>;
public state$: Observable<T>;
@@ -32,4 +33,6 @@
const currentState = this.subject$.getValue();
this.subject$.next({...currentState, ...newState});
}
+
+ finalize() {}
}
diff --git a/polygerrit-ui/app/models/plugins/plugins-model.ts b/polygerrit-ui/app/models/plugins/plugins-model.ts
index 372a34e..19aefbb 100644
--- a/polygerrit-ui/app/models/plugins/plugins-model.ts
+++ b/polygerrit-ui/app/models/plugins/plugins-model.ts
@@ -60,7 +60,7 @@
});
}
- finalize() {
+ override finalize() {
this.subject$.complete();
}
diff --git a/polygerrit-ui/app/models/user/user-model.ts b/polygerrit-ui/app/models/user/user-model.ts
index c62a94e..4b4541a 100644
--- a/polygerrit-ui/app/models/user/user-model.ts
+++ b/polygerrit-ui/app/models/user/user-model.ts
@@ -140,7 +140,7 @@
];
}
- finalize() {
+ override finalize() {
for (const s of this.subscriptions) {
s.unsubscribe();
}
diff --git a/polygerrit-ui/app/models/views/admin.ts b/polygerrit-ui/app/models/views/admin.ts
index 856d947..2ad95a2 100644
--- a/polygerrit-ui/app/models/views/admin.ts
+++ b/polygerrit-ui/app/models/views/admin.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {GerritView} from '../../services/router/router-model';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -20,13 +21,10 @@
offset?: number | string;
}
-const DEFAULT_STATE: AdminViewState = {
- view: GerritView.ADMIN,
- adminView: AdminChildView.REPOS,
-};
+export const adminViewModelToken = define<AdminViewModel>('admin-view-model');
-export class AdminViewModel extends Model<AdminViewState> {
+export class AdminViewModel extends Model<AdminViewState | undefined> {
constructor() {
- super(DEFAULT_STATE);
+ super(undefined);
}
}
diff --git a/polygerrit-ui/app/models/views/agreement.ts b/polygerrit-ui/app/models/views/agreement.ts
index 4f1763d..839699c 100644
--- a/polygerrit-ui/app/models/views/agreement.ts
+++ b/polygerrit-ui/app/models/views/agreement.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {GerritView} from '../../services/router/router-model';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -13,6 +14,10 @@
const DEFAULT_STATE: AgreementViewState = {view: GerritView.AGREEMENTS};
+export const agreementViewModelToken = define<AgreementViewModel>(
+ 'agreement-view-model'
+);
+
export class AgreementViewModel extends Model<AgreementViewState> {
constructor() {
super(DEFAULT_STATE);
diff --git a/polygerrit-ui/app/models/views/change.ts b/polygerrit-ui/app/models/views/change.ts
index 94d465e6..7f75d83 100644
--- a/polygerrit-ui/app/models/views/change.ts
+++ b/polygerrit-ui/app/models/views/change.ts
@@ -17,6 +17,7 @@
getPatchRangeExpression,
} from '../../utils/url-util';
import {AttemptChoice} from '../checks/checks-util';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -40,10 +41,6 @@
usp?: string;
}
-const DEFAULT_STATE: ChangeViewState = {
- view: GerritView.CHANGE,
-};
-
export function createChangeUrl(state: Omit<ChangeViewState, 'view'>) {
let range = getPatchRangeExpression(state);
if (range.length) {
@@ -80,8 +77,11 @@
}
}
-export class ChangeViewModel extends Model<ChangeViewState> {
+export const changeViewModelToken =
+ define<ChangeViewModel>('change-view-model');
+
+export class ChangeViewModel extends Model<ChangeViewState | undefined> {
constructor() {
- super(DEFAULT_STATE);
+ super(undefined);
}
}
diff --git a/polygerrit-ui/app/models/views/dashboard.ts b/polygerrit-ui/app/models/views/dashboard.ts
index dec53d1..9326b9e 100644
--- a/polygerrit-ui/app/models/views/dashboard.ts
+++ b/polygerrit-ui/app/models/views/dashboard.ts
@@ -7,6 +7,7 @@
import {GerritView} from '../../services/router/router-model';
import {DashboardId} from '../../types/common';
import {encodeURL} from '../../utils/url-util';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -28,10 +29,6 @@
title?: string;
}
-const DEFAULT_STATE: DashboardViewState = {
- view: GerritView.DASHBOARD,
-};
-
const REPO_TOKEN_PATTERN = /\${(project|repo)}/g;
function sectionsToEncodedParams(
@@ -68,8 +65,12 @@
}
}
-export class DashboardViewModel extends Model<DashboardViewState> {
+export const dashboardViewModelToken = define<DashboardViewModel>(
+ 'dashboard-view-model'
+);
+
+export class DashboardViewModel extends Model<DashboardViewState | undefined> {
constructor() {
- super(DEFAULT_STATE);
+ super(undefined);
}
}
diff --git a/polygerrit-ui/app/models/views/diff.ts b/polygerrit-ui/app/models/views/diff.ts
index 68f416f..85fa081 100644
--- a/polygerrit-ui/app/models/views/diff.ts
+++ b/polygerrit-ui/app/models/views/diff.ts
@@ -12,12 +12,13 @@
import {GerritView} from '../../services/router/router-model';
import {UrlEncodedCommentId} from '../../types/common';
import {encodeURL, getPatchRangeExpression} from '../../utils/url-util';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
export interface DiffViewState extends ViewState {
view: GerritView.DIFF;
- changeNum?: NumericChangeId;
+ changeNum: NumericChangeId;
project?: RepoName;
commentId?: UrlEncodedCommentId;
path?: string;
@@ -28,10 +29,6 @@
commentLink?: boolean;
}
-const DEFAULT_STATE: DiffViewState = {
- view: GerritView.DIFF,
-};
-
export function createDiffUrl(state: Omit<DiffViewState, 'view'>): string {
let range = getPatchRangeExpression(state);
if (range.length) range = '/' + range;
@@ -58,8 +55,10 @@
}
}
-export class DiffViewModel extends Model<DiffViewState> {
+export const diffViewModelToken = define<DiffViewModel>('diff-view-model');
+
+export class DiffViewModel extends Model<DiffViewState | undefined> {
constructor() {
- super(DEFAULT_STATE);
+ super(undefined);
}
}
diff --git a/polygerrit-ui/app/models/views/documentation.ts b/polygerrit-ui/app/models/views/documentation.ts
index 4273b13..b564d64 100644
--- a/polygerrit-ui/app/models/views/documentation.ts
+++ b/polygerrit-ui/app/models/views/documentation.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {GerritView} from '../../services/router/router-model';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -12,12 +13,14 @@
filter?: string | null;
}
-const DEFAULT_STATE: DocumentationViewState = {
- view: GerritView.DOCUMENTATION_SEARCH,
-};
+export const documentationViewModelToken = define<DocumentationViewModel>(
+ 'documentation-view-model'
+);
-export class DocumentationViewModel extends Model<DocumentationViewState> {
+export class DocumentationViewModel extends Model<
+ DocumentationViewState | undefined
+> {
constructor() {
- super(DEFAULT_STATE);
+ super(undefined);
}
}
diff --git a/polygerrit-ui/app/models/views/edit.ts b/polygerrit-ui/app/models/views/edit.ts
index 102a7e0..d8f4770 100644
--- a/polygerrit-ui/app/models/views/edit.ts
+++ b/polygerrit-ui/app/models/views/edit.ts
@@ -11,22 +11,19 @@
} from '../../api/rest-api';
import {GerritView} from '../../services/router/router-model';
import {encodeURL, getPatchRangeExpression} from '../../utils/url-util';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
export interface EditViewState extends ViewState {
view: GerritView.EDIT;
- changeNum?: NumericChangeId;
- project?: RepoName;
- path?: string;
- patchNum?: RevisionPatchSetNum;
+ changeNum: NumericChangeId;
+ project: RepoName;
+ path: string;
+ patchNum: RevisionPatchSetNum;
lineNum?: number;
}
-const DEFAULT_STATE: EditViewState = {
- view: GerritView.EDIT,
-};
-
export function createEditUrl(state: Omit<EditViewState, 'view'>): string {
if (state.patchNum === undefined) {
state = {...state, patchNum: EDIT};
@@ -50,8 +47,10 @@
}
}
-export class EditViewModel extends Model<EditViewState> {
+export const editViewModelToken = define<EditViewModel>('edit-view-model');
+
+export class EditViewModel extends Model<EditViewState | undefined> {
constructor() {
- super(DEFAULT_STATE);
+ super(undefined);
}
}
diff --git a/polygerrit-ui/app/models/views/group.ts b/polygerrit-ui/app/models/views/group.ts
index bac8eb5..277bcff 100644
--- a/polygerrit-ui/app/models/views/group.ts
+++ b/polygerrit-ui/app/models/views/group.ts
@@ -6,6 +6,7 @@
import {GerritView} from '../../services/router/router-model';
import {GroupId} from '../../types/common';
import {encodeURL, getBaseUrl} from '../../utils/url-util';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -30,6 +31,8 @@
return getBaseUrl() + url;
}
+export const groupViewModelToken = define<GroupViewModel>('group-view-model');
+
export class GroupViewModel extends Model<GroupViewState | undefined> {
constructor() {
super(undefined);
diff --git a/polygerrit-ui/app/models/views/plugin.ts b/polygerrit-ui/app/models/views/plugin.ts
index 5b0e701..ac7e925 100644
--- a/polygerrit-ui/app/models/views/plugin.ts
+++ b/polygerrit-ui/app/models/views/plugin.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {GerritView} from '../../services/router/router-model';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -15,6 +16,9 @@
const DEFAULT_STATE: PluginViewState = {view: GerritView.PLUGIN_SCREEN};
+export const pluginViewModelToken =
+ define<PluginViewModel>('plugin-view-model');
+
export class PluginViewModel extends Model<PluginViewState> {
constructor() {
super(DEFAULT_STATE);
diff --git a/polygerrit-ui/app/models/views/repo.ts b/polygerrit-ui/app/models/views/repo.ts
index d7e7a73..02fd17d 100644
--- a/polygerrit-ui/app/models/views/repo.ts
+++ b/polygerrit-ui/app/models/views/repo.ts
@@ -6,6 +6,7 @@
import {GerritView} from '../../services/router/router-model';
import {RepoName} from '../../types/common';
import {encodeURL, getBaseUrl} from '../../utils/url-util';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -26,10 +27,6 @@
offset?: number | string;
}
-const DEFAULT_STATE: RepoViewState = {
- view: GerritView.REPO,
-};
-
export function createRepoUrl(state: Omit<RepoViewState, 'view'>) {
let url = `/admin/repos/${encodeURL(`${state.repo}`, true)}`;
if (state.detail === RepoDetailView.GENERAL) {
@@ -48,8 +45,10 @@
return getBaseUrl() + url;
}
-export class RepoViewModel extends Model<RepoViewState> {
+export const repoViewModelToken = define<RepoViewModel>('repo-view-model');
+
+export class RepoViewModel extends Model<RepoViewState | undefined> {
constructor() {
- super(DEFAULT_STATE);
+ super(undefined);
}
}
diff --git a/polygerrit-ui/app/models/views/search.ts b/polygerrit-ui/app/models/views/search.ts
index d932b39..58cb8f7 100644
--- a/polygerrit-ui/app/models/views/search.ts
+++ b/polygerrit-ui/app/models/views/search.ts
@@ -7,6 +7,7 @@
import {GerritView} from '../../services/router/router-model';
import {addQuotesWhen} from '../../utils/string-util';
import {encodeURL} from '../../utils/url-util';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -82,12 +83,11 @@
return '/q/' + operators.join('+') + offsetExpr;
}
-const DEFAULT_STATE: SearchViewState = {
- view: GerritView.SEARCH,
-};
+export const searchViewModelToken =
+ define<SearchViewModel>('search-view-model');
-export class SearchViewModel extends Model<SearchViewState> {
+export class SearchViewModel extends Model<SearchViewState | undefined> {
constructor() {
- super(DEFAULT_STATE);
+ super(undefined);
}
}
diff --git a/polygerrit-ui/app/models/views/settings.ts b/polygerrit-ui/app/models/views/settings.ts
index f2e67a4..c1a8c08 100644
--- a/polygerrit-ui/app/models/views/settings.ts
+++ b/polygerrit-ui/app/models/views/settings.ts
@@ -4,7 +4,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {GerritView} from '../../services/router/router-model';
+import {select} from '../../utils/observable-util';
import {getBaseUrl} from '../../utils/url-util';
+import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -13,14 +15,22 @@
emailToken?: string;
}
-const DEFAULT_STATE: SettingsViewState = {view: GerritView.SETTINGS};
-
export function createSettingsUrl() {
return getBaseUrl() + '/settings';
}
-export class SettingsViewModel extends Model<SettingsViewState> {
+export const settingsViewModelToken = define<SettingsViewModel>(
+ 'settings-view-model'
+);
+
+export class SettingsViewModel extends Model<SettingsViewState | undefined> {
constructor() {
- super(DEFAULT_STATE);
+ super(undefined);
+ }
+
+ public emailToken$ = select(this.state$, state => state?.emailToken);
+
+ clearToken() {
+ this.updateState({emailToken: undefined});
}
}
diff --git a/polygerrit-ui/app/services/app-context-init.ts b/polygerrit-ui/app/services/app-context-init.ts
index 2dca9a9..3a0079f 100644
--- a/polygerrit-ui/app/services/app-context-init.ts
+++ b/polygerrit-ui/app/services/app-context-init.ts
@@ -32,6 +32,31 @@
import {PluginsModel} from '../models/plugins/plugins-model';
import {HighlightService} from './highlight/highlight-service';
import {AccountsModel} from '../models/accounts-model/accounts-model';
+import {
+ DashboardViewModel,
+ dashboardViewModelToken,
+} from '../models/views/dashboard';
+import {
+ SettingsViewModel,
+ settingsViewModelToken,
+} from '../models/views/settings';
+import {GrRouter, routerToken} from '../elements/core/gr-router/gr-router';
+import {AdminViewModel, adminViewModelToken} from '../models/views/admin';
+import {
+ AgreementViewModel,
+ agreementViewModelToken,
+} from '../models/views/agreement';
+import {ChangeViewModel, changeViewModelToken} from '../models/views/change';
+import {DiffViewModel, diffViewModelToken} from '../models/views/diff';
+import {
+ DocumentationViewModel,
+ documentationViewModelToken,
+} from '../models/views/documentation';
+import {EditViewModel, editViewModelToken} from '../models/views/edit';
+import {GroupViewModel, groupViewModelToken} from '../models/views/group';
+import {PluginViewModel, pluginViewModelToken} from '../models/views/plugin';
+import {RepoViewModel, repoViewModelToken} from '../models/views/repo';
+import {SearchViewModel, searchViewModelToken} from '../models/views/search';
/**
* The AppContext lazy initializator for all services
@@ -85,6 +110,50 @@
const browserModel = new BrowserModel(appContext.userModel);
dependencies.set(browserModelToken, browserModel);
+ const adminViewModel = new AdminViewModel();
+ dependencies.set(adminViewModelToken, adminViewModel);
+ const agreementViewModel = new AgreementViewModel();
+ dependencies.set(agreementViewModelToken, agreementViewModel);
+ const changeViewModel = new ChangeViewModel();
+ dependencies.set(changeViewModelToken, changeViewModel);
+ const dashboardViewModel = new DashboardViewModel();
+ dependencies.set(dashboardViewModelToken, dashboardViewModel);
+ const diffViewModel = new DiffViewModel();
+ dependencies.set(diffViewModelToken, diffViewModel);
+ const documentationViewModel = new DocumentationViewModel();
+ dependencies.set(documentationViewModelToken, documentationViewModel);
+ const editViewModel = new EditViewModel();
+ dependencies.set(editViewModelToken, editViewModel);
+ const groupViewModel = new GroupViewModel();
+ dependencies.set(groupViewModelToken, groupViewModel);
+ const pluginViewModel = new PluginViewModel();
+ dependencies.set(pluginViewModelToken, pluginViewModel);
+ const repoViewModel = new RepoViewModel();
+ dependencies.set(repoViewModelToken, repoViewModel);
+ const searchViewModel = new SearchViewModel();
+ dependencies.set(searchViewModelToken, searchViewModel);
+ const settingsViewModel = new SettingsViewModel();
+ dependencies.set(settingsViewModelToken, settingsViewModel);
+
+ const router = new GrRouter(
+ appContext.reportingService,
+ appContext.routerModel,
+ appContext.restApiService,
+ adminViewModel,
+ agreementViewModel,
+ changeViewModel,
+ dashboardViewModel,
+ diffViewModel,
+ documentationViewModel,
+ editViewModel,
+ groupViewModel,
+ pluginViewModel,
+ repoViewModel,
+ searchViewModel,
+ settingsViewModel
+ );
+ dependencies.set(routerToken, router);
+
const changeModel = new ChangeModel(
appContext.routerModel,
appContext.restApiService,
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
index 106f1f3..33b9176 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -16,8 +16,7 @@
LifeCycle,
Timing,
} from '../../constants/reporting';
-import {getCLS, getFID, getLCP} from 'web-vitals';
-import {Metric} from 'web-vitals/src/types';
+import {getCLS, getFID, getLCP, Metric} from 'web-vitals';
// Latency reporting constants.
diff --git a/polygerrit-ui/app/services/router/router-model.ts b/polygerrit-ui/app/services/router/router-model.ts
index 8b3b0b8..72a3344 100644
--- a/polygerrit-ui/app/services/router/router-model.ts
+++ b/polygerrit-ui/app/services/router/router-model.ts
@@ -64,8 +64,6 @@
);
}
- finalize() {}
-
// Private but used in tests
setState(state: RouterState) {
this.subject$.next(state);
diff --git a/polygerrit-ui/app/test/test-app-context-init.ts b/polygerrit-ui/app/test/test-app-context-init.ts
index 15857c8..eda4e58 100644
--- a/polygerrit-ui/app/test/test-app-context-init.ts
+++ b/polygerrit-ui/app/test/test-app-context-init.ts
@@ -37,6 +37,31 @@
AccountsModel,
accountsModelToken,
} from '../models/accounts-model/accounts-model';
+import {
+ DashboardViewModel,
+ dashboardViewModelToken,
+} from '../models/views/dashboard';
+import {
+ SettingsViewModel,
+ settingsViewModelToken,
+} from '../models/views/settings';
+import {GrRouter, routerToken} from '../elements/core/gr-router/gr-router';
+import {AdminViewModel, adminViewModelToken} from '../models/views/admin';
+import {
+ AgreementViewModel,
+ agreementViewModelToken,
+} from '../models/views/agreement';
+import {ChangeViewModel, changeViewModelToken} from '../models/views/change';
+import {DiffViewModel, diffViewModelToken} from '../models/views/diff';
+import {
+ DocumentationViewModel,
+ documentationViewModelToken,
+} from '../models/views/documentation';
+import {EditViewModel, editViewModelToken} from '../models/views/edit';
+import {GroupViewModel, groupViewModelToken} from '../models/views/group';
+import {PluginViewModel, pluginViewModelToken} from '../models/views/plugin';
+import {RepoViewModel, repoViewModelToken} from '../models/views/repo';
+import {SearchViewModel, searchViewModelToken} from '../models/views/search';
export function createTestAppContext(): AppContext & Finalizable {
const appRegistry: Registry<AppContext> = {
@@ -92,6 +117,51 @@
const browserModel = () => new BrowserModel(appContext.userModel);
dependencies.set(browserModelToken, browserModel);
+ const adminViewModelCreator = () => new AdminViewModel();
+ dependencies.set(adminViewModelToken, adminViewModelCreator);
+ const agreementViewModelCreator = () => new AgreementViewModel();
+ dependencies.set(agreementViewModelToken, agreementViewModelCreator);
+ const changeViewModelCreator = () => new ChangeViewModel();
+ dependencies.set(changeViewModelToken, changeViewModelCreator);
+ const dashboardViewModelCreator = () => new DashboardViewModel();
+ dependencies.set(dashboardViewModelToken, dashboardViewModelCreator);
+ const diffViewModelCreator = () => new DiffViewModel();
+ dependencies.set(diffViewModelToken, diffViewModelCreator);
+ const documentationViewModelCreator = () => new DocumentationViewModel();
+ dependencies.set(documentationViewModelToken, documentationViewModelCreator);
+ const editViewModelCreator = () => new EditViewModel();
+ dependencies.set(editViewModelToken, editViewModelCreator);
+ const groupViewModelCreator = () => new GroupViewModel();
+ dependencies.set(groupViewModelToken, groupViewModelCreator);
+ const pluginViewModelCreator = () => new PluginViewModel();
+ dependencies.set(pluginViewModelToken, pluginViewModelCreator);
+ const repoViewModelCreator = () => new RepoViewModel();
+ dependencies.set(repoViewModelToken, repoViewModelCreator);
+ const searchViewModelCreator = () => new SearchViewModel();
+ dependencies.set(searchViewModelToken, searchViewModelCreator);
+ const settingsViewModelCreator = () => new SettingsViewModel();
+ dependencies.set(settingsViewModelToken, settingsViewModelCreator);
+
+ const routerCreator = () =>
+ new GrRouter(
+ appContext.reportingService,
+ appContext.routerModel,
+ appContext.restApiService,
+ resolver(adminViewModelToken),
+ resolver(agreementViewModelToken),
+ resolver(changeViewModelToken),
+ resolver(dashboardViewModelToken),
+ resolver(diffViewModelToken),
+ resolver(documentationViewModelToken),
+ resolver(editViewModelToken),
+ resolver(groupViewModelToken),
+ resolver(pluginViewModelToken),
+ resolver(repoViewModelToken),
+ resolver(searchViewModelToken),
+ resolver(settingsViewModelToken)
+ );
+ dependencies.set(routerToken, routerCreator);
+
const changeModelCreator = () =>
new ChangeModel(
appContext.routerModel,
diff --git a/polygerrit-ui/app/utils/account-util.ts b/polygerrit-ui/app/utils/account-util.ts
index f0bfda9..0e2317f 100644
--- a/polygerrit-ui/app/utils/account-util.ts
+++ b/polygerrit-ui/app/utils/account-util.ts
@@ -185,11 +185,11 @@
if (!accounts) return [];
const accountSuggestions = [];
for (const account of accounts) {
- let nameAndEmail;
+ let nameAndEmail: string;
if (account.email !== undefined) {
- nameAndEmail = `${account.name} <${account.email}>`;
+ nameAndEmail = `${account.name ?? ''} <${account.email}>`;
} else {
- nameAndEmail = account.name;
+ nameAndEmail = account.name ?? '';
}
accountSuggestions.push({
name: nameAndEmail,