Merge "Extract listing of GPG keys into a util class, so it can be reused."
diff --git a/polygerrit-ui/app/constants/reporting.ts b/polygerrit-ui/app/constants/reporting.ts
index 18ee992..ef2f63e 100644
--- a/polygerrit-ui/app/constants/reporting.ts
+++ b/polygerrit-ui/app/constants/reporting.ts
@@ -10,6 +10,7 @@
STARTED_AS_GUEST = 'Started as guest',
VISIBILILITY_HIDDEN = 'Visibility changed to hidden',
VISIBILILITY_VISIBLE = 'Visibility changed to visible',
+ FOCUS = 'Focus changed',
EXTENSION_DETECTED = 'Extension detected',
PLUGINS_INSTALLED = 'Plugins installed',
PLUGINS_FAILED = 'Some plugins failed to load',
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index 4eee3cb..e0c28bc 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -42,7 +42,6 @@
BranchName,
ChangeActionDialog,
ChangeInfo,
- ChangeViewChangeInfo,
CherryPickInput,
CommitId,
InheritedBooleanInfo,
@@ -100,7 +99,7 @@
import {changeModelToken} from '../../../models/change/change-model';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, css, html, nothing} from 'lit';
-import {customElement, property, query, state} from 'lit/decorators.js';
+import {customElement, query, state} from 'lit/decorators.js';
import {ifDefined} from 'lit/directives/if-defined.js';
import {assertIsDefined, queryAll, uuid} from '../../../utils/common-util';
import {Interaction} from '../../../constants/reporting';
@@ -113,6 +112,9 @@
import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {modalStyles} from '../../../styles/gr-modal-styles';
import {subscribe} from '../../lit/subscription-controller';
+import {userModelToken} from '../../../models/user/user-model';
+import {ParsedChangeInfo} from '../../../types/types';
+import {configModelToken} from '../../../models/config/config-model';
const ERR_BRANCH_EMPTY = 'The destination branch can’t be empty.';
const ERR_COMMIT_EMPTY = 'The commit message can’t be empty.';
@@ -377,76 +379,54 @@
RevisionActions = RevisionActions;
- @property({type: Object})
- change?: ChangeViewChangeInfo;
+ @state() change?: ParsedChangeInfo;
- @state()
- actions: ActionNameToActionInfoMap = {};
+ @state() actions: ActionNameToActionInfoMap = {};
- @property({type: Array})
- primaryActionKeys: PrimaryActionKey[] = [
+ @state() primaryActionKeys: PrimaryActionKey[] = [
ChangeActions.READY,
RevisionActions.SUBMIT,
];
- @property({type: Boolean})
- disableEdit = false;
-
- // private but used in test
@state() _hideQuickApproveAction = false;
- @property({type: Object})
- account?: AccountInfo;
+ @state() account?: AccountInfo;
- @property({type: String})
- changeNum?: NumericChangeId;
+ @state() changeNum?: NumericChangeId;
- @property({type: String})
- changeStatus?: ChangeStatus;
+ @state() changeStatus?: ChangeStatus;
- @property({type: String})
- commitNum?: CommitId;
+ @state() commitNum?: CommitId;
@state() latestPatchNum?: PatchSetNumber;
- @property({type: String})
- commitMessage = '';
+ @state() commitMessage = '';
- @property({type: Object})
- revisionActions: ActionNameToActionInfoMap = {};
+ @state() revisionActions: ActionNameToActionInfoMap = {};
- @state() private revisionSubmitAction?: ActionInfo | null;
+ @state() revisionSubmitAction?: ActionInfo | null;
- // used as a proprty type so cannot be private
@state() revisionRebaseAction?: ActionInfo | null;
- @property({type: String})
- privateByDefault?: InheritedBooleanInfo;
+ @state() privateByDefault?: InheritedBooleanInfo;
- // private but used in test
@state() loading = true;
- // private but used in test
@state() actionLoadingMessage = '';
- @state() private inProgressActionKeys = new Set<string>();
+ @state() inProgressActionKeys = new Set<string>();
- // _computeAllActions always returns an array
- // private but used in test
@state() allActionValues: UIActionInfo[] = [];
- // private but used in test
@state() topLevelActions?: UIActionInfo[];
- // private but used in test
@state() topLevelPrimaryActions?: UIActionInfo[];
- // private but used in test
@state() topLevelSecondaryActions?: UIActionInfo[];
- @state() private menuActions?: MenuAction[];
+ @state() menuActions?: MenuAction[];
- @state() private overflowActions: OverflowAction[] = [
+ @state() overflowActions: OverflowAction[] = [
{
type: ActionType.CHANGE,
key: ChangeActions.WIP,
@@ -493,29 +473,21 @@
},
];
- @state() private actionPriorityOverrides: ActionPriorityOverride[] = [];
+ @state() actionPriorityOverrides: ActionPriorityOverride[] = [];
- @state() private additionalActions: UIActionInfo[] = [];
+ @state() additionalActions: UIActionInfo[] = [];
- // private but used in test
@state() hiddenActions: string[] = [];
- // private but used in test
@state() disabledMenuActions: string[] = [];
- // private but used in test
- @state()
- editPatchsetLoaded = false;
+ @state() editPatchsetLoaded = false;
- @property({type: Boolean})
- editMode = false;
+ @state() editMode = false;
- // private but used in test
- @state()
- editBasedOnCurrentPatchSet = true;
+ @state() editBasedOnCurrentPatchSet = true;
- @property({type: Boolean})
- loggedIn = false;
+ @state() loggedIn = false;
private readonly restApiService = getAppContext().restApiService;
@@ -523,6 +495,10 @@
private readonly getPluginLoader = resolve(this, pluginLoaderToken);
+ private readonly getUserModel = resolve(this, userModelToken);
+
+ private readonly getConfigModel = resolve(this, configModelToken);
+
private readonly getChangeModel = resolve(this, changeModelToken);
private readonly getStorage = resolve(this, storageServiceToken);
@@ -546,6 +522,51 @@
() => this.getChangeModel().patchNum$,
x => (this.editPatchsetLoaded = x === 'edit')
);
+ subscribe(
+ this,
+ () => this.getChangeModel().changeNum$,
+ x => (this.changeNum = x)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().change$,
+ x => (this.change = x)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().status$,
+ x => (this.changeStatus = x)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().editMode$,
+ x => (this.editMode = x)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().revision$,
+ rev => (this.commitNum = rev?.commit?.commit)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().latestRevision$,
+ rev => (this.commitMessage = rev?.commit?.message ?? '')
+ );
+ subscribe(
+ this,
+ () => this.getUserModel().account$,
+ x => (this.account = x)
+ );
+ subscribe(
+ this,
+ () => this.getUserModel().loggedIn$,
+ x => (this.loggedIn = x)
+ );
+ subscribe(
+ this,
+ () => this.getConfigModel().repoConfig$,
+ config => (this.privateByDefault = config?.private_by_default)
+ );
}
override connectedCallback() {
@@ -865,7 +886,7 @@
this.revisionActions = revisionActions;
this.sendShowRevisionActions({
- change,
+ change: change as ChangeInfo,
revisionActions,
});
this.handleLoadingComplete();
@@ -1031,18 +1052,7 @@
}
private editStatusChanged() {
- // Hide change edits if not logged in
- if (this.change === undefined || !this.loggedIn) {
- return;
- }
- if (this.disableEdit) {
- delete this.actions.rebaseEdit;
- delete this.actions.publishEdit;
- delete this.actions.deleteEdit;
- delete this.actions.stopEdit;
- delete this.actions.edit;
- return;
- }
+ if (!this.change || !this.loggedIn) return;
if (this.editPatchsetLoaded) {
// Only show actions that mutate an edit if an actual edit patch set
// is loaded.
@@ -1121,7 +1131,7 @@
if (!this.change || !this.change.labels || !this.change.permitted_labels) {
return null;
}
- if (this.change && this.change.status === ChangeStatus.MERGED) {
+ if (this.change?.status === ChangeStatus.MERGED) {
return null;
}
let result;
@@ -1323,18 +1333,18 @@
// private but used in test
canSubmitChange() {
- if (!this.change) {
- return false;
- }
+ if (!this.change) return false;
+ const change = this.change as ChangeInfo;
+ const revision = this.getRevision(change, this.latestPatchNum);
return this.getPluginLoader().jsApiService.canSubmitChange(
- this.change,
- this.getRevision(this.change, this.latestPatchNum)
+ change,
+ revision
);
}
// private but used in test
- getRevision(change: ChangeViewChangeInfo, patchNum?: PatchSetNumber) {
- for (const rev of Object.values(change.revisions)) {
+ getRevision(change: ChangeInfo, patchNum?: PatchSetNumber) {
+ for (const rev of Object.values(change.revisions ?? {})) {
if (rev._number === patchNum) {
return rev;
}
@@ -1805,7 +1815,7 @@
if (dialog.init) dialog.init();
dialog.hidden = false;
assertIsDefined(this.actionsModal, 'actionsModal');
- this.actionsModal.showModal();
+ if (this.actionsModal.isConnected) this.actionsModal.showModal();
whenVisible(dialog, () => {
if (dialog.resetFocus) {
dialog.resetFocus();
@@ -1818,7 +1828,7 @@
// private but used in test
setReviewOnRevert(newChangeId: NumericChangeId) {
const review = this.getPluginLoader().jsApiService.getReviewPostRevert(
- this.change
+ this.change as ChangeInfo
);
if (!review) {
return Promise.resolve(undefined);
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
index 946191b..b628cc1 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
@@ -129,6 +129,7 @@
element = await fixture<GrChangeActions>(html`
<gr-change-actions></gr-change-actions>
`);
+ element.changeStatus = ChangeStatus.NEW;
element.change = {
...createChangeViewChange(),
actions: {
@@ -155,7 +156,7 @@
await element.reload();
});
- test('render', () => {
+ test('render', async () => {
assert.shadowDom.equal(
element,
/* HTML */ `
@@ -200,6 +201,24 @@
Rebase
</gr-button>
</gr-tooltip-content>
+ <gr-tooltip-content
+ has-tooltip=""
+ position-below=""
+ title="Edit this change"
+ >
+ <gr-button
+ aria-disabled="false"
+ class="edit"
+ data-action-key="edit"
+ data-label="Edit"
+ link=""
+ role="button"
+ tabindex="0"
+ >
+ <gr-icon filled="" icon="edit"> </gr-icon>
+ Edit
+ </gr-button>
+ </gr-tooltip-content>
</section>
<gr-button
aria-disabled="false"
@@ -705,29 +724,6 @@
});
suite('change edits', () => {
- test('disableEdit', async () => {
- element.editMode = false;
- element.editBasedOnCurrentPatchSet = false;
- element.change = {
- ...createChangeViewChange(),
- status: ChangeStatus.NEW,
- };
- element.disableEdit = true;
- await element.updateComplete;
-
- assert.isNotOk(
- query(element, 'gr-button[data-action-key="publishEdit"]')
- );
- assert.isNotOk(
- query(element, 'gr-button[data-action-key="rebaseEdit"]')
- );
- assert.isNotOk(
- query(element, 'gr-button[data-action-key="deleteEdit"]')
- );
- assert.isNotOk(query(element, 'gr-button[data-action-key="edit"]'));
- assert.isNotOk(query(element, 'gr-button[data-action-key="stopEdit"]'));
- });
-
test('shows confirm dialog for delete edit', async () => {
element.loggedIn = true;
element.editMode = true;
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index b7851a6..1cb25ea 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -82,6 +82,12 @@
import {createChangeUrl} from '../../../models/views/change';
import {getChangeWeblinks} from '../../../utils/weblink-util';
import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
+import {subscribe} from '../../lit/subscription-controller';
+import {userModelToken} from '../../../models/user/user-model';
+import {resolve} from '../../../models/dependency';
+import {configModelToken} from '../../../models/config/config-model';
+import {changeModelToken} from '../../../models/change/change-model';
+import {relatedChangesModelToken} from '../../../models/change/related-changes-model';
const HASHTAG_ADD_MESSAGE = 'Add Hashtag';
@@ -118,54 +124,90 @@
export class GrChangeMetadata extends LitElement {
@query('#webLinks') webLinks?: HTMLElement;
- @property({type: Object}) change?: ParsedChangeInfo;
-
- @property({type: Object}) revertedChange?: ChangeInfo;
-
- @property({type: Object}) account?: AccountDetailInfo;
-
- @property({type: Object}) revision?: RevisionInfo | EditRevisionInfo;
-
- // TODO: Just use `revision.commit` instead.
- @property({type: Object}) commitInfo?: CommitInfoWithRequiredCommit;
-
- @property({type: Object}) serverConfig?: ServerInfo;
-
+ // TODO: Convert to @state. That requires the change model to keep track of
+ // current revision actions. Then we can also get rid of the
+ // `revision-actions-changed` event.
@property({type: Boolean}) parentIsCurrent?: boolean;
- @property({type: Object}) repoConfig?: ConfigInfo;
+ @state() change?: ParsedChangeInfo;
- // private but used in test
+ @state() revertedChange?: ChangeInfo;
+
+ @state() account?: AccountDetailInfo;
+
+ @state() revision?: RevisionInfo | EditRevisionInfo;
+
+ @state() serverConfig?: ServerInfo;
+
+ @state() repoConfig?: ConfigInfo;
+
@state() mutable = false;
- @state() private readonly notCurrentMessage = NOT_CURRENT_MESSAGE;
+ @state() readonly notCurrentMessage = NOT_CURRENT_MESSAGE;
- // private but used in test
@state() topicReadOnly = true;
- // private but used in test
@state() hashtagReadOnly = true;
- @state() private pushCertificateValidation?: PushCertificateValidationInfo;
+ @state() pushCertificateValidation?: PushCertificateValidationInfo;
- // private but used in test
@state() settingTopic = false;
- // private but used in test
@state() currentParents: ParentCommitInfo[] = [];
- @state() private showAllSections = false;
+ @state() showAllSections = false;
- @state() private queryTopic?: AutocompleteQuery;
+ @state() queryTopic?: AutocompleteQuery;
- @state() private queryHashtag?: AutocompleteQuery;
+ @state() queryHashtag?: AutocompleteQuery;
private restApiService = getAppContext().restApiService;
private readonly reporting = getAppContext().reportingService;
+ private readonly getUserModel = resolve(this, userModelToken);
+
+ private readonly getConfigModel = resolve(this, configModelToken);
+
+ private readonly getChangeModel = resolve(this, changeModelToken);
+
+ private readonly getRelatedChangesModel = resolve(
+ this,
+ relatedChangesModelToken
+ );
+
constructor() {
super();
+ subscribe(
+ this,
+ () => this.getConfigModel().serverConfig$,
+ serverConfig => (this.serverConfig = serverConfig)
+ );
+ subscribe(
+ this,
+ () => this.getConfigModel().repoConfig$,
+ repoConfig => (this.repoConfig = repoConfig)
+ );
+ subscribe(
+ this,
+ () => this.getUserModel().account$,
+ account => (this.account = account)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().change$,
+ change => (this.change = change)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().revision$,
+ revision => (this.revision = revision)
+ );
+ subscribe(
+ this,
+ () => this.getRelatedChangesModel().revertingChange$,
+ revertingChange => (this.revertedChange = revertingChange)
+ );
this.queryTopic = (input: string) => this.getTopicSuggestions(input);
this.queryHashtag = (input: string) => this.getHashtagSuggestions(input);
}
@@ -735,7 +777,10 @@
// private but used in test
computeWebLinks(): WebLinkInfo[] {
- return getChangeWeblinks(this.commitInfo?.web_links, this.serverConfig);
+ return getChangeWeblinks(
+ this.revision?.commit?.web_links,
+ this.serverConfig
+ );
}
private computeStrategy() {
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 c46fe24..e0e1364 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
@@ -9,7 +9,6 @@
import {ChangeRole, GrChangeMetadata} from './gr-change-metadata';
import {
createServerInfo,
- createUserConfig,
createParsedChange,
createAccountWithId,
createCommitInfoWithRequiredCommit,
@@ -61,18 +60,9 @@
let element: GrChangeMetadata;
setup(async () => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(false));
- stubRestApi('getConfig').returns(
- Promise.resolve({
- ...createServerInfo(),
- user: {
- ...createUserConfig(),
- anonymouscowardname: 'test coward name',
- },
- })
- );
element = await fixture(html`<gr-change-metadata></gr-change-metadata>`);
element.change = createParsedChange();
+ element.account = undefined;
await element.updateComplete;
});
@@ -251,16 +241,22 @@
});
test('weblinks hidden when no weblinks', async () => {
- element.commitInfo = createCommitInfoWithRequiredCommit();
+ element.revision = {
+ ...createRevision(),
+ commit: createCommitInfoWithRequiredCommit(),
+ };
element.serverConfig = createServerInfo();
await element.updateComplete;
assert.isNull(element.webLinks);
});
test('weblinks hidden when only gitiles weblink', async () => {
- element.commitInfo = {
- ...createCommitInfoWithRequiredCommit(),
- web_links: [{...createWebLinkInfo(), name: 'gitiles', url: '#'}],
+ element.revision = {
+ ...createRevision(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(),
+ web_links: [{...createWebLinkInfo(), name: 'gitiles', url: '#'}],
+ },
};
element.serverConfig = createServerInfo();
await element.updateComplete;
@@ -270,9 +266,12 @@
test('weblinks hidden when sole weblink is set as primary', async () => {
const browser = 'browser';
- element.commitInfo = {
- ...createCommitInfoWithRequiredCommit(),
- web_links: [{...createWebLinkInfo(), name: browser, url: '#'}],
+ element.revision = {
+ ...createRevision(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(),
+ web_links: [{...createWebLinkInfo(), name: browser, url: '#'}],
+ },
};
element.serverConfig = {
...createServerInfo(),
@@ -286,9 +285,12 @@
});
test('weblinks are visible when other weblinks', async () => {
- element.commitInfo = {
- ...createCommitInfoWithRequiredCommit(),
- web_links: [{...createWebLinkInfo(), name: 'test', url: '#'}],
+ element.revision = {
+ ...createRevision(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(),
+ web_links: [{...createWebLinkInfo(), name: 'test', url: '#'}],
+ },
};
await element.updateComplete;
const webLinks = element.webLinks!;
@@ -297,12 +299,15 @@
});
test('weblinks are visible when gitiles and other weblinks', async () => {
- element.commitInfo = {
- ...createCommitInfoWithRequiredCommit(),
- web_links: [
- {...createWebLinkInfo(), name: 'test', url: '#'},
- {...createWebLinkInfo(), name: 'gitiles', url: '#'},
- ],
+ element.revision = {
+ ...createRevision(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(),
+ web_links: [
+ {...createWebLinkInfo(), name: 'test', url: '#'},
+ {...createWebLinkInfo(), name: 'gitiles', url: '#'},
+ ],
+ },
};
await element.updateComplete;
const webLinks = element.webLinks!;
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 e1ae35c..2e1a8b9 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
@@ -335,10 +335,7 @@
@state()
private updateCheckTimerHandle?: number | null;
- // Private but used in tests.
- getEditMode(): boolean {
- return !!this.viewState?.edit || this.patchNum === EDIT;
- }
+ @state() editMode = false;
isSubmitEnabled(): boolean {
return !!(
@@ -664,6 +661,11 @@
);
subscribe(
this,
+ () => this.getChangeModel().editMode$,
+ editMode => (this.editMode = editMode)
+ );
+ subscribe(
+ this,
() => this.getChangeModel().patchNum$,
patchNum => (this.patchNum = patchNum)
);
@@ -1134,23 +1136,16 @@
${this.renderTabHeaders()} ${this.renderTabContent()}
${this.renderChangeLog()}
</div>
- <gr-apply-fix-dialog
- id="applyFixDialog"
- .change=${this.change}
- .changeNum=${this.changeNum}
- ></gr-apply-fix-dialog>
+ <gr-apply-fix-dialog id="applyFixDialog"></gr-apply-fix-dialog>
<dialog id="downloadModal" tabindex="-1">
<gr-download-dialog
id="downloadDialog"
- .change=${this.change}
- .config=${this.serverConfig?.download}
@close=${this.handleDownloadDialogClose}
></gr-download-dialog>
</dialog>
<dialog id="includedInModal" tabindex="-1">
<gr-included-in-dialog
id="includedInDialog"
- .changeNum=${this.changeNum}
@close=${this.handleIncludedInDialogClose}
></gr-included-in-dialog>
</dialog>
@@ -1287,27 +1282,18 @@
}
private renderCommitActions() {
- return html` <div class="commitActions">
- <!-- always show gr-change-actions regardless if logged in or not -->
- <gr-change-actions
- id="actions"
- .change=${this.change}
- .disableEdit=${false}
- .account=${this.account}
- .changeNum=${this.changeNum}
- .changeStatus=${this.change?.status}
- .commitNum=${this.revision?.commit?.commit}
- .commitMessage=${this.latestCommitMessage}
- .editMode=${this.getEditMode()}
- .privateByDefault=${this.projectConfig?.private_by_default}
- .loggedIn=${this.loggedIn}
- @edit-tap=${() => this.handleEditTap()}
- @stop-edit-tap=${() => this.handleStopEditTap()}
- @download-tap=${() => this.handleOpenDownloadDialog()}
- @included-tap=${() => this.handleOpenIncludedInDialog()}
- @revision-actions-changed=${this.handleRevisionActionsChanged}
- ></gr-change-actions>
- </div>`;
+ return html`
+ <div class="commitActions">
+ <gr-change-actions
+ id="actions"
+ @edit-tap=${() => this.handleEditTap()}
+ @stop-edit-tap=${() => this.handleStopEditTap()}
+ @download-tap=${() => this.handleOpenDownloadDialog()}
+ @included-tap=${() => this.handleOpenIncludedInDialog()}
+ @revision-actions-changed=${this.handleRevisionActionsChanged}
+ ></gr-change-actions>
+ </div>
+ `;
}
private renderChangeInfo() {
@@ -1315,20 +1301,13 @@
this.loggedIn,
this.editingCommitMessage,
this.change,
- this.getEditMode()
+ this.editMode
);
return html` <div class="changeInfo">
<div class="changeInfo-column changeMetadata">
<gr-change-metadata
id="metadata"
- .change=${this.change}
- .revertedChange=${this.revertingChange}
- .account=${this.account}
- .revision=${this.revision}
- .commitInfo=${this.revision?.commit}
- .serverConfig=${this.serverConfig}
.parentIsCurrent=${this.isParentCurrent()}
- .repoConfig=${this.projectConfig}
@show-reply-dialog=${this.handleShowReplyDialog}
>
</gr-change-metadata>
@@ -1461,7 +1440,7 @@
.changeNum=${this.changeNum}
.commitInfo=${this.revision?.commit}
.changeUrl=${this.computeChangeUrl()}
- .editMode=${this.getEditMode()}
+ .editMode=${this.editMode}
.loggedIn=${this.loggedIn}
.shownFileCount=${this.shownFileCount}
.filesExpanded=${this.fileList?.filesExpanded}
@@ -1475,7 +1454,7 @@
id="fileList"
.change=${this.change}
.changeNum=${this.changeNum}
- .editMode=${this.getEditMode()}
+ .editMode=${this.editMode}
@files-shown-changed=${(e: CustomEvent<{length: number}>) => {
this.shownFileCount = e.detail.length;
}}
@@ -1957,7 +1936,7 @@
change: this.change,
patchNum: this.patchNum,
basePatchNum: this.basePatchNum,
- edit: this.getEditMode(),
+ edit: this.editMode,
messageHash: hash,
});
history.replaceState(null, '', url);
@@ -2405,7 +2384,7 @@
// Private but used in tests.
computeHeaderClass() {
const classes = ['header'];
- if (this.getEditMode()) {
+ if (this.editMode) {
classes.push('editMode');
}
return classes.join(' ');
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 5331558..ae60449 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
@@ -73,7 +73,7 @@
import {Modifier} from '../../../utils/dom-util';
import {GrButton} from '../../shared/gr-button/gr-button';
import {GrCopyLinks} from '../gr-copy-links/gr-copy-links';
-import {ChangeChildView, ChangeViewState} from '../../../models/views/change';
+import {ChangeChildView} from '../../../models/views/change';
import {rootUrl} from '../../../utils/url-util';
import {testResolver} from '../../../test/common-test-setup';
import {UserModel, userModelToken} from '../../../models/user/user-model';
@@ -1318,10 +1318,9 @@
});
});
- test('header class computation', () => {
+ test('header class computation', async () => {
assert.equal(element.computeHeaderClass(), 'header');
- assertIsDefined(element.viewState);
- element.viewState.edit = true;
+ element.editMode = true;
assert.equal(element.computeHeaderClass(), 'header editMode');
});
@@ -1342,38 +1341,6 @@
assert.equal(scrollStub.lastCall.args[0], 'TEST');
});
- test('computeEditMode', async () => {
- const callCompute = async (viewState: ChangeViewState) => {
- element.viewState = viewState;
- element.patchNum = viewState.patchNum;
- element.basePatchNum = viewState.basePatchNum ?? PARENT;
- await element.updateComplete;
- return element.getEditMode();
- };
- assert.isTrue(
- await callCompute({
- ...createChangeViewState(),
- edit: true,
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- })
- );
- assert.isFalse(
- await callCompute({
- ...createChangeViewState(),
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- })
- );
- assert.isTrue(
- await callCompute({
- ...createChangeViewState(),
- basePatchNum: 1 as BasePatchSetNum,
- patchNum: EDIT,
- })
- );
- });
-
test('file-action-tap handling', async () => {
element.patchNum = 1 as RevisionPatchSetNum;
element.change = {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
index fedc377..e421c9c 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
@@ -15,6 +15,7 @@
import {resolve} from '../../../models/dependency';
import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {createSearchUrl} from '../../../models/views/search';
+import {ParsedChangeInfo} from '../../../types/types';
const ERR_COMMIT_NOT_FOUND = 'Unable to find the commit hash of this change.';
const INSERT_REASON_STRING = '<INSERT REASONING HERE>';
@@ -170,15 +171,23 @@
return this.revertType === RevertType.REVERT_SUBMISSION;
}
- modifyRevertMsg(change: ChangeInfo, commitMessage: string, message: string) {
+ modifyRevertMsg(
+ change: ParsedChangeInfo,
+ commitMessage: string,
+ message: string
+ ) {
return this.getPluginLoader().jsApiService.modifyRevertMsg(
- change,
+ change as ChangeInfo,
message,
commitMessage
);
}
- populate(change: ChangeInfo, commitMessage: string, changesCount: number) {
+ populate(
+ change: ParsedChangeInfo,
+ commitMessage: string,
+ changesCount: number
+ ) {
this.changesCount = changesCount;
// The option to revert a single change is always available
this.populateRevertSingleChangeMessage(
@@ -190,7 +199,7 @@
}
populateRevertSingleChangeMessage(
- change: ChangeInfo,
+ change: ParsedChangeInfo,
commitMessage: string,
commitHash?: CommitId
) {
@@ -215,18 +224,21 @@
}
private modifyRevertSubmissionMsg(
- change: ChangeInfo,
+ change: ParsedChangeInfo,
msg: string,
commitMessage: string
) {
return this.getPluginLoader().jsApiService.modifyRevertSubmissionMsg(
- change,
+ change as ChangeInfo,
msg,
commitMessage
);
}
- populateRevertSubmissionMessage(change: ChangeInfo, commitMessage: string) {
+ populateRevertSubmissionMessage(
+ change: ParsedChangeInfo,
+ commitMessage: string
+ ) {
// Follow the same convention of the revert
const commitHash = change.current_revision;
if (!commitHash) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts
index 904285f..8d71e15 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts
@@ -5,7 +5,7 @@
*/
import {fixture, html, assert} from '@open-wc/testing';
import '../../../test/common-test-setup';
-import {createChange} from '../../../test/test-data-generators';
+import {createParsedChange} from '../../../test/test-data-generators';
import {ChangeSubmissionId, CommitId} from '../../../types/common';
import './gr-confirm-revert-dialog';
import {GrConfirmRevertDialog} from './gr-confirm-revert-dialog';
@@ -48,7 +48,7 @@
const alertStub = sinon.stub();
element.addEventListener('show-alert', alertStub);
element.populateRevertSingleChangeMessage(
- createChange(),
+ createParsedChange(),
'not a commitHash in sight',
undefined
);
@@ -58,7 +58,7 @@
test('single line', () => {
assert.isNotOk(element.message);
element.populateRevertSingleChangeMessage(
- createChange(),
+ createParsedChange(),
'one line commit\n\nChange-Id: abcdefg\n',
'abcd123' as CommitId
);
@@ -72,7 +72,7 @@
test('multi line', () => {
assert.isNotOk(element.message);
element.populateRevertSingleChangeMessage(
- createChange(),
+ createParsedChange(),
'many lines\ncommit\n\nmessage\n\nChange-Id: abcdefg\n',
'abcd123' as CommitId
);
@@ -86,7 +86,7 @@
test('issue above change id', () => {
assert.isNotOk(element.message);
element.populateRevertSingleChangeMessage(
- createChange(),
+ createParsedChange(),
'much lines\nvery\n\ncommit\n\nBug: Issue 42\nChange-Id: abcdefg\n',
'abcd123' as CommitId
);
@@ -100,7 +100,7 @@
test('revert a revert', () => {
assert.isNotOk(element.message);
element.populateRevertSingleChangeMessage(
- createChange(),
+ createParsedChange(),
'Revert "one line commit"\n\nChange-Id: abcdefg\n',
'abcd123' as CommitId
);
@@ -115,7 +115,7 @@
element.changesCount = 3;
element.populateRevertSubmissionMessage(
{
- ...createChange(),
+ ...createParsedChange(),
submission_id: '5545' as ChangeSubmissionId,
current_revision: 'abcd123' as CommitId,
},
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
index 11dc890..e1808bf 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
@@ -5,7 +5,7 @@
*/
import '../../shared/gr-download-commands/gr-download-commands';
import {changeBaseURL, getRevisionKey} from '../../../utils/change-util';
-import {ChangeInfo, DownloadInfo, PatchSetNum} from '../../../types/common';
+import {DownloadInfo, PatchSetNum} from '../../../types/common';
import {GrDownloadCommands} from '../../shared/gr-download-commands/gr-download-commands';
import {GrButton} from '../../shared/gr-button/gr-button';
import {copyToClipbard, hasOwnProperty} from '../../../utils/common-util';
@@ -13,13 +13,15 @@
import {fontStyles} from '../../../styles/gr-font-styles';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, html, css} from 'lit';
-import {customElement, property, state, query} from 'lit/decorators.js';
+import {customElement, state, query} from 'lit/decorators.js';
import {assertIsDefined} from '../../../utils/common-util';
import {BindValueChangeEvent} from '../../../types/events';
import {ShortcutController} from '../../lit/shortcut-controller';
import {subscribe} from '../../lit/subscription-controller';
import {resolve} from '../../../models/dependency';
import {changeModelToken} from '../../../models/change/change-model';
+import {ParsedChangeInfo} from '../../../types/types';
+import {configModelToken} from '../../../models/config/config-model';
@customElement('gr-download-dialog')
export class GrDownloadDialog extends LitElement {
@@ -35,27 +37,37 @@
@query('#closeButton') protected closeButton?: GrButton;
- @property({type: Object})
- change: ChangeInfo | undefined;
+ @state() change?: ParsedChangeInfo;
- @property({type: Object})
- config?: DownloadInfo;
+ @state() config?: DownloadInfo;
@state() patchNum?: PatchSetNum;
- @state() private selectedScheme?: string;
+ @state() selectedScheme?: string;
private readonly shortcuts = new ShortcutController(this);
private readonly getChangeModel = resolve(this, changeModelToken);
+ private readonly getConfigModel = resolve(this, configModelToken);
+
constructor() {
super();
subscribe(
this,
+ () => this.getChangeModel().change$,
+ x => (this.change = x)
+ );
+ subscribe(
+ this,
() => this.getChangeModel().patchNum$,
x => (this.patchNum = x)
);
+ subscribe(
+ this,
+ () => this.getConfigModel().download$,
+ x => (this.config = x)
+ );
for (const key of ['1', '2', '3', '4', '5']) {
this.shortcuts.addLocal({key}, e => this.handleNumberKey(e));
}
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts
index e5f40a6..73d7618 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts
@@ -8,6 +8,7 @@
createChange,
createCommit,
createDownloadInfo,
+ createParsedChange,
createRevision,
} from '../../../test/test-data-generators';
import {
@@ -160,7 +161,7 @@
suite('gr-download-dialog tests with no fetch options', () => {
setup(async () => {
element.change = {
- ...createChange(),
+ ...createParsedChange(),
revisions: {
r1: {
...createRevision(),
@@ -204,7 +205,7 @@
test('computed fields', () => {
element.change = {
- ...createChange(),
+ ...createParsedChange(),
project: 'test/project' as RepoName,
_number: 123 as NumericChangeId,
};
@@ -233,7 +234,7 @@
element.patchNum = 1 as PatchSetNum;
element.change = {
- ...createChange(),
+ ...createParsedChange(),
revisions: {
r1: {...createRevision(), commit: createCommit()},
},
@@ -241,7 +242,7 @@
assert.isTrue(element.computeHidePatchFile());
element.change = {
- ...createChange(),
+ ...createParsedChange(),
revisions: {
r1: {
...createRevision(),
@@ -255,7 +256,7 @@
assert.isFalse(element.computeHidePatchFile());
element.change = {
- ...createChange(),
+ ...createParsedChange(),
revisions: {
r1: {
...createRevision(),
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
index 33dfe82..32da400 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
@@ -10,9 +10,12 @@
import {fontStyles} from '../../../styles/gr-font-styles';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, html, css} from 'lit';
-import {customElement, property, state} from 'lit/decorators.js';
+import {customElement, state} from 'lit/decorators.js';
import {BindValueChangeEvent} from '../../../types/events';
import {fireNoBubble} from '../../../utils/event-util';
+import {resolve} from '../../../models/dependency';
+import {changeModelToken} from '../../../models/change/change-model';
+import {subscribe} from '../../lit/subscription-controller';
interface DisplayGroup {
title: string;
@@ -27,19 +30,18 @@
* @event close
*/
- @property({type: Object})
- changeNum?: NumericChangeId;
+ @state() changeNum?: NumericChangeId;
- // private but used in test
@state() includedIn?: IncludedInInfo;
@state() private loaded = false;
- // private but used in test
@state() filterText = '';
private readonly restApiService = getAppContext().restApiService;
+ private readonly getChangeModel = resolve(this, changeModelToken);
+
static override get styles() {
return [
fontStyles,
@@ -93,6 +95,15 @@
];
}
+ constructor() {
+ super();
+ subscribe(
+ this,
+ () => this.getChangeModel().changeNum$,
+ changeNum => (this.changeNum = changeNum)
+ );
+ }
+
override render() {
return html`
<header>
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 8bcbb2b..4f1ce7a 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -24,7 +24,7 @@
import {GrButton} from '../../shared/gr-button/gr-button';
import {TokenHighlightLayer} from '../../../embed/diff/gr-diff-builder/token-highlight-layer';
import {css, html, LitElement, nothing} from 'lit';
-import {customElement, property, query, state} from 'lit/decorators.js';
+import {customElement, query, state} from 'lit/decorators.js';
import {sharedStyles} from '../../../styles/shared-styles';
import {subscribe} from '../../lit/subscription-controller';
import {assert} from '../../../utils/common-util';
@@ -39,6 +39,7 @@
import {fireReload} from '../../../utils/event-util';
import {when} from 'lit/directives/when.js';
import {Timing} from '../../../constants/reporting';
+import {changeModelToken} from '../../../models/change/change-model';
interface FilePreview {
filepath: string;
@@ -62,10 +63,10 @@
@query('#nextFix')
nextFix?: GrButton;
- @property({type: Object})
+ @state()
change?: ParsedChangeInfo;
- @property({type: Number})
+ @state()
changeNum?: NumericChangeId;
@state()
@@ -102,6 +103,8 @@
private readonly getUserModel = resolve(this, userModelToken);
+ private readonly getChangeModel = resolve(this, changeModelToken);
+
private readonly getNavigation = resolve(this, navigationToken);
private readonly reporting = getAppContext().reportingService;
@@ -133,6 +136,16 @@
this.syntaxLayer.setEnabled(!!this.diffPrefs.syntax_highlighting);
}
);
+ subscribe(
+ this,
+ () => this.getChangeModel().change$,
+ change => (this.change = change)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().changeNum$,
+ changeNum => (this.changeNum = changeNum)
+ );
}
static override styles = [
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 bdb634b..0f54ad1 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
@@ -46,7 +46,6 @@
PreferencesInfo,
RepoName,
RevisionPatchSetNum,
- ServerInfo,
CommentMap,
} from '../../../types/common';
import {DiffInfo, DiffPreferencesInfo, WebLinkInfo} from '../../../types/diff';
@@ -80,7 +79,6 @@
import {ShortcutController} from '../../lit/shortcut-controller';
import {subscribe} from '../../lit/subscription-controller';
import {customElement, property, query, state} from 'lit/decorators.js';
-import {configModelToken} from '../../../models/config/config-model';
import {a11yStyles} from '../../../styles/gr-a11y-styles';
import {sharedStyles} from '../../../styles/shared-styles';
import {ifDefined} from 'lit/directives/if-defined.js';
@@ -194,9 +192,6 @@
@property({type: Object})
prefs?: DiffPreferencesInfo;
- @state()
- private serverConfig?: ServerInfo;
-
// Private but used in tests.
@state()
userPrefs?: PreferencesInfo;
@@ -241,8 +236,6 @@
private readonly getShortcutsService = resolve(this, shortcutsServiceToken);
- private readonly getConfigModel = resolve(this, configModelToken);
-
private readonly getViewModel = resolve(this, changeViewModelToken);
private throttledToggleFileReviewed?: (e: KeyboardEvent) => void;
@@ -340,13 +333,6 @@
);
subscribe(
this,
- () => this.getConfigModel().serverConfig$,
- config => {
- this.serverConfig = config;
- }
- );
- subscribe(
- this,
() => this.getCommentsModel().changeComments$,
changeComments => {
this.changeComments = changeComments;
@@ -966,12 +952,8 @@
}
private renderDialogs() {
- return html` <gr-apply-fix-dialog
- id="applyFixDialog"
- .change=${this.change}
- .changeNum=${this.changeNum}
- >
- </gr-apply-fix-dialog>
+ return html`
+ <gr-apply-fix-dialog id="applyFixDialog"></gr-apply-fix-dialog>
<gr-diff-preferences-dialog
id="diffPreferencesDialog"
@reload-diff-preference=${this.handleReloadingDiffPreference}
@@ -980,12 +962,10 @@
<dialog id="downloadModal" tabindex="-1">
<gr-download-dialog
id="downloadDialog"
- .change=${this.change}
- .patchNum=${this.patchNum}
- .config=${this.serverConfig?.download}
@close=${this.handleDownloadDialogClose}
></gr-download-dialog>
- </dialog>`;
+ </dialog>
+ `;
}
/**
diff --git a/polygerrit-ui/app/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts
index 6d111aa..f5d7fef 100644
--- a/polygerrit-ui/app/models/change/change-model.ts
+++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -281,6 +281,12 @@
viewModelState?.patchNum || latestPatchN
);
+ /** The user can enter edit mode without an `EDIT` patchset existing yet. */
+ public readonly editMode$ = select(
+ combineLatest([this.viewModel.edit$, this.patchNum$]),
+ ([edit, patchNum]) => !!edit || patchNum === EDIT
+ );
+
/**
* Emits the base patchset number. This is identical to the
* `viewModel.basePatchNum$`, but has some special logic for merges.
diff --git a/polygerrit-ui/app/models/config/config-model.ts b/polygerrit-ui/app/models/config/config-model.ts
index 168e0f4..4c0df60 100644
--- a/polygerrit-ui/app/models/config/config-model.ts
+++ b/polygerrit-ui/app/models/config/config-model.ts
@@ -34,6 +34,11 @@
configState => configState.serverConfig
);
+ public download$ = select(
+ this.serverConfig$,
+ serverConfig => serverConfig?.download
+ );
+
public mergeabilityComputationBehavior$ = select(
this.serverConfig$,
serverConfig => serverConfig?.change?.mergeability_computation_behavior
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
index 49259b4..9b08a6e 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
@@ -34,6 +34,7 @@
appStarted(): void;
onVisibilityChange(): void;
+ onFocusChange(): void;
beforeLocationChanged(): void;
locationChanged(page: string): void;
dashboardDisplayed(): void;
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 350f199..b8758e2 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -185,6 +185,12 @@
document.addEventListener('visibilitychange', () => {
reportingService.onVisibilityChange();
});
+ window.addEventListener('blur', () => {
+ reportingService.onFocusChange();
+ });
+ window.addEventListener('focus', () => {
+ reportingService.onFocusChange();
+ });
}
export function initClickReporter(reportingService: ReportingService) {
@@ -526,6 +532,20 @@
this._reportNavResTimes();
}
+ onFocusChange() {
+ this.reporter(
+ LIFECYCLE.TYPE,
+ LIFECYCLE.CATEGORY.VISIBILITY,
+ LifeCycle.FOCUS,
+ undefined,
+ {
+ isVisible: document.visibilityState === 'visible',
+ hasFocus: document.hasFocus(),
+ },
+ false
+ );
+ }
+
onVisibilityChange() {
this.hiddenDurationTimer.onVisibilityChange();
let eventName;
@@ -542,6 +562,8 @@
undefined,
{
hiddenDurationMs: this.hiddenDurationTimer.hiddenDurationMs,
+ isVisible: document.visibilityState === 'visible',
+ hasFocus: document.hasFocus(),
},
false
);
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
index d4efbcc..fd1b88c 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
@@ -44,6 +44,9 @@
onVisibilityChange: () => {
log('onVisibilityChange');
},
+ onFocusChange: () => {
+ log('onFocusChange');
+ },
pluginLoaded: () => {},
pluginsLoaded: () => {},
pluginsFailed: () => {},