blob: 893eeb9c5ba033d330e96ee8772c7633341c4c69 [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
Ben Rohlfs94fcbbc2022-05-27 10:45:03 +02003 * Copyright 2015 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04005 */
Ben Rohlfsbadc1342020-09-22 10:04:23 +02006import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
Ben Rohlfs0e30caa2021-12-08 08:48:52 +01007import '../../plugins/gr-endpoint-param/gr-endpoint-param';
8import '../../plugins/gr-endpoint-slot/gr-endpoint-slot';
Ben Rohlfsbadc1342020-09-22 10:04:23 +02009import '../../shared/gr-account-chip/gr-account-chip';
Ben Rohlfsbadc1342020-09-22 10:04:23 +020010import '../../shared/gr-button/gr-button';
Chris Poucetb7e9bb12022-07-22 14:11:03 +020011import '../../shared/gr-icon/gr-icon';
Ben Rohlfsbadc1342020-09-22 10:04:23 +020012import '../../shared/gr-formatted-text/gr-formatted-text';
Ben Rohlfsbadc1342020-09-22 10:04:23 +020013import '../../shared/gr-account-list/gr-account-list';
14import '../gr-label-scores/gr-label-scores';
15import '../gr-thread-list/gr-thread-list';
16import '../../../styles/shared-styles';
Ben Rohlfs21220bf2022-11-04 08:53:30 +010017import {GrReviewerSuggestionsProvider} from '../../../services/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider';
Chris Poucetc6e880b2021-11-15 19:57:06 +010018import {getAppContext} from '../../../services/app-context';
Ben Rohlfsbadc1342020-09-22 10:04:23 +020019import {
20 ChangeStatus,
21 DraftsAction,
22 ReviewerState,
23 SpecialFilePath,
24} from '../../../constants/constants';
Dhruv Srivastavaeeba4562021-05-06 09:49:52 +020025import {
Dhruv Srivastavaa1882db2022-08-18 10:16:26 +020026 getUserId,
Dhruv Srivastava4a9892e2022-08-03 17:27:18 +020027 isAccountNewlyAdded,
Dhruv Srivastavaeeba4562021-05-06 09:49:52 +020028 removeServiceUsers,
Dhruv Srivastava4a9892e2022-08-03 17:27:18 +020029 toReviewInput,
Dhruv Srivastavaeeba4562021-05-06 09:49:52 +020030} from '../../../utils/account-util';
Ben Rohlfsa7ab9502021-02-15 17:45:45 +010031import {TargetElement} from '../../../api/plugin';
Ben Rohlfsf3dc4cf2023-03-02 12:49:34 +010032import {isDefined, ParsedChangeInfo} from '../../../types/types';
Ben Rohlfsbadc1342020-09-22 10:04:23 +020033import {
Ben Rohlfsbadc1342020-09-22 10:04:23 +020034 AccountInfoInput,
Dhruv80dd5ee2022-04-06 13:34:02 +020035 AccountInput,
Dhruv569d6162022-04-08 10:21:00 +020036 AccountInputDetail,
Ben Rohlfsbadc1342020-09-22 10:04:23 +020037 GrAccountList,
38 GroupInfoInput,
Ben Rohlfsbadc1342020-09-22 10:04:23 +020039 RawAccountInput,
40} from '../../shared/gr-account-list/gr-account-list';
Ben Rohlfsbadc1342020-09-22 10:04:23 +020041import {
42 AccountId,
43 AccountInfo,
44 AttentionSetInput,
45 ChangeInfo,
Dhruv Srivastava65edec82023-02-28 19:37:59 +010046 CommentThread,
47 DraftInfo,
Ben Rohlfsbadc1342020-09-22 10:04:23 +020048 GroupInfo,
49 isAccount,
Frank Borden891b73e2021-01-29 16:43:50 -080050 isDetailedLabelInfo,
Ben Rohlfsbadc1342020-09-22 10:04:23 +020051 isReviewerAccountSuggestion,
52 isReviewerGroupSuggestion,
Ben Rohlfsbadc1342020-09-22 10:04:23 +020053 ParsedJSON,
Dhruv Srivastava88ff8ac2021-07-02 16:23:04 +020054 ReviewerInput,
Ben Rohlfsbadc1342020-09-22 10:04:23 +020055 ReviewInput,
56 ReviewResult,
57 ServerInfo,
Dhruv2235ecb2022-04-07 11:58:19 +020058 SuggestedReviewerGroupInfo,
Ben Rohlfsbadc1342020-09-22 10:04:23 +020059 Suggestion,
Dhruv Srivastava462eb392022-08-18 10:42:11 +020060 UserId,
Dhruv Srivastava4e27dc42023-03-01 10:49:49 +010061 UnsavedInfo,
62 isDraft,
Ben Rohlfsbadc1342020-09-22 10:04:23 +020063} from '../../../types/common';
64import {GrButton} from '../../shared/gr-button/gr-button';
65import {GrLabelScores} from '../gr-label-scores/gr-label-scores';
66import {GrLabelScoreRow} from '../gr-label-score-row/gr-label-score-row';
Ben Rohlfsd6a47662020-10-01 14:42:47 +020067import {
68 areSetsEqual,
Ben Rohlfsc1c6afd2021-02-18 13:13:22 +010069 assertIsDefined,
Ben Rohlfsd6a47662020-10-01 14:42:47 +020070 containsAll,
Dhruv Srivastavaf189ec82022-08-04 14:33:46 +020071 difference,
Dhruv Srivastava5dbf4672021-06-29 09:06:20 +020072 queryAndAssert,
Ben Rohlfsd6a47662020-10-01 14:42:47 +020073} from '../../../utils/common-util';
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +020074import {
Dhruv Srivastava625a0a02022-11-28 12:13:51 +010075 createPatchsetLevelUnsavedDraft,
Dhruv Srivastavadc2b34b2022-09-01 09:20:32 +020076 getFirstComment,
Dhruv Srivastavadc2b34b2022-09-01 09:20:32 +020077 isPatchsetLevel,
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +020078 isUnresolved,
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +020079} from '../../../utils/comment-util';
Ben Rohlfsbadc1342020-09-22 10:04:23 +020080import {GrAccountChip} from '../../shared/gr-account-chip/gr-account-chip';
Frank Borden891b73e2021-01-29 16:43:50 -080081import {
Frank Borden891b73e2021-01-29 16:43:50 -080082 getApprovalInfo,
83 getMaxAccounts,
Milutin Kristoficbd445802021-10-20 21:00:21 +020084 StandardLabels,
Frank Borden891b73e2021-01-29 16:43:50 -080085} from '../../../utils/label-util';
Milutin Kristofic5b3f0872020-12-05 22:08:09 +010086import {pluralize} from '../../../utils/string-util';
Ben Rohlfs62278c82021-03-12 11:35:56 +010087import {
88 fireAlert,
Ben Rohlfs6bb90532023-02-17 18:55:56 +010089 fireError,
Ben Rohlfs44f01042023-02-18 13:27:57 +010090 fire,
91 fireNoBubble,
92 fireNoBubbleNoCompose,
Ben Rohlfs62278c82021-03-12 11:35:56 +010093 fireIronAnnounce,
Ben Rohlfs66367b62021-04-30 10:06:32 +020094 fireReload,
Ben Rohlfs62278c82021-03-12 11:35:56 +010095 fireServerError,
96} from '../../../utils/event-util';
Ben Rohlfsa7ab9502021-02-15 17:45:45 +010097import {ErrorCallback} from '../../../api/rest';
Dhruv Srivastava32c45082022-09-05 15:25:39 +020098import {DelayedTask} from '../../../utils/async-util';
Milutin Kristofica7c24c12021-06-07 23:09:26 +020099import {Interaction, Timing} from '../../../constants/reporting';
Dhruv Srivastava71007ec2022-09-09 09:03:42 +0200100import {
101 getMentionedReason,
102 getReplyByReason,
103} from '../../../utils/attention-set-util';
Chris Poucetc6e880b2021-11-15 19:57:06 +0100104import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
Dhruv80dd5ee2022-04-06 13:34:02 +0200105import {resolve} from '../../../models/dependency';
Chris Poucetbf65b8f2022-01-18 21:18:12 +0000106import {changeModelToken} from '../../../models/change/change-model';
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +0200107import {
108 ConfigInfo,
109 LabelNameToValuesMap,
Dhruv Srivastavab1679c82022-09-01 09:10:09 +0200110 PatchSetNumber,
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +0200111} from '../../../api/rest-api';
Dhruv Srivastava92b0c142022-11-21 20:33:10 +0530112import {css, html, PropertyValues, LitElement, nothing} from 'lit';
Dhruv80dd5ee2022-04-06 13:34:02 +0200113import {sharedStyles} from '../../../styles/shared-styles';
Frank Borden42c1a452022-08-11 16:27:20 +0200114import {when} from 'lit/directives/when.js';
115import {classMap} from 'lit/directives/class-map.js';
Ben Rohlfsc3684352023-02-17 16:01:39 +0100116import {
117 AddReviewerEvent,
118 RemoveReviewerEvent,
119 ValueChangedEvent,
120} from '../../../types/events';
Frank Borden42c1a452022-08-11 16:27:20 +0200121import {customElement, property, state, query} from 'lit/decorators.js';
Frank Borden59801032022-05-06 14:37:18 +0200122import {subscribe} from '../../lit/subscription-controller';
Dhruv Srivastavaefe10d9c2022-06-28 12:22:48 +0200123import {configModelToken} from '../../../models/config/config-model';
Ben Rohlfsead93662022-08-05 09:09:15 +0200124import {hasHumanReviewer, isOwner} from '../../../utils/change-util';
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200125import {commentsModelToken} from '../../../models/comments/comments-model';
Dhruv Srivastavaee018e92022-08-31 11:37:46 +0200126import {
127 CommentEditingChangedDetail,
128 GrComment,
129} from '../../shared/gr-comment/gr-comment';
Dhruv Srivastavae2cbb802022-09-07 14:15:48 +0200130import {ShortcutController} from '../../lit/shortcut-controller';
Dhruv Srivastava51b70092022-10-20 15:26:12 +0200131import {Key, Modifier, whenVisible} from '../../../utils/dom-util';
Milutin Kristofic944063a2022-09-14 13:49:55 +0200132import {GrThreadList} from '../gr-thread-list/gr-thread-list';
Chris Poucetbb0cf832022-10-24 12:32:10 +0200133import {userModelToken} from '../../../models/user/user-model';
Chris Poucet4ed2c5d2022-10-24 22:28:15 +0200134import {accountsModelToken} from '../../../models/accounts-model/accounts-model';
Chris Poucete3d66862022-10-26 11:19:50 +0200135import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
Dhruv Srivastavad6bd9df2022-11-14 19:46:47 +0530136import {modalStyles} from '../../../styles/gr-modal-styles';
Ben Rohlfsf3dc4cf2023-03-02 12:49:34 +0100137import {ironAnnouncerRequestAvailability} from '../../polymer-util';
Wyatt Allen4f4b3a72016-07-28 12:05:53 -0700138
Milutin Kristoficbec88f12020-10-13 16:53:28 +0200139export enum FocusTarget {
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200140 ANY = 'any',
141 BODY = 'body',
142 CCS = 'cc',
143 REVIEWERS = 'reviewers',
144}
Kasper Nilssona8271552017-01-17 11:54:40 -0800145
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200146enum ReviewerType {
147 REVIEWER = 'REVIEWER',
148 CC = 'CC',
149}
Wyatt Allen6cf58752017-04-24 16:59:07 +0200150
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200151enum LatestPatchState {
152 LATEST = 'latest',
153 CHECKING = 'checking',
154 NOT_LATEST = 'not-latest',
155}
Kasper Nilssonaa746bd2017-09-18 22:12:30 -0700156
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100157const ButtonLabels = {
158 START_REVIEW: 'Start review',
159 SEND: 'Send',
160};
Wyatt Allenf2247d42017-10-26 18:01:18 -0700161
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100162const ButtonTooltips = {
Ben Rohlfsafc9e1f2021-02-15 12:50:35 +0100163 SAVE: 'Send changes and comments as work in progress but do not start review',
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100164 START_REVIEW: 'Mark as ready for review and send reply',
165 SEND: 'Send reply',
Milutin Kristofic216e8852020-12-17 09:18:11 +0100166 DISABLED_COMMENT_EDITING: 'Save draft comments to enable send',
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100167};
Kasper Nilssonfb54dc62018-03-26 13:57:57 -0700168
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100169const EMPTY_REPLY_MESSAGE = 'Cannot send an empty reply.';
170
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200171@customElement('gr-reply-dialog')
Dhruv80dd5ee2022-04-06 13:34:02 +0200172export class GrReplyDialog extends LitElement {
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200173 FocusTarget = FocusTarget;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100174
Chris Poucetc6e880b2021-11-15 19:57:06 +0100175 private readonly reporting = getAppContext().reportingService;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100176
Chris Poucetbf65b8f2022-01-18 21:18:12 +0000177 private readonly getChangeModel = resolve(this, changeModelToken);
Dhruv Srivastavaf3cfbe32021-04-21 16:26:56 +0200178
Chris Poucetbb0cf832022-10-24 12:32:10 +0200179 private readonly getCommentsModel = resolve(this, commentsModelToken);
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200180
Dhruv Srivastavaed173952022-08-08 09:37:36 +0200181 // TODO: update type to only ParsedChangeInfo
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200182 @property({type: Object})
Dhruv Srivastavaed173952022-08-08 09:37:36 +0200183 change?: ParsedChangeInfo | ChangeInfo;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200184
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200185 @property({type: Boolean})
186 canBeStarted = false;
187
Dhruv80dd5ee2022-04-06 13:34:02 +0200188 @property({type: Boolean, reflect: true})
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200189 disabled = false;
190
Dhruv Srivastava11d44e42022-08-30 14:29:22 +0200191 @state()
192 draftCommentThreads: CommentThread[] = [];
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200193
194 @property({type: Object})
Dhruv Srivastava276c6a92022-03-30 14:58:46 +0200195 permittedLabels?: LabelNameToValuesMap;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200196
197 @property({type: Object})
Dhruv80dd5ee2022-04-06 13:34:02 +0200198 projectConfig?: ConfigInfo;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200199
Dhruv Srivastavae8b86392022-10-20 17:17:21 +0200200 @query('#patchsetLevelComment') patchsetLevelGrComment?: GrComment;
201
Dhruv80dd5ee2022-04-06 13:34:02 +0200202 @query('#reviewers') reviewersList?: GrAccountList;
203
204 @query('#ccs') ccsList?: GrAccountList;
205
206 @query('#cancelButton') cancelButton?: GrButton;
207
208 @query('#sendButton') sendButton?: GrButton;
209
210 @query('#labelScores') labelScores?: GrLabelScores;
211
Dhruv Srivastavad6bd9df2022-11-14 19:46:47 +0530212 @query('#reviewerConfirmationModal')
213 reviewerConfirmationModal?: HTMLDialogElement;
Dhruv80dd5ee2022-04-06 13:34:02 +0200214
Paladox noned1e16172023-02-22 19:00:06 +0000215 @state() latestPatchNum?: PatchSetNumber;
216
Dhruv Srivastavaefe10d9c2022-06-28 12:22:48 +0200217 @state() serverConfig?: ServerInfo;
218
Dhruv80dd5ee2022-04-06 13:34:02 +0200219 @state()
Chris Poucetafd0f7c2022-10-04 10:04:43 +0000220 patchsetLevelDraftMessage = '';
221
222 @state()
Dhruv80dd5ee2022-04-06 13:34:02 +0200223 filterReviewerSuggestion: (input: Suggestion) => boolean;
224
225 @state()
226 filterCCSuggestion: (input: Suggestion) => boolean;
227
228 @state()
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200229 knownLatestState?: LatestPatchState;
230
Dhruv80dd5ee2022-04-06 13:34:02 +0200231 @state()
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200232 underReview = true;
233
Dhruv80dd5ee2022-04-06 13:34:02 +0200234 @state()
235 account?: AccountInfo;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200236
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200237 get ccs() {
Dhruv Srivastava0758a3c2022-08-12 09:53:02 +0200238 return [
239 ...this._ccs,
240 ...this.mentionedUsers.filter(v => !this.isAlreadyReviewerOrCC(v)),
241 ];
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200242 }
243
244 /**
245 * We pass the ccs object to AccountInput for modifying where it needs to
246 * add a value to CC. The returned value contains both mentionedUsers and
247 * normal ccs hence separate the two when setting ccs.
248 */
249 set ccs(ccs: AccountInput[]) {
250 this._ccs = ccs.filter(
251 cc =>
252 !this.mentionedUsers.some(
Dhruv Srivastavaa1882db2022-08-18 10:16:26 +0200253 mentionedCC => getUserId(mentionedCC) === getUserId(cc)
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200254 )
255 );
256 this.requestUpdate('ccs', ccs);
257 }
258
Dhruv80dd5ee2022-04-06 13:34:02 +0200259 @state()
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200260 _ccs: AccountInput[] = [];
261
262 /**
263 * Maintain a separate list of users added to cc due to being mentioned in
264 * unresolved drafts.
265 * If the draft is discarded or edited to remove the mention then we want to
266 * remove the user from being added to CC.
267 * Instead of figuring out when we should remove the mentioned user ie when
268 * they get removed from the last comment, we recompute this property when
269 * any of the draft comments change.
270 * If we add the user to the existing ccs object then we cannot differentiate
271 * if the user was added manually to CC or added due to being mentioned hence
272 * we cannot reset the mentioned ccs when drafts change.
273 */
274 @state()
275 mentionedUsers: AccountInput[] = [];
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200276
Dhruv80dd5ee2022-04-06 13:34:02 +0200277 @state()
Dhruv Srivastava3ffc5ba2022-09-15 11:19:52 +0200278 mentionedUsersInUnresolvedDrafts: AccountInfo[] = [];
279
280 @state()
Dhruv80dd5ee2022-04-06 13:34:02 +0200281 attentionCcsCount = 0;
Ben Rohlfs25937b12020-10-06 15:12:53 +0200282
Dhruv80dd5ee2022-04-06 13:34:02 +0200283 @state()
284 ccPendingConfirmation: SuggestedReviewerGroupInfo | null = null;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200285
Dhruv80dd5ee2022-04-06 13:34:02 +0200286 @state()
287 messagePlaceholder?: string;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200288
Dhruv80dd5ee2022-04-06 13:34:02 +0200289 @state()
Dhruv80dd5ee2022-04-06 13:34:02 +0200290 uploader?: AccountInfo;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200291
Dhruv80dd5ee2022-04-06 13:34:02 +0200292 @state()
293 pendingConfirmationDetails: SuggestedReviewerGroupInfo | null = null;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200294
Dhruv80dd5ee2022-04-06 13:34:02 +0200295 @state()
296 includeComments = true;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200297
Dhruv80dd5ee2022-04-06 13:34:02 +0200298 @state() reviewers: AccountInput[] = [];
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200299
Dhruv80dd5ee2022-04-06 13:34:02 +0200300 @state()
301 reviewerPendingConfirmation: SuggestedReviewerGroupInfo | null = null;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200302
Dhruv80dd5ee2022-04-06 13:34:02 +0200303 @state()
Dhruv80dd5ee2022-04-06 13:34:02 +0200304 sendButtonLabel?: string;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200305
Dhruv80dd5ee2022-04-06 13:34:02 +0200306 @state()
307 savingComments = false;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200308
Dhruv80dd5ee2022-04-06 13:34:02 +0200309 @state()
310 reviewersMutated = false;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200311
Ben Rohlfs21f18ab2020-10-13 23:03:08 +0200312 /**
313 * Signifies that the user has changed their vote on a label or (if they have
314 * not yet voted on a label) if a selected vote is different from the default
315 * vote.
316 */
Dhruv80dd5ee2022-04-06 13:34:02 +0200317 @state()
318 labelsChanged = false;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200319
Dhruv80dd5ee2022-04-06 13:34:02 +0200320 @state()
321 readonly saveTooltip: string = ButtonTooltips.SAVE;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200322
Dhruv80dd5ee2022-04-06 13:34:02 +0200323 @state()
324 pluginMessage = '';
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200325
Dhruv80dd5ee2022-04-06 13:34:02 +0200326 @state()
327 commentEditing = false;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200328
Dhruv80dd5ee2022-04-06 13:34:02 +0200329 @state()
330 attentionExpanded = false;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200331
Dhruv80dd5ee2022-04-06 13:34:02 +0200332 @state()
Dhruv Srivastava462eb392022-08-18 10:42:11 +0200333 currentAttentionSet: Set<UserId> = new Set();
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200334
Dhruv80dd5ee2022-04-06 13:34:02 +0200335 @state()
Dhruv Srivastava462eb392022-08-18 10:42:11 +0200336 newAttentionSet: Set<UserId> = new Set();
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200337
Dhruv80dd5ee2022-04-06 13:34:02 +0200338 @state()
339 sendDisabled?: boolean;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200340
Dhruv80dd5ee2022-04-06 13:34:02 +0200341 @state()
Dhruv Srivastava463bb332022-08-31 13:00:49 +0200342 patchsetLevelDraftIsResolved = true;
Ben Rohlfsbadc1342020-09-22 10:04:23 +0200343
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +0200344 @state()
Chris Poucetafd0f7c2022-10-04 10:04:43 +0000345 patchsetLevelComment?: UnsavedInfo | DraftInfo;
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +0200346
Chris Poucetc6e880b2021-11-15 19:57:06 +0100347 private readonly restApiService: RestApiService =
348 getAppContext().restApiService;
Ben Rohlfs43935a42020-12-01 19:14:09 +0100349
Chris Poucete3d66862022-10-26 11:19:50 +0200350 private readonly getPluginLoader = resolve(this, pluginLoaderToken);
351
Dhruv Srivastavaefe10d9c2022-06-28 12:22:48 +0200352 private readonly getConfigModel = resolve(this, configModelToken);
353
Chris Poucet4ed2c5d2022-10-24 22:28:15 +0200354 private readonly getAccountsModel = resolve(this, accountsModelToken);
Dhruv Srivastava71007ec2022-09-09 09:03:42 +0200355
Chris Poucetbb0cf832022-10-24 12:32:10 +0200356 private readonly getUserModel = resolve(this, userModelToken);
357
Dhruv80dd5ee2022-04-06 13:34:02 +0200358 storeTask?: DelayedTask;
Ben Rohlfsf92d3b52021-03-10 23:13:03 +0100359
Frank Borden59801032022-05-06 14:37:18 +0200360 private isLoggedIn = false;
361
Dhruv Srivastava712f1d72022-09-07 10:05:24 +0200362 private readonly shortcuts = new ShortcutController(this);
Logan Hanksadd2bce2016-07-20 13:22:51 -0700363
Dhruv80dd5ee2022-04-06 13:34:02 +0200364 static override styles = [
365 sharedStyles,
Dhruv Srivastavad6bd9df2022-11-14 19:46:47 +0530366 modalStyles,
Dhruv80dd5ee2022-04-06 13:34:02 +0200367 css`
368 :host {
369 background-color: var(--dialog-background-color);
370 display: block;
371 max-height: 90vh;
Dhruv8e9897f2022-05-05 11:27:37 +0200372 --label-score-padding-left: var(--spacing-xl);
Dhruv80dd5ee2022-04-06 13:34:02 +0200373 }
374 :host([disabled]) {
375 pointer-events: none;
376 }
377 :host([disabled]) .container {
378 opacity: 0.5;
379 }
380 section {
381 border-top: 1px solid var(--border-color);
382 flex-shrink: 0;
383 padding: var(--spacing-m) var(--spacing-xl);
384 width: 100%;
385 }
386 section.labelsContainer {
387 /* We want the :hover highlight to extend to the border of the dialog. */
388 padding: var(--spacing-m) 0;
389 }
390 .stickyBottom {
391 background-color: var(--dialog-background-color);
392 box-shadow: 0px 0px 8px 0px rgba(60, 64, 67, 0.15);
393 margin-top: var(--spacing-s);
394 bottom: 0;
395 position: sticky;
396 /* @see Issue 8602 */
397 z-index: 1;
398 }
399 .stickyBottom.newReplyDialog {
400 margin-top: unset;
401 }
402 .actions {
403 display: flex;
404 justify-content: space-between;
405 }
406 .actions .right gr-button {
407 margin-left: var(--spacing-l);
408 }
409 .peopleContainer,
410 .labelsContainer {
411 flex-shrink: 0;
412 }
413 .peopleContainer {
414 border-top: none;
415 display: table;
416 }
417 .peopleList {
418 display: flex;
419 }
420 .peopleListLabel {
421 color: var(--deemphasized-text-color);
422 margin-top: var(--spacing-xs);
423 min-width: 6em;
424 padding-right: var(--spacing-m);
425 }
426 gr-account-list {
427 display: flex;
428 flex-wrap: wrap;
429 flex: 1;
430 }
Dhruv Srivastavad6bd9df2022-11-14 19:46:47 +0530431 #reviewerConfirmationModal {
Dhruv80dd5ee2022-04-06 13:34:02 +0200432 padding: var(--spacing-l);
433 text-align: center;
434 }
435 .reviewerConfirmationButtons {
436 margin-top: var(--spacing-l);
437 }
438 .groupName {
439 font-weight: var(--font-weight-bold);
440 }
441 .groupSize {
442 font-style: italic;
443 }
444 .textareaContainer {
445 min-height: 12em;
446 position: relative;
447 }
448 .newReplyDialog.textareaContainer {
449 min-height: unset;
450 }
451 textareaContainer,
Dhruv80dd5ee2022-04-06 13:34:02 +0200452 gr-endpoint-decorator[name='reply-text'] {
453 display: flex;
454 width: 100%;
455 }
Dhruv80dd5ee2022-04-06 13:34:02 +0200456 gr-endpoint-decorator[name='reply-text'] {
457 flex-direction: column;
458 }
Dhruv80dd5ee2022-04-06 13:34:02 +0200459 #checkingStatusLabel,
460 #notLatestLabel {
461 margin-left: var(--spacing-l);
462 }
463 #checkingStatusLabel {
464 color: var(--deemphasized-text-color);
465 font-style: italic;
466 }
467 #notLatestLabel,
468 #savingLabel {
469 color: var(--error-text-color);
470 }
471 #savingLabel {
472 display: none;
473 }
474 #savingLabel.saving {
475 display: inline;
476 }
477 #pluginMessage {
478 color: var(--deemphasized-text-color);
479 margin-left: var(--spacing-l);
480 margin-bottom: var(--spacing-m);
481 }
482 #pluginMessage:empty {
483 display: none;
484 }
Dhruv80dd5ee2022-04-06 13:34:02 +0200485 .attention .edit-attention-button {
486 vertical-align: top;
487 --gr-button-padding: 0px 4px;
488 }
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200489 .attention .edit-attention-button gr-icon {
Dhruv80dd5ee2022-04-06 13:34:02 +0200490 color: inherit;
Ben Rohlfsb1b0ac92022-08-01 14:33:02 +0200491 /* The line-height:26px hack (see below) requires us to do this.
492 Normally the gr-icon would account for a proper positioning
493 within the standard line-height:20px context. */
494 top: 5px;
Dhruv80dd5ee2022-04-06 13:34:02 +0200495 }
496 .attention a,
497 .attention-detail a {
498 text-decoration: none;
499 }
500 .attentionSummary {
501 display: flex;
502 justify-content: space-between;
503 }
504 .attentionSummary {
505 /* The account label for selection is misbehaving currently: It consumes
506 26px height instead of 20px, which is the default line-height and thus
507 the max that can be nicely fit into an inline layout flow. We
508 acknowledge that using a fixed 26px value here is a hack and not a
509 great solution. */
510 line-height: 26px;
511 }
512 .attentionSummary gr-account-label,
513 .attention-detail gr-account-label {
514 --account-max-length: 120px;
515 display: inline-block;
516 padding: var(--spacing-xs) var(--spacing-m);
517 user-select: none;
518 --label-border-radius: 8px;
519 }
520 .attentionSummary gr-account-label {
521 margin: 0 var(--spacing-xs);
522 line-height: var(--line-height-normal);
523 vertical-align: top;
524 }
525 .attention-detail .peopleListValues {
526 line-height: calc(var(--line-height-normal) + 10px);
527 }
528 .attention-detail gr-account-label {
529 line-height: var(--line-height-normal);
530 }
531 .attentionSummary gr-account-label:focus,
532 .attention-detail gr-account-label:focus {
533 outline: none;
534 }
535 .attentionSummary gr-account-label:hover,
536 .attention-detail gr-account-label:hover {
537 box-shadow: var(--elevation-level-1);
538 cursor: pointer;
539 }
540 .attention-detail .attentionDetailsTitle {
541 display: flex;
542 justify-content: space-between;
543 }
544 .attention-detail .selectUsers {
545 color: var(--deemphasized-text-color);
546 margin-bottom: var(--spacing-m);
547 }
548 .attentionTip {
549 padding: var(--spacing-m);
550 border: 1px solid var(--border-color);
551 border-radius: var(--border-radius);
552 margin-top: var(--spacing-m);
Kamil Musine0629712023-02-06 15:06:25 +0100553 background-color: var(--line-item-highlight-color);
Dhruv80dd5ee2022-04-06 13:34:02 +0200554 }
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200555 .attentionTip div gr-icon {
Dhruv80dd5ee2022-04-06 13:34:02 +0200556 margin-right: var(--spacing-s);
557 }
558 .patchsetLevelContainer {
559 width: 80ch;
560 border-radius: var(--border-radius);
561 box-shadow: var(--elevation-level-2);
562 }
563 .patchsetLevelContainer.resolved {
564 background-color: var(--comment-background-color);
565 }
566 .patchsetLevelContainer.unresolved {
567 background-color: var(--unresolved-comment-background-color);
568 }
Ben Rohlfse48dece2022-11-24 11:11:43 +0000569 .privateVisiblityInfo {
Dhruv Srivastava92b0c142022-11-21 20:33:10 +0530570 display: flex;
571 justify-content: center;
572 background-color: var(--info-background);
573 padding: var(--spacing-s) 0;
574 }
Ben Rohlfse48dece2022-11-24 11:11:43 +0000575 .privateVisiblityInfo gr-icon {
Dhruv Srivastava92b0c142022-11-21 20:33:10 +0530576 margin-right: var(--spacing-m);
577 color: var(--info-foreground);
578 }
Dhruv80dd5ee2022-04-06 13:34:02 +0200579 `,
580 ];
581
Chris Poucet5ec77f02022-05-12 11:25:21 +0200582 constructor() {
583 super();
584 this.filterReviewerSuggestion =
585 this.filterReviewerSuggestionGenerator(false);
586 this.filterCCSuggestion = this.filterReviewerSuggestionGenerator(true);
Dhruv Srivastava712f1d72022-09-07 10:05:24 +0200587
588 this.shortcuts.addLocal({key: Key.ESC}, () => this.cancel());
Dhruv Srivastavae2cbb802022-09-07 14:15:48 +0200589 this.shortcuts.addLocal(
590 {key: Key.ENTER, modifiers: [Modifier.CTRL_KEY]},
591 () => this.submit()
592 );
593 this.shortcuts.addLocal(
594 {key: Key.ENTER, modifiers: [Modifier.META_KEY]},
595 () => this.submit()
596 );
Dhruv Srivastava712f1d72022-09-07 10:05:24 +0200597
Chris Poucet5ec77f02022-05-12 11:25:21 +0200598 subscribe(
599 this,
Chris Poucetbb0cf832022-10-24 12:32:10 +0200600 () => this.getUserModel().loggedIn$,
Chris Poucet5ec77f02022-05-12 11:25:21 +0200601 isLoggedIn => (this.isLoggedIn = isLoggedIn)
602 );
Dhruv Srivastavaefe10d9c2022-06-28 12:22:48 +0200603 subscribe(
604 this,
605 () => this.getConfigModel().serverConfig$,
606 config => {
607 this.serverConfig = config;
608 }
609 );
Dhruv Srivastavaed173952022-08-08 09:37:36 +0200610 subscribe(
611 this,
612 () => this.getChangeModel().change$,
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200613 x => (this.change = x)
614 );
615 subscribe(
616 this,
Dhruv Srivastavab1679c82022-09-01 09:10:09 +0200617 () => this.getChangeModel().latestPatchNum$,
618 x => (this.latestPatchNum = x)
619 );
620 subscribe(
621 this,
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200622 () => this.getCommentsModel().mentionedUsersInDrafts$,
Dhruv Srivastavaed173952022-08-08 09:37:36 +0200623 x => {
Dhruv Srivastava0758a3c2022-08-12 09:53:02 +0200624 this.mentionedUsers = x;
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200625 this.reviewersMutated =
626 this.reviewersMutated || this.mentionedUsers.length > 0;
627 }
628 );
629 subscribe(
630 this,
631 () => this.getCommentsModel().mentionedUsersInUnresolvedDrafts$,
632 x => {
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200633 this.mentionedUsersInUnresolvedDrafts = x.filter(
634 v => !this.isAlreadyReviewerOrCC(v)
635 );
Dhruv Srivastavaed173952022-08-08 09:37:36 +0200636 }
637 );
Dhruv Srivastava4e5fd112022-08-25 12:01:22 +0200638 subscribe(
639 this,
640 () => this.getCommentsModel().patchsetLevelDrafts$,
Chris Poucetafd0f7c2022-10-04 10:04:43 +0000641 x => (this.patchsetLevelComment = x[0])
Dhruv Srivastava4e5fd112022-08-25 12:01:22 +0200642 );
Dhruv Srivastava11d44e42022-08-30 14:29:22 +0200643 subscribe(
644 this,
645 () => this.getCommentsModel().draftThreads$,
Dhruv Srivastavafb91ec92022-08-25 14:22:53 +0200646 threads =>
647 (this.draftCommentThreads = threads.filter(
Dhruv Srivastavadc2b34b2022-09-01 09:20:32 +0200648 t => !(isDraft(getFirstComment(t)) && isPatchsetLevel(t))
Dhruv Srivastavafb91ec92022-08-25 14:22:53 +0200649 ))
Dhruv Srivastava11d44e42022-08-30 14:29:22 +0200650 );
Chris Poucet5ec77f02022-05-12 11:25:21 +0200651 }
652
653 override connectedCallback() {
654 super.connectedCallback();
Ben Rohlfsf3dc4cf2023-03-02 12:49:34 +0100655 ironAnnouncerRequestAvailability();
Chris Poucete3d66862022-10-26 11:19:50 +0200656
657 this.getPluginLoader().jsApiService.addElement(
658 TargetElement.REPLY_DIALOG,
659 this
660 );
661
Chris Poucet5ec77f02022-05-12 11:25:21 +0200662 this.restApiService.getAccount().then(account => {
663 if (account) this.account = account;
664 });
665
Dhruv Srivastavaee018e92022-08-31 11:37:46 +0200666 this.addEventListener(
667 'comment-editing-changed',
668 (e: CustomEvent<CommentEditingChangedDetail>) => {
669 // Patchset level comment is always in editing mode which means it would
670 // set commentEditing = true and the send button would be permanently
671 // disabled.
672 if (e.detail.path === SpecialFilePath.PATCHSET_LEVEL_COMMENTS) return;
Milutin Kristofic944063a2022-09-14 13:49:55 +0200673 const commentList = queryAndAssert<GrThreadList>(this, '#commentList');
674 // It can be one or more comments were in editing mode. Wwitching one
675 // thread in editing, we need to check if there are still other threads
676 // in editing.
677 this.commentEditing = Array.from(commentList.threadElements ?? []).some(
678 thread => thread.editing
679 );
Dhruv Srivastavaee018e92022-08-31 11:37:46 +0200680 }
681 );
Chris Poucet5ec77f02022-05-12 11:25:21 +0200682
683 // Plugins on reply-reviewers endpoint can take advantage of these
684 // events to add / remove reviewers
685
Ben Rohlfsc3684352023-02-17 16:01:39 +0100686 this.addEventListener('add-reviewer', (e: AddReviewerEvent) => {
687 const reviewer = e.detail.reviewer;
Chris Poucet5ec77f02022-05-12 11:25:21 +0200688 // Only support account type, see more from:
689 // elements/shared/gr-account-list/gr-account-list.js#addAccountItem
690 this.reviewersList?.addAccountItem({
Ben Rohlfsc3684352023-02-17 16:01:39 +0100691 account: reviewer,
Chris Poucet5ec77f02022-05-12 11:25:21 +0200692 count: 1,
693 });
694 });
695
Ben Rohlfsc3684352023-02-17 16:01:39 +0100696 this.addEventListener('remove-reviewer', (e: RemoveReviewerEvent) => {
697 const reviewer = e.detail.reviewer;
698 this.reviewersList?.removeAccount(reviewer);
Chris Poucet5ec77f02022-05-12 11:25:21 +0200699 });
700 }
701
Dhruv80dd5ee2022-04-06 13:34:02 +0200702 override willUpdate(changedProperties: PropertyValues) {
Dhruv80dd5ee2022-04-06 13:34:02 +0200703 if (changedProperties.has('ccPendingConfirmation')) {
704 this.pendingConfirmationUpdated(this.ccPendingConfirmation);
705 }
706 if (changedProperties.has('reviewerPendingConfirmation')) {
707 this.pendingConfirmationUpdated(this.reviewerPendingConfirmation);
708 }
709 if (changedProperties.has('change')) {
710 this.computeUploader();
Dhruv Srivastavaed173952022-08-08 09:37:36 +0200711 this.rebuildReviewerArrays();
Dhruv80dd5ee2022-04-06 13:34:02 +0200712 }
713 if (changedProperties.has('canBeStarted')) {
714 this.computeMessagePlaceholder();
715 this.computeSendButtonLabel();
716 }
Dhruv80dd5ee2022-04-06 13:34:02 +0200717 if (changedProperties.has('sendDisabled')) {
718 this.sendDisabledChanged();
719 }
720 if (changedProperties.has('attentionExpanded')) {
721 this.onAttentionExpandedChange();
722 }
723 if (
724 changedProperties.has('account') ||
725 changedProperties.has('reviewers') ||
726 changedProperties.has('ccs') ||
727 changedProperties.has('change') ||
728 changedProperties.has('draftCommentThreads') ||
Dhruv Srivastava3ffc5ba2022-09-15 11:19:52 +0200729 changedProperties.has('mentionedUsersInUnresolvedDrafts') ||
Dhruv80dd5ee2022-04-06 13:34:02 +0200730 changedProperties.has('includeComments') ||
731 changedProperties.has('labelsChanged') ||
Chris Poucetafd0f7c2022-10-04 10:04:43 +0000732 changedProperties.has('patchsetLevelDraftMessage') ||
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200733 changedProperties.has('mentionedCCs')
Dhruv80dd5ee2022-04-06 13:34:02 +0200734 ) {
735 this.computeNewAttention();
736 }
737 }
738
Gerrit Code Review09637ae2021-08-19 14:34:39 +0000739 override disconnectedCallback() {
Dhruvd5435522022-06-22 14:58:47 +0200740 this.storeTask?.flush();
Ben Rohlfs5f520da2021-03-10 14:58:43 +0100741 super.disconnectedCallback();
Ben Rohlfs04fc1ca2021-02-13 13:06:53 +0100742 }
743
Dhruv80dd5ee2022-04-06 13:34:02 +0200744 override render() {
745 if (!this.change) return;
746 this.sendDisabled = this.computeSendButtonDisabled();
747 return html`
748 <div tabindex="-1">
749 <section class="peopleContainer">
750 <gr-endpoint-decorator name="reply-reviewers">
751 <gr-endpoint-param
Milutin Kristofic8f69b302022-05-10 10:47:09 +0200752 name="change"
Dhruv80dd5ee2022-04-06 13:34:02 +0200753 .value=${this.change}
754 ></gr-endpoint-param>
Dhruv Srivastava418c48d2022-08-03 14:35:10 +0200755 <gr-endpoint-param name="reviewers" .value=${[...this.reviewers]}>
Dhruv80dd5ee2022-04-06 13:34:02 +0200756 </gr-endpoint-param>
757 ${this.renderReviewerList()}
758 <gr-endpoint-slot name="below"></gr-endpoint-slot>
759 </gr-endpoint-decorator>
760 ${this.renderCCList()} ${this.renderReviewConfirmation()}
Dhruv Srivastava92b0c142022-11-21 20:33:10 +0530761 ${this.renderPrivateVisiblityInfo()}
Dhruv80dd5ee2022-04-06 13:34:02 +0200762 </section>
763 <section class="labelsContainer">${this.renderLabels()}</section>
764 <section class="newReplyDialog textareaContainer">
765 ${this.renderReplyText()}
766 </section>
Dhruv80dd5ee2022-04-06 13:34:02 +0200767 ${this.renderDraftsSection()}
768 <div class="stickyBottom newReplyDialog">
769 <gr-endpoint-decorator name="reply-bottom">
770 <gr-endpoint-param
Milutin Kristofic11168902022-05-10 10:51:18 +0200771 name="change"
Dhruv80dd5ee2022-04-06 13:34:02 +0200772 .value=${this.change}
773 ></gr-endpoint-param>
774 ${this.renderAttentionSummarySection()}
775 ${this.renderAttentionDetailsSection()}
776 <gr-endpoint-slot name="above-actions"></gr-endpoint-slot>
777 ${this.renderActionsSection()}
778 </gr-endpoint-decorator>
779 </div>
780 </div>
781 `;
782 }
783
784 private renderReviewerList() {
785 return html`
786 <div class="peopleList">
787 <div class="peopleListLabel">Reviewers</div>
788 <gr-account-list
789 id="reviewers"
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200790 .accounts=${[...this.reviewers]}
Dhruv Srivastava9a898152022-07-08 15:53:54 +0200791 .change=${this.change}
Dhruv Srivastava4a9892e2022-08-03 17:27:18 +0200792 .reviewerState=${ReviewerState.REVIEWER}
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200793 @account-added=${this.handleAccountAdded}
Dhruv80dd5ee2022-04-06 13:34:02 +0200794 @accounts-changed=${this.handleReviewersChanged}
795 .removableValues=${this.change?.removable_reviewers}
796 .filter=${this.filterReviewerSuggestion}
797 .pendingConfirmation=${this.reviewerPendingConfirmation}
798 @pending-confirmation-changed=${this
799 .handleReviewersConfirmationChanged}
800 .placeholder=${'Add reviewer...'}
801 @account-text-changed=${this.handleAccountTextEntry}
802 .suggestionsProvider=${this.getReviewerSuggestionsProvider(
803 this.change
804 )}
805 >
806 </gr-account-list>
807 <gr-endpoint-slot name="right"></gr-endpoint-slot>
808 </div>
809 `;
810 }
811
812 private renderCCList() {
813 return html`
814 <div class="peopleList">
815 <div class="peopleListLabel">CC</div>
816 <gr-account-list
817 id="ccs"
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200818 .accounts=${[...this.ccs]}
Dhruv Srivastava4a9892e2022-08-03 17:27:18 +0200819 .change=${this.change}
820 .reviewerState=${ReviewerState.CC}
Dhruv Srivastavadb01c602022-07-18 15:34:34 +0200821 @account-added=${this.handleAccountAdded}
Dhruv80dd5ee2022-04-06 13:34:02 +0200822 @accounts-changed=${this.handleCcsChanged}
Dhruv80dd5ee2022-04-06 13:34:02 +0200823 .filter=${this.filterCCSuggestion}
824 .pendingConfirmation=${this.ccPendingConfirmation}
825 @pending-confirmation-changed=${this.handleCcsConfirmationChanged}
826 allow-any-input
827 .placeholder=${'Add CC...'}
828 @account-text-changed=${this.handleAccountTextEntry}
829 .suggestionsProvider=${this.getCcSuggestionsProvider(this.change)}
830 >
831 </gr-account-list>
832 </div>
833 `;
834 }
835
836 private renderReviewConfirmation() {
837 return html`
Dhruv Srivastavad6bd9df2022-11-14 19:46:47 +0530838 <dialog
839 tabindex="-1"
840 id="reviewerConfirmationModal"
841 @close=${this.cancelPendingReviewer}
Dhruv80dd5ee2022-04-06 13:34:02 +0200842 >
843 <div class="reviewerConfirmation">
844 Group
845 <span class="groupName">
846 ${this.pendingConfirmationDetails?.group.name}
847 </span>
848 has
849 <span class="groupSize">
850 ${this.pendingConfirmationDetails?.count}
851 </span>
852 members.
853 <br />
854 Are you sure you want to add them all?
855 </div>
856 <div class="reviewerConfirmationButtons">
857 <gr-button @click=${this.confirmPendingReviewer}>Yes</gr-button>
858 <gr-button @click=${this.cancelPendingReviewer}>No</gr-button>
859 </div>
Dhruv Srivastavad6bd9df2022-11-14 19:46:47 +0530860 </dialog>
Dhruv80dd5ee2022-04-06 13:34:02 +0200861 `;
862 }
863
Dhruv Srivastava92b0c142022-11-21 20:33:10 +0530864 private renderPrivateVisiblityInfo() {
865 const addedAccounts = [
866 ...(this.reviewersList?.additions() ?? []),
867 ...(this.ccsList?.additions() ?? []),
868 ];
869 if (!this.change?.is_private || !addedAccounts.length) return nothing;
870 return html`
Ben Rohlfse48dece2022-11-24 11:11:43 +0000871 <div class="privateVisiblityInfo">
Dhruv Srivastava92b0c142022-11-21 20:33:10 +0530872 <gr-icon icon="info"></gr-icon>
873 <div>
874 Adding a reviewer/CC will make this private change visible to them
875 </div>
876 </div>
877 `;
878 }
879
Dhruv80dd5ee2022-04-06 13:34:02 +0200880 private renderLabels() {
881 if (!this.change || !this.account || !this.permittedLabels) return;
882 return html`
883 <gr-endpoint-decorator name="reply-label-scores">
884 <gr-label-scores
885 id="labelScores"
886 .account=${this.account}
887 .change=${this.change}
888 @labels-changed=${this._handleLabelsChanged}
889 .permittedLabels=${this.permittedLabels}
890 ></gr-label-scores>
891 <gr-endpoint-param
Milutin Kristofic11168902022-05-10 10:51:18 +0200892 name="change"
Dhruv80dd5ee2022-04-06 13:34:02 +0200893 .value=${this.change}
894 ></gr-endpoint-param>
895 </gr-endpoint-decorator>
896 <div id="pluginMessage">${this.pluginMessage}</div>
897 `;
898 }
899
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +0200900 private renderPatchsetLevelComment() {
Chris Poucetafd0f7c2022-10-04 10:04:43 +0000901 if (!this.patchsetLevelComment)
Dhruv Srivastava625a0a02022-11-28 12:13:51 +0100902 this.patchsetLevelComment = createPatchsetLevelUnsavedDraft(
903 this.latestPatchNum,
904 this.patchsetLevelDraftMessage,
905 !this.patchsetLevelDraftIsResolved
906 );
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +0200907 return html`
908 <gr-comment
Dhruv Srivastava8bdda3b2022-08-30 19:11:31 +0200909 id="patchsetLevelComment"
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +0200910 .comment=${this.patchsetLevelComment}
911 .comments=${[this.patchsetLevelComment]}
Dhruv Srivastava463bb332022-08-31 13:00:49 +0200912 @comment-unresolved-changed=${(e: ValueChangedEvent<boolean>) => {
913 this.patchsetLevelDraftIsResolved = !e.detail.value;
914 }}
915 @comment-text-changed=${(e: ValueChangedEvent<string>) => {
Chris Poucetafd0f7c2022-10-04 10:04:43 +0000916 this.patchsetLevelDraftMessage = e.detail.value;
Dhruv Srivastavab6199642022-08-30 18:49:32 +0200917 }}
Dhruv Srivastavaf007abc2022-09-06 11:18:57 +0200918 .messagePlaceholder=${this.messagePlaceholder}
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +0200919 hide-header
Dhruv Srivastava4e5fd112022-08-25 12:01:22 +0200920 permanent-editing-mode
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +0200921 ></gr-comment>
922 `;
923 }
924
Dhruv80dd5ee2022-04-06 13:34:02 +0200925 private renderReplyText() {
926 if (!this.change) return;
927 return html`
928 <div
929 class=${classMap({
930 patchsetLevelContainer: true,
931 [this.getUnresolvedPatchsetLevelClass(
Dhruv Srivastava463bb332022-08-31 13:00:49 +0200932 this.patchsetLevelDraftIsResolved
Dhruv80dd5ee2022-04-06 13:34:02 +0200933 )]: true,
934 })}
935 >
936 <gr-endpoint-decorator name="reply-text">
Dhruv Srivastavae75f6f72022-08-25 10:14:37 +0200937 ${this.renderPatchsetLevelComment()}
Milutin Kristofic11168902022-05-10 10:51:18 +0200938 <gr-endpoint-param name="change" .value=${this.change}>
Dhruv80dd5ee2022-04-06 13:34:02 +0200939 </gr-endpoint-param>
940 </gr-endpoint-decorator>
Dhruv80dd5ee2022-04-06 13:34:02 +0200941 </div>
942 `;
943 }
944
945 private renderDraftsSection() {
946 if (this.computeHideDraftList(this.draftCommentThreads)) return;
947 return html`
948 <section class="draftsContainer">
949 <div class="includeComments">
950 <input
951 type="checkbox"
952 id="includeComments"
953 @change=${this.handleIncludeCommentsChanged}
954 ?checked=${this.includeComments}
955 />
956 <label for="includeComments"
957 >Publish ${this.computeDraftsTitle(this.draftCommentThreads)}</label
958 >
959 </div>
960 ${when(
961 this.includeComments,
962 () => html`
963 <gr-thread-list
964 id="commentList"
Dhruv Srivastava11d44e42022-08-30 14:29:22 +0200965 .threads=${this.draftCommentThreads}
Dhruv80dd5ee2022-04-06 13:34:02 +0200966 hide-dropdown
967 >
968 </gr-thread-list>
969 `
970 )}
971 <span
972 id="savingLabel"
973 class=${this.computeSavingLabelClass(this.savingComments)}
974 >
975 Saving comments...
976 </span>
977 </section>
978 `;
979 }
980
981 private renderAttentionSummarySection() {
982 if (this.attentionExpanded) return;
983 return html`
984 <section class="attention">
985 <div class="attentionSummary">
986 <div>
987 ${when(
988 this.computeShowNoAttentionUpdate(),
989 () => html` <span>${this.computeDoNotUpdateMessage()}</span> `
990 )}
991 ${when(
992 !this.computeShowNoAttentionUpdate(),
993 () => html`
994 <span>Bring to attention of</span>
995 ${this.computeNewAttentionAccounts().map(
996 account => html`
997 <gr-account-label
998 .account=${account}
999 .forceAttention=${this.computeHasNewAttention(account)}
1000 .selected=${this.computeHasNewAttention(account)}
1001 .hideHovercard=${true}
1002 .selectionChipStyle=${true}
1003 @click=${this.handleAttentionClick}
1004 ></gr-account-label>
1005 `
1006 )}
1007 `
1008 )}
1009 <gr-tooltip-content
1010 has-tooltip
1011 title=${this.computeAttentionButtonTitle()}
1012 >
1013 <gr-button
1014 class="edit-attention-button"
1015 @click=${this.handleAttentionModify}
1016 ?disabled=${this.sendDisabled}
1017 link
1018 position-below
1019 data-label="Edit"
1020 data-action-type="change"
1021 data-action-key="edit"
1022 role="button"
1023 tabindex="0"
1024 >
Ben Rohlfsb1b0ac92022-08-01 14:33:02 +02001025 <div>
1026 <gr-icon icon="edit" filled small></gr-icon>
1027 <span>Modify</span>
1028 </div>
Dhruv80dd5ee2022-04-06 13:34:02 +02001029 </gr-button>
1030 </gr-tooltip-content>
1031 </div>
1032 <div>
1033 <a
1034 href="https://gerrit-review.googlesource.com/Documentation/user-attention-set.html"
1035 target="_blank"
1036 >
Chris Poucetb7e9bb12022-07-22 14:11:03 +02001037 <gr-icon icon="help" title="read documentation"></gr-icon>
Dhruv80dd5ee2022-04-06 13:34:02 +02001038 </a>
1039 </div>
1040 </div>
1041 </section>
1042 `;
1043 }
1044
1045 private renderAttentionDetailsSection() {
1046 if (!this.attentionExpanded) return;
1047 return html`
1048 <section class="attention-detail">
1049 <div class="attentionDetailsTitle">
1050 <div>
1051 <span>Modify attention to</span>
1052 </div>
1053 <div></div>
1054 <div>
1055 <a
1056 href="https://gerrit-review.googlesource.com/Documentation/user-attention-set.html"
1057 target="_blank"
1058 >
Chris Poucetb7e9bb12022-07-22 14:11:03 +02001059 <gr-icon icon="help" title="read documentation"></gr-icon>
Dhruv80dd5ee2022-04-06 13:34:02 +02001060 </a>
1061 </div>
1062 </div>
1063 <div class="selectUsers">
1064 <span
1065 >Select chips to set who will be in the attention set after sending
1066 this reply</span
1067 >
1068 </div>
1069 <div class="peopleList">
1070 <div class="peopleListLabel">Owner</div>
1071 <div class="peopleListValues">
1072 <gr-account-label
Dhruv Srivastavafcb63502022-07-11 14:42:00 +02001073 .account=${this.change?.owner}
1074 ?forceAttention=${this.computeHasNewAttention(this.change?.owner)}
1075 .selected=${this.computeHasNewAttention(this.change?.owner)}
Dhruv80dd5ee2022-04-06 13:34:02 +02001076 .hideHovercard=${true}
1077 .selectionChipStyle=${true}
1078 @click=${this.handleAttentionClick}
1079 >
1080 </gr-account-label>
1081 </div>
1082 </div>
1083 ${when(
1084 this.uploader,
1085 () => html`
1086 <div class="peopleList">
1087 <div class="peopleListLabel">Uploader</div>
1088 <div class="peopleListValues">
1089 <gr-account-label
1090 .account=${this.uploader}
1091 ?forceAttention=${this.computeHasNewAttention(this.uploader)}
1092 .selected=${this.computeHasNewAttention(this.uploader)}
1093 .hideHovercard=${true}
1094 .selectionChipStyle=${true}
1095 @click=${this.handleAttentionClick}
1096 >
1097 </gr-account-label>
1098 </div>
1099 </div>
1100 `
1101 )}
1102 <div class="peopleList">
1103 <div class="peopleListLabel">Reviewers</div>
1104 <div class="peopleListValues">
Dhruv Srivastava517a7862022-08-03 14:46:21 +02001105 ${removeServiceUsers(this.reviewers).map(
Dhruv80dd5ee2022-04-06 13:34:02 +02001106 account => html`
1107 <gr-account-label
1108 .account=${account}
1109 ?forceAttention=${this.computeHasNewAttention(account)}
1110 .selected=${this.computeHasNewAttention(account)}
1111 .hideHovercard=${true}
1112 .selectionChipStyle=${true}
1113 @click=${this.handleAttentionClick}
1114 >
1115 </gr-account-label>
1116 `
1117 )}
1118 </div>
1119 </div>
1120
1121 ${when(
1122 this.attentionCcsCount,
1123 () => html`
1124 <div class="peopleList">
1125 <div class="peopleListLabel">CC</div>
1126 <div class="peopleListValues">
Dhruv Srivastava517a7862022-08-03 14:46:21 +02001127 ${removeServiceUsers(this.ccs).map(
Dhruv80dd5ee2022-04-06 13:34:02 +02001128 account => html`
1129 <gr-account-label
1130 .account=${account}
1131 ?forceAttention=${this.computeHasNewAttention(account)}
1132 .selected=${this.computeHasNewAttention(account)}
1133 .hideHovercard=${true}
1134 .selectionChipStyle=${true}
1135 @click=${this.handleAttentionClick}
1136 >
1137 </gr-account-label>
1138 `
1139 )}
1140 </div>
1141 </div>
1142 `
1143 )}
1144 ${when(
Dhruv Srivastava4a03a142022-07-11 11:34:50 +02001145 this.computeShowAttentionTip(),
Dhruv80dd5ee2022-04-06 13:34:02 +02001146 () => html`
1147 <div class="attentionTip">
Chris Poucetb7e9bb12022-07-22 14:11:03 +02001148 <gr-icon icon="lightbulb"></gr-icon>
Dhruvc22bd652022-07-04 11:38:30 +02001149 Please be mindful of requiring attention from too many users.
Dhruv80dd5ee2022-04-06 13:34:02 +02001150 </div>
1151 `
1152 )}
1153 </section>
1154 `;
1155 }
1156
1157 private renderActionsSection() {
1158 return html`
1159 <section class="actions">
1160 <div class="left">
1161 ${when(
1162 this.knownLatestState === LatestPatchState.CHECKING,
1163 () => html`
1164 <span id="checkingStatusLabel">
Paladox noned1e16172023-02-22 19:00:06 +00001165 Checking whether patch ${this.latestPatchNum} is latest...
Dhruv80dd5ee2022-04-06 13:34:02 +02001166 </span>
1167 `
1168 )}
1169 ${when(
1170 this.knownLatestState === LatestPatchState.NOT_LATEST,
1171 () => html`
1172 <span id="notLatestLabel">
1173 ${this.computePatchSetWarning()}
1174 <gr-button link @click=${this._reload}>Reload</gr-button>
1175 </span>
1176 `
1177 )}
1178 </div>
1179 <div class="right">
1180 <gr-button
1181 link
1182 id="cancelButton"
1183 class="action cancel"
1184 @click=${this.cancelTapHandler}
1185 >Cancel</gr-button
1186 >
1187 ${when(
1188 this.canBeStarted,
1189 () => html`
1190 <!-- Use 'Send' here as the change may only about reviewers / ccs
1191 and when this button is visible, the next button will always
1192 be 'Start review' -->
1193 <gr-tooltip-content has-tooltip title=${this.saveTooltip}>
1194 <gr-button
1195 link
1196 ?disabled=${this.knownLatestState ===
1197 LatestPatchState.NOT_LATEST}
1198 class="action save"
1199 @click=${this.saveClickHandler}
1200 >Send As WIP</gr-button
1201 >
1202 </gr-tooltip-content>
1203 `
1204 )}
1205 <gr-tooltip-content
1206 has-tooltip
1207 title=${this.computeSendButtonTooltip(
1208 this.canBeStarted,
1209 this.commentEditing
1210 )}
1211 >
1212 <gr-button
1213 id="sendButton"
1214 primary
1215 ?disabled=${this.sendDisabled}
1216 class="action send"
1217 @click=${this.sendTapHandler}
1218 >${this.sendButtonLabel}
1219 </gr-button>
1220 </gr-tooltip-content>
1221 </div>
1222 </section>
1223 `;
1224 }
1225
Ben Rohlfs00049652021-10-25 16:40:38 +02001226 /**
1227 * Note that this method is not actually *opening* the dialog. Opening and
1228 * showing the dialog is dealt with by the overlay. This method is used by the
1229 * change view for initializing the dialog after opening the overlay. Maybe it
1230 * should be called `onOpened()` or `initialize()`?
1231 */
1232 open(focusTarget?: FocusTarget, quote?: string) {
Ben Rohlfsc1c6afd2021-02-18 13:13:22 +01001233 assertIsDefined(this.change, 'change');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001234 this.knownLatestState = LatestPatchState.CHECKING;
Chris Poucetbf65b8f2022-01-18 21:18:12 +00001235 this.getChangeModel()
1236 .fetchChangeUpdates(this.change)
1237 .then(result => {
1238 this.knownLatestState = result.isLatest
1239 ? LatestPatchState.LATEST
1240 : LatestPatchState.NOT_LATEST;
1241 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001242
Dhruv80dd5ee2022-04-06 13:34:02 +02001243 this.focusOn(focusTarget);
Ben Rohlfs00049652021-10-25 16:40:38 +02001244 if (quote?.length) {
1245 // If a reply quote has been provided, use it.
Chris Poucetafd0f7c2022-10-04 10:04:43 +00001246 this.patchsetLevelDraftMessage = quote;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001247 }
Ben Rohlfs43935a42020-12-01 19:14:09 +01001248 if (this.restApiService.hasPendingDiffDrafts()) {
Dhruv80dd5ee2022-04-06 13:34:02 +02001249 this.savingComments = true;
Ben Rohlfs43935a42020-12-01 19:14:09 +01001250 this.restApiService.awaitPendingDiffDrafts().then(() => {
Ben Rohlfs44f01042023-02-18 13:27:57 +01001251 fire(this, 'comment-refresh', {});
Dhruv80dd5ee2022-04-06 13:34:02 +02001252 this.savingComments = false;
Dmitrii Filippovb82003c2019-11-05 17:02:59 +01001253 });
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001254 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001255 }
1256
Chris Poucet54d2abc2022-04-28 11:51:35 +02001257 hasDrafts() {
Dhruv Srivastava463bb332022-08-31 13:00:49 +02001258 return (
Chris Poucetafd0f7c2022-10-04 10:04:43 +00001259 this.patchsetLevelDraftMessage.length > 0 ||
Dhruv Srivastava463bb332022-08-31 13:00:49 +02001260 this.draftCommentThreads.length > 0
1261 );
Dhruv Srivastava568feec2020-10-30 20:05:04 +01001262 }
1263
Gerrit Code Review09637ae2021-08-19 14:34:39 +00001264 override focus() {
Dhruv80dd5ee2022-04-06 13:34:02 +02001265 this.focusOn(FocusTarget.ANY);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001266 }
1267
Dhruv80dd5ee2022-04-06 13:34:02 +02001268 private handleIncludeCommentsChanged(e: Event) {
1269 if (!(e.target instanceof HTMLInputElement)) return;
1270 this.includeComments = e.target.checked;
1271 }
1272
1273 setLabelValue(label: string, value: string): void {
Frank Bordenc3705eb2021-11-29 18:29:11 +01001274 const selectorEl =
1275 this.getLabelScores().shadowRoot?.querySelector<GrLabelScoreRow>(
1276 `gr-label-score-row[name="${label}"]`
1277 );
1278 selectorEl?.setSelectedValue(value);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001279 }
1280
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001281 getLabelValue(label: string) {
Frank Bordenc3705eb2021-11-29 18:29:11 +01001282 const selectorEl =
1283 this.getLabelScores().shadowRoot?.querySelector<GrLabelScoreRow>(
1284 `gr-label-score-row[name="${label}"]`
1285 );
1286 return selectorEl?.selectedValue;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001287 }
1288
Frank Borden7281dbb2022-05-16 10:52:54 +02001289 // TODO: Combine logic into handleReviewersChanged & handleCCsChanged and
1290 // remove account-added event from GrAccountList.
Dhruv Srivastavadb01c602022-07-18 15:34:34 +02001291 handleAccountAdded(e: CustomEvent<AccountInputDetail>) {
Dhruv569d6162022-04-08 10:21:00 +02001292 const account = e.detail.account;
Dhruv Srivastavaa1882db2022-08-18 10:16:26 +02001293 const key = getUserId(account);
Dhruv569d6162022-04-08 10:21:00 +02001294 const reviewerType =
1295 (e.target as GrAccountList).getAttribute('id') === 'ccs'
1296 ? ReviewerType.CC
1297 : ReviewerType.REVIEWER;
1298 const isReviewer = ReviewerType.REVIEWER === reviewerType;
Dhruv Srivastava31caeec2022-08-05 14:39:34 +02001299 const reviewerList = isReviewer ? this.ccsList : this.reviewersList;
1300 // Remove any accounts that already exist as a CC for reviewer
1301 // or vice versa.
1302 if (reviewerList?.removeAccount(account)) {
Dhruv569d6162022-04-08 10:21:00 +02001303 const moveFrom = isReviewer ? 'CC' : 'reviewer';
1304 const moveTo = isReviewer ? 'reviewer' : 'CC';
1305 const id = account.name || key;
1306 const message = `${id} moved from ${moveFrom} to ${moveTo}.`;
1307 fireAlert(this, message);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001308 }
1309 }
1310
Dhruv Srivastava463bb332022-08-31 13:00:49 +02001311 getUnresolvedPatchsetLevelClass(patchsetLevelDraftIsResolved: boolean) {
1312 return patchsetLevelDraftIsResolved ? 'resolved' : 'unresolved';
Dhruv Srivastava5dbf4672021-06-29 09:06:20 +02001313 }
1314
Dhruv Srivastava82f6b202022-08-03 15:22:20 +02001315 computeReviewers() {
Dhruv Srivastava88ff8ac2021-07-02 16:23:04 +02001316 const reviewers: ReviewerInput[] = [];
Dhruv Srivastava4a9892e2022-08-03 17:27:18 +02001317 const reviewerAdditions = this.reviewersList?.additions() ?? [];
1318 reviewers.push(
1319 ...reviewerAdditions.map(v => toReviewInput(v, ReviewerState.REVIEWER))
1320 );
Dhruv Srivastava82f6b202022-08-03 15:22:20 +02001321
Dhruv Srivastava4a9892e2022-08-03 17:27:18 +02001322 const ccAdditions = this.ccsList?.additions() ?? [];
1323 reviewers.push(...ccAdditions.map(v => toReviewInput(v, ReviewerState.CC)));
1324
1325 // ignore removal from reviewer request if being added as CC
1326 let removals = difference(
1327 this.reviewersList?.removals() ?? [],
1328 ccAdditions,
Dhruv Srivastavaa1882db2022-08-18 10:16:26 +02001329 (a, b) => getUserId(a) === getUserId(b)
Dhruv Srivastava4a9892e2022-08-03 17:27:18 +02001330 ).map(v => toReviewInput(v, ReviewerState.REMOVED));
Dhruv Srivastava82f6b202022-08-03 15:22:20 +02001331 reviewers.push(...removals);
1332
Dhruv Srivastava4a9892e2022-08-03 17:27:18 +02001333 // ignore removal from CC request if being added as reviewer
1334 removals = difference(
1335 this.ccsList?.removals() ?? [],
1336 reviewerAdditions,
Dhruv Srivastavaa1882db2022-08-18 10:16:26 +02001337 (a, b) => getUserId(a) === getUserId(b)
Dhruv Srivastava4a9892e2022-08-03 17:27:18 +02001338 ).map(v => toReviewInput(v, ReviewerState.REMOVED));
Dhruv Srivastava82f6b202022-08-03 15:22:20 +02001339 reviewers.push(...removals);
1340
Dhruv Srivastavaca3d4952022-08-08 20:58:04 +02001341 // The owner is returned as a reviewer in the ChangeInfo object in some
1342 // cases, and trying to remove the owner as a reviewer returns in a
1343 // 500 server error.
1344 return reviewers.filter(
1345 reviewerInput =>
1346 !(
1347 this.change?.owner._account_id === reviewerInput.reviewer &&
1348 reviewerInput.state === ReviewerState.REMOVED
1349 )
1350 );
Dhruv Srivastava88ff8ac2021-07-02 16:23:04 +02001351 }
1352
Dhruv Srivastava8bdda3b2022-08-30 19:11:31 +02001353 async send(includeComments: boolean, startReview: boolean) {
Milutin Kristofic48a0f432021-03-16 22:53:08 +01001354 this.reporting.time(Timing.SEND_REPLY);
Dhruv Srivastava5dbf4672021-06-29 09:06:20 +02001355 const labels = this.getLabelScores().getLabelValues();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001356
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001357 const reviewInput: ReviewInput = {
1358 drafts: includeComments
1359 ? DraftsAction.PUBLISH_ALL_REVISIONS
1360 : DraftsAction.KEEP,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001361 labels,
1362 };
1363
1364 if (startReview) {
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001365 reviewInput.ready = true;
Dhruv Srivastavaf970bf12022-11-28 14:35:23 +01001366 } else if (this.change?.work_in_progress) {
Dhruv Srivastava753895c2022-11-26 00:30:23 +05301367 const addedAccounts = [
1368 ...(this.reviewersList?.additions() ?? []),
1369 ...(this.ccsList?.additions() ?? []),
1370 ];
1371 if (addedAccounts.length > 0) {
1372 fireAlert(this, 'Reviewers are not notified for WIP changes');
1373 }
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001374 }
1375
Dhruv Srivastava8bdda3b2022-08-30 19:11:31 +02001376 this.disabled = true;
1377
Dhruv80dd5ee2022-04-06 13:34:02 +02001378 const reason = getReplyByReason(this.account, this.serverConfig);
Ben Rohlfs1af9ec32020-08-26 12:51:06 +02001379
David Ostrovsky954e5082021-05-05 12:28:13 +02001380 reviewInput.ignore_automatic_attention_set_rules = true;
1381 reviewInput.add_to_attention_set = [];
Dhruv Srivastava71007ec2022-09-09 09:03:42 +02001382 const allAccounts = this.allAccounts();
1383
1384 const newAttentionSetAdditions: AccountInfo[] = Array.from(
1385 this.newAttentionSet
1386 )
1387 .filter(user => !this.currentAttentionSet.has(user))
1388 .map(user => allAccounts.find(a => getUserId(a) === user))
Ben Rohlfs9b2f7402022-10-17 12:40:31 +02001389 .filter(isDefined);
Dhruv Srivastava71007ec2022-09-09 09:03:42 +02001390
1391 const newAttentionSetUsers = (
1392 await Promise.all(
Chris Poucet4ed2c5d2022-10-24 22:28:15 +02001393 newAttentionSetAdditions.map(a =>
1394 this.getAccountsModel().fillDetails(a)
1395 )
Dhruv Srivastava71007ec2022-09-09 09:03:42 +02001396 )
Ben Rohlfs9b2f7402022-10-17 12:40:31 +02001397 ).filter(isDefined);
Dhruv Srivastava71007ec2022-09-09 09:03:42 +02001398
1399 for (const user of newAttentionSetUsers) {
Ben Rohlfsb7082e12023-01-23 11:43:48 +01001400 const reason =
1401 getMentionedReason(
1402 this.draftCommentThreads,
1403 this.account,
1404 user,
1405 this.serverConfig
1406 ) ?? '';
Dhruv Srivastava71007ec2022-09-09 09:03:42 +02001407 reviewInput.add_to_attention_set.push({user: getUserId(user), reason});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001408 }
David Ostrovsky954e5082021-05-05 12:28:13 +02001409 reviewInput.remove_from_attention_set = [];
Dhruv80dd5ee2022-04-06 13:34:02 +02001410 for (const user of this.currentAttentionSet) {
1411 if (!this.newAttentionSet.has(user)) {
David Ostrovsky954e5082021-05-05 12:28:13 +02001412 reviewInput.remove_from_attention_set.push({user, reason});
1413 }
1414 }
1415 this.reportAttentionSetChanges(
Dhruv80dd5ee2022-04-06 13:34:02 +02001416 this.attentionExpanded,
David Ostrovsky954e5082021-05-05 12:28:13 +02001417 reviewInput.add_to_attention_set,
1418 reviewInput.remove_from_attention_set
1419 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001420
Dhruv Srivastavae8b86392022-10-20 17:17:21 +02001421 await this.patchsetLevelGrComment?.save();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001422
Dhruv Srivastava012e36a2021-05-14 12:09:44 +02001423 assertIsDefined(this.change, 'change');
Dhruv Srivastava82f6b202022-08-03 15:22:20 +02001424 reviewInput.reviewers = this.computeReviewers();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001425
Dhruv80dd5ee2022-04-06 13:34:02 +02001426 const errFn = (r?: Response | null) => this.handle400Error(r);
1427 return this.saveReview(reviewInput, errFn)
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001428 .then(response => {
1429 if (!response) {
1430 // Null or undefined response indicates that an error handler
1431 // took responsibility, so just return.
Dhruv Srivastava57ae7472021-05-05 11:42:03 +02001432 return;
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001433 }
1434 if (!response.ok) {
Ben Rohlfsea4cb7e2020-12-03 16:00:00 +01001435 fireServerError(response);
Dhruv Srivastava57ae7472021-05-05 11:42:03 +02001436 return;
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001437 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001438
Chris Poucetafd0f7c2022-10-04 10:04:43 +00001439 this.patchsetLevelDraftMessage = '';
Dhruv80dd5ee2022-04-06 13:34:02 +02001440 this.includeComments = true;
Ben Rohlfs44f01042023-02-18 13:27:57 +01001441 fireNoBubble(this, 'send', {});
Ben Rohlfs62278c82021-03-12 11:35:56 +01001442 fireIronAnnounce(this, 'Reply sent');
Dhruv Srivastava57ae7472021-05-05 11:42:03 +02001443 return;
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001444 })
1445 .then(result => {
1446 this.disabled = false;
1447 return result;
1448 })
1449 .catch(err => {
1450 this.disabled = false;
1451 throw err;
1452 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001453 }
1454
Dhruv80dd5ee2022-04-06 13:34:02 +02001455 focusOn(section?: FocusTarget) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001456 // Safeguard- always want to focus on something.
1457 if (!section || section === FocusTarget.ANY) {
Dhruv80dd5ee2022-04-06 13:34:02 +02001458 section = this.chooseFocusTarget();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001459 }
Dhruv Srivastava51b70092022-10-20 15:26:12 +02001460 whenVisible(this, () => {
1461 if (section === FocusTarget.REVIEWERS) {
1462 const reviewerEntry = this.reviewersList?.focusStart;
1463 reviewerEntry?.focus();
1464 } else if (section === FocusTarget.CCS) {
1465 const ccEntry = this.ccsList?.focusStart;
1466 ccEntry?.focus();
Dhruv Srivastavae8b86392022-10-20 17:17:21 +02001467 } else {
1468 this.patchsetLevelGrComment?.focus();
Dhruv Srivastava51b70092022-10-20 15:26:12 +02001469 }
1470 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001471 }
1472
Dhruv80dd5ee2022-04-06 13:34:02 +02001473 chooseFocusTarget() {
Ben Rohlfsead93662022-08-05 09:09:15 +02001474 if (!isOwner(this.change, this.account)) return FocusTarget.BODY;
1475 if (hasHumanReviewer(this.change)) return FocusTarget.BODY;
1476 return FocusTarget.REVIEWERS;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001477 }
1478
Dhruv Srivastavaed173952022-08-08 09:37:36 +02001479 isOwner(account?: AccountInfo, change?: ParsedChangeInfo | ChangeInfo) {
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001480 if (!account || !change || !change.owner) return false;
1481 return account._account_id === change.owner._account_id;
1482 }
1483
Dhruv80dd5ee2022-04-06 13:34:02 +02001484 handle400Error(r?: Response | null) {
David Ostrovskyf91f9662021-02-22 19:34:37 +01001485 if (!r) throw new Error('Response is empty.');
Ben Rohlfsea4cb7e2020-12-03 16:00:00 +01001486 let response: Response = r;
Dhruv80dd5ee2022-04-06 13:34:02 +02001487 // A call to saveReview could fail with a server error if erroneous
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001488 // reviewers were requested. This is signalled with a 400 Bad Request
Ben Rohlfs9ead5412021-01-22 22:11:36 +01001489 // status. The default gr-rest-api error handling would result in a large
1490 // JSON response body being displayed to the user in the gr-error-manager
1491 // toast.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001492 //
1493 // We can modify the error handling behavior by passing this function
1494 // through to restAPI as a custom error handling function. Since we're
1495 // short-circuiting restAPI we can do our own response parsing and fire
1496 // the server-error ourselves.
1497 //
1498 this.disabled = false;
1499
1500 // Using response.clone() here, because getResponseObject() and
1501 // potentially the generic error handler will want to call text() on the
1502 // response object, which can only be done once per object.
Ben Rohlfs43935a42020-12-01 19:14:09 +01001503 const jsonPromise = this.restApiService.getResponseObject(response.clone());
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001504 return jsonPromise.then((parsed: ParsedJSON) => {
1505 const result = parsed as ReviewResult;
Frank Bordende52af02022-05-12 10:14:14 +02001506 // Only perform custom error handling for 400s and a parsable
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001507 // ReviewResult response.
Ben Rohlfsea4cb7e2020-12-03 16:00:00 +01001508 if (response.status === 400 && result && result.reviewers) {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001509 const errors: string[] = [];
1510 const addReviewers = Object.values(result.reviewers);
1511 addReviewers.forEach(r => errors.push(r.error ?? 'no explanation'));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001512 response = {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001513 ...response,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001514 ok: false,
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001515 text: () => Promise.resolve(errors.join(', ')),
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001516 };
1517 }
Ben Rohlfsea4cb7e2020-12-03 16:00:00 +01001518 fireServerError(response);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001519 });
1520 }
1521
Dhruv80dd5ee2022-04-06 13:34:02 +02001522 computeHideDraftList(draftCommentThreads?: CommentThread[]) {
Ben Rohlfs3cf22a72020-06-02 13:47:52 +02001523 return !draftCommentThreads || draftCommentThreads.length === 0;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001524 }
1525
Dhruv80dd5ee2022-04-06 13:34:02 +02001526 computeDraftsTitle(draftCommentThreads?: CommentThread[]) {
Ben Rohlfs3cf22a72020-06-02 13:47:52 +02001527 const total = draftCommentThreads ? draftCommentThreads.length : 0;
Milutin Kristofic5b3f0872020-12-05 22:08:09 +01001528 return pluralize(total, 'Draft');
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001529 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001530
Dhruv80dd5ee2022-04-06 13:34:02 +02001531 computeMessagePlaceholder() {
1532 this.messagePlaceholder = this.canBeStarted
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001533 ? 'Add a note for your reviewers...'
1534 : 'Say something nice...';
1535 }
1536
Dhruv80dd5ee2022-04-06 13:34:02 +02001537 rebuildReviewerArrays() {
1538 if (!this.change?.owner || !this.change?.reviewers) return;
Dhruv Srivastavaed173952022-08-08 09:37:36 +02001539 const getAccounts = (state: ReviewerState) =>
1540 Object.values(this.change?.reviewers[state] ?? []).filter(
1541 account => account._account_id !== this.change!.owner._account_id
1542 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001543
Dhruv Srivastavaed173952022-08-08 09:37:36 +02001544 this.ccs = getAccounts(ReviewerState.CC);
1545 this.reviewers = getAccounts(ReviewerState.REVIEWER);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001546 }
1547
Dhruv80dd5ee2022-04-06 13:34:02 +02001548 handleAttentionModify() {
1549 this.attentionExpanded = true;
Ben Rohlfs6a0a1072020-10-16 15:01:34 +02001550 }
1551
Dhruv80dd5ee2022-04-06 13:34:02 +02001552 onAttentionExpandedChange() {
Ben Rohlfs91675d72020-07-24 09:55:54 +02001553 // If the attention-detail section is expanded without dispatching this
1554 // event, then the dialog may expand beyond the screen's bottom border.
Ben Rohlfs44f01042023-02-18 13:27:57 +01001555 fire(this, 'iron-resize', {});
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001556 }
1557
Dhruv80dd5ee2022-04-06 13:34:02 +02001558 computeAttentionButtonTitle(sendDisabled?: boolean) {
Ben Rohlfs43f8e5f2020-10-09 10:36:29 +02001559 return sendDisabled
1560 ? 'Modify the attention set by adding a comment or use the account ' +
1561 'hovercard in the change page.'
1562 : 'Edit attention set changes';
1563 }
1564
Dhruv80dd5ee2022-04-06 13:34:02 +02001565 handleAttentionClick(e: Event) {
Dhruv Srivastava7f77a612022-08-17 11:38:03 +02001566 const targetAccount = (e.target as GrAccountChip)?.account;
1567 if (!targetAccount) return;
Dhruv Srivastava462eb392022-08-18 10:42:11 +02001568 const id = getUserId(targetAccount);
Dhruv Srivastava7f77a612022-08-17 11:38:03 +02001569 if (!id || !this.account || !this.change?.owner) return;
Ben Rohlfs1af9ec32020-08-26 12:51:06 +02001570
Dhruv Srivastavaa1882db2022-08-18 10:16:26 +02001571 const self = id === getUserId(this.account) ? '_SELF' : '';
1572 const role = id === getUserId(this.change.owner) ? 'OWNER' : '_REVIEWER';
Ben Rohlfs1af9ec32020-08-26 12:51:06 +02001573
Dhruv80dd5ee2022-04-06 13:34:02 +02001574 if (this.newAttentionSet.has(id)) {
1575 this.newAttentionSet.delete(id);
Milutin Kristofica7c24c12021-06-07 23:09:26 +02001576 this.reporting.reportInteraction(Interaction.ATTENTION_SET_CHIP, {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001577 action: `REMOVE${self}${role}`,
1578 });
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001579 } else {
Dhruv80dd5ee2022-04-06 13:34:02 +02001580 this.newAttentionSet.add(id);
Milutin Kristofica7c24c12021-06-07 23:09:26 +02001581 this.reporting.reportInteraction(Interaction.ATTENTION_SET_CHIP, {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001582 action: `ADD${self}${role}`,
1583 });
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001584 }
Ben Rohlfs1af9ec32020-08-26 12:51:06 +02001585
Dhruv Srivastava7a096c02022-07-11 10:22:51 +02001586 this.requestUpdate();
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001587 }
1588
Dhruv80dd5ee2022-04-06 13:34:02 +02001589 computeHasNewAttention(account?: AccountInfo) {
Dhruv Srivastava664cbb82022-09-19 11:43:27 +02001590 return !!(account && this.newAttentionSet?.has(getUserId(account)));
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001591 }
1592
Dhruv80dd5ee2022-04-06 13:34:02 +02001593 computeNewAttention() {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001594 if (
Dhruv80dd5ee2022-04-06 13:34:02 +02001595 this.account?._account_id === undefined ||
1596 this.change === undefined ||
Dhruv Srivastava11d44e42022-08-30 14:29:22 +02001597 this.includeComments === undefined
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001598 ) {
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001599 return;
1600 }
Ben Rohlfs31825d82020-10-02 18:08:04 +02001601 // The draft comments are only relevant for the attention set as long as the
1602 // user actually plans to publish their drafts.
Dhruv80dd5ee2022-04-06 13:34:02 +02001603 const draftCommentThreads = this.includeComments
1604 ? this.draftCommentThreads
1605 : [];
1606 const hasVote = !!this.labelsChanged;
1607 const isOwner = this.isOwner(this.account, this.change);
1608 const isUploader = this.uploader?._account_id === this.account._account_id;
Dhruv Srivastavadb01c602022-07-18 15:34:34 +02001609
Dhruv80dd5ee2022-04-06 13:34:02 +02001610 this.attentionCcsCount = removeServiceUsers(this.ccs).length;
1611 this.currentAttentionSet = new Set(
1612 Object.keys(this.change.attention_set || {}).map(
1613 id => Number(id) as AccountId
1614 )
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001615 );
Dhruv80dd5ee2022-04-06 13:34:02 +02001616 const newAttention = new Set(this.currentAttentionSet);
Dhruv Srivastavadb01c602022-07-18 15:34:34 +02001617
1618 for (const user of this.mentionedUsersInUnresolvedDrafts) {
Dhruv Srivastava664cbb82022-09-19 11:43:27 +02001619 newAttention.add(getUserId(user));
Dhruv Srivastavadb01c602022-07-18 15:34:34 +02001620 }
1621
Dhruv80dd5ee2022-04-06 13:34:02 +02001622 if (this.change.status === ChangeStatus.NEW) {
Ben Rohlfsf8f0fa82020-08-11 16:18:58 +02001623 // Add everyone that the user is replying to in a comment thread.
Dhruv80dd5ee2022-04-06 13:34:02 +02001624 this.computeCommentAccounts(draftCommentThreads).forEach(id =>
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001625 newAttention.add(id)
Ben Rohlfsf8f0fa82020-08-11 16:18:58 +02001626 );
1627 // Remove the current user.
Dhruv80dd5ee2022-04-06 13:34:02 +02001628 newAttention.delete(this.account._account_id);
Ben Rohlfs834b5912020-10-13 16:52:24 +02001629 // Add all new reviewers, but not the current reviewer, if they are also
1630 // sending a draft or a label vote.
1631 const notIsReviewerAndHasDraftOrLabel = (r: AccountInfo) =>
Dhruv80dd5ee2022-04-06 13:34:02 +02001632 !(
1633 r._account_id === this.account!._account_id &&
Chris Poucet54d2abc2022-04-28 11:51:35 +02001634 (this.hasDrafts() || hasVote)
Dhruv80dd5ee2022-04-06 13:34:02 +02001635 );
1636 this.reviewers
1637 .filter(r => isAccount(r))
Dhruv Srivastava4a9892e2022-08-03 17:27:18 +02001638 .filter(
1639 r =>
1640 isAccountNewlyAdded(r, ReviewerState.REVIEWER, this.change) ||
1641 (this.canBeStarted && isOwner)
1642 )
Ben Rohlfs834b5912020-10-13 16:52:24 +02001643 .filter(notIsReviewerAndHasDraftOrLabel)
Dhruv80dd5ee2022-04-06 13:34:02 +02001644 .forEach(r => newAttention.add((r as AccountInfo)._account_id!));
Ben Rohlfs834b5912020-10-13 16:52:24 +02001645 // Add owner and uploader, if someone else replies.
Chris Poucet54d2abc2022-04-28 11:51:35 +02001646 if (this.hasDrafts() || hasVote) {
Dhruv80dd5ee2022-04-06 13:34:02 +02001647 if (this.uploader?._account_id && !isUploader) {
1648 newAttention.add(this.uploader._account_id);
Ben Rohlfs834b5912020-10-13 16:52:24 +02001649 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001650 if (this.change.owner?._account_id && !isOwner) {
1651 newAttention.add(this.change.owner._account_id);
Ben Rohlfsf8f0fa82020-08-11 16:18:58 +02001652 }
1653 }
1654 } else {
1655 // The only reason for adding someone to the attention set for merged or
Ben Rohlfs31825d82020-10-02 18:08:04 +02001656 // abandoned changes is that someone makes a comment thread unresolved.
1657 const hasUnresolvedDraft = draftCommentThreads.some(isUnresolved);
Dhruv80dd5ee2022-04-06 13:34:02 +02001658 if (this.change.owner && hasUnresolvedDraft) {
1659 // A change owner must have an account_id.
1660 newAttention.add(this.change.owner._account_id!);
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001661 }
Ben Rohlfsf8f0fa82020-08-11 16:18:58 +02001662 // Remove the current user.
Dhruv80dd5ee2022-04-06 13:34:02 +02001663 newAttention.delete(this.account._account_id);
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001664 }
Ben Rohlfs116ca522020-07-28 16:50:45 +02001665 // Finally make sure that everyone in the attention set is still active as
1666 // owner, reviewer or cc.
Dhruv80dd5ee2022-04-06 13:34:02 +02001667 const allAccountIds = this.allAccounts()
Dhruv Srivastava462eb392022-08-18 10:42:11 +02001668 .map(a => getUserId(a))
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001669 .filter(id => !!id);
Dhruv Srivastavadb01c602022-07-18 15:34:34 +02001670 this.newAttentionSet = new Set([
1671 ...[...newAttention].filter(id => allAccountIds.includes(id)),
1672 ]);
1673
Dhruv Srivastava4a03a142022-07-11 11:34:50 +02001674 this.attentionExpanded = this.computeShowAttentionTip();
Ben Rohlfs0a4705a2020-09-30 13:39:15 +02001675 }
1676
Dhruv Srivastava4a03a142022-07-11 11:34:50 +02001677 computeShowAttentionTip() {
1678 if (
1679 !this.account ||
Dhruv Srivastavafcb63502022-07-11 14:42:00 +02001680 !this.change?.owner ||
Dhruv Srivastava4a03a142022-07-11 11:34:50 +02001681 !this.currentAttentionSet ||
1682 !this.newAttentionSet
1683 )
Ben Rohlfs0a4705a2020-09-30 13:39:15 +02001684 return false;
Dhruv Srivastavafcb63502022-07-11 14:42:00 +02001685 const isOwner = this.account._account_id === this.change.owner._account_id;
Dhruv Srivastava4a03a142022-07-11 11:34:50 +02001686 const addedIds = [...this.newAttentionSet].filter(
1687 id => !this.currentAttentionSet.has(id)
Ben Rohlfs0a4705a2020-09-30 13:39:15 +02001688 );
1689 return isOwner && addedIds.length > 2;
Ben Rohlfs116ca522020-07-28 16:50:45 +02001690 }
1691
Dhruv80dd5ee2022-04-06 13:34:02 +02001692 computeCommentAccounts(threads: CommentThread[]) {
Milutin Kristoficbd445802021-10-20 21:00:21 +02001693 const crLabel = this.change?.labels?.[StandardLabels.CODE_REVIEW];
Ben Rohlfs31825d82020-10-02 18:08:04 +02001694 const maxCrVoteAccountIds = getMaxAccounts(crLabel).map(a => a._account_id);
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001695 const accountIds = new Set<AccountId>();
Ben Rohlfs116ca522020-07-28 16:50:45 +02001696 threads.forEach(thread => {
Ben Rohlfs31825d82020-10-02 18:08:04 +02001697 const unresolved = isUnresolved(thread);
Ben Rohlfs116ca522020-07-28 16:50:45 +02001698 thread.comments.forEach(comment => {
1699 if (comment.author) {
Dhruv80dd5ee2022-04-06 13:34:02 +02001700 // A comment author must have an account_id.
Ben Rohlfs31825d82020-10-02 18:08:04 +02001701 const authorId = comment.author._account_id!;
1702 const hasGivenMaxReviewVote = maxCrVoteAccountIds.includes(authorId);
1703 if (unresolved || !hasGivenMaxReviewVote) accountIds.add(authorId);
Ben Rohlfs116ca522020-07-28 16:50:45 +02001704 }
1705 });
1706 });
1707 return accountIds;
Dmitrii Filippov50aa4c22020-03-18 16:05:11 +01001708 }
1709
Dhruv80dd5ee2022-04-06 13:34:02 +02001710 computeShowNoAttentionUpdate() {
1711 return this.sendDisabled || this.computeNewAttentionAccounts().length === 0;
Ben Rohlfs91675d72020-07-24 09:55:54 +02001712 }
1713
Dhruv80dd5ee2022-04-06 13:34:02 +02001714 computeDoNotUpdateMessage() {
1715 if (!this.currentAttentionSet || !this.newAttentionSet) return '';
1716 if (
1717 this.sendDisabled ||
1718 areSetsEqual(this.currentAttentionSet, this.newAttentionSet)
1719 ) {
Ben Rohlfsd6a47662020-10-01 14:42:47 +02001720 return 'No changes to the attention set.';
1721 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001722 if (containsAll(this.currentAttentionSet, this.newAttentionSet)) {
Ben Rohlfsd6a47662020-10-01 14:42:47 +02001723 return 'No additions to the attention set.';
1724 }
Milutin Kristofic380b9f62020-12-03 09:24:17 +01001725 this.reporting.error(
Kamil Musinf0ece022022-10-14 15:22:44 +02001726 'computeDoNotUpdateMessage',
Milutin Kristofic380b9f62020-12-03 09:24:17 +01001727 new Error(
Dhruv80dd5ee2022-04-06 13:34:02 +02001728 'computeDoNotUpdateMessage()' +
Milutin Kristofic380b9f62020-12-03 09:24:17 +01001729 'should not be called when users were added to the attention set.'
1730 )
Ben Rohlfsd6a47662020-10-01 14:42:47 +02001731 );
1732 return '';
1733 }
1734
Dhruv80dd5ee2022-04-06 13:34:02 +02001735 computeNewAttentionAccounts(): AccountInfo[] {
1736 if (
1737 this.currentAttentionSet === undefined ||
1738 this.newAttentionSet === undefined
1739 ) {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001740 return [];
1741 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001742 return [...this.newAttentionSet]
1743 .filter(id => !this.currentAttentionSet.has(id))
1744 .map(id => this.findAccountById(id))
1745 .filter(account => !!account) as AccountInfo[];
Ben Rohlfs91675d72020-07-24 09:55:54 +02001746 }
1747
Dhruv Srivastava462eb392022-08-18 10:42:11 +02001748 findAccountById(userId: UserId) {
Dhruv Srivastava664cbb82022-09-19 11:43:27 +02001749 return this.allAccounts().find(r => getUserId(r) === userId);
Ben Rohlfs116ca522020-07-28 16:50:45 +02001750 }
1751
Dhruv80dd5ee2022-04-06 13:34:02 +02001752 allAccounts() {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001753 let allAccounts: (AccountInfoInput | GroupInfoInput)[] = [];
Ben Rohlfs91675d72020-07-24 09:55:54 +02001754 if (this.change && this.change.owner) allAccounts.push(this.change.owner);
Dhruv80dd5ee2022-04-06 13:34:02 +02001755 if (this.uploader) allAccounts.push(this.uploader);
1756 if (this.reviewers) allAccounts = [...allAccounts, ...this.reviewers];
1757 if (this.ccs) allAccounts = [...allAccounts, ...this.ccs];
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001758 return removeServiceUsers(allAccounts.filter(isAccount));
Ben Rohlfs417a3fd2020-08-11 16:53:20 +02001759 }
1760
Dhruv80dd5ee2022-04-06 13:34:02 +02001761 computeUploader() {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001762 if (
Dhruv80dd5ee2022-04-06 13:34:02 +02001763 !this.change?.current_revision ||
1764 !this.change?.revisions?.[this.change.current_revision]
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001765 ) {
Dhruv80dd5ee2022-04-06 13:34:02 +02001766 this.uploader = undefined;
1767 return;
Ben Rohlfs116ca522020-07-28 16:50:45 +02001768 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001769 const rev = this.change.revisions[this.change.current_revision];
Ben Rohlfs116ca522020-07-28 16:50:45 +02001770
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001771 if (
1772 !rev.uploader ||
Dhruv80dd5ee2022-04-06 13:34:02 +02001773 this.change?.owner._account_id === rev.uploader._account_id
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001774 ) {
Dhruv80dd5ee2022-04-06 13:34:02 +02001775 this.uploader = undefined;
1776 return;
Ben Rohlfs116ca522020-07-28 16:50:45 +02001777 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001778 this.uploader = rev.uploader;
Ben Rohlfs116ca522020-07-28 16:50:45 +02001779 }
1780
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001781 /**
1782 * Generates a function to filter out reviewer/CC entries. When isCCs is
Dhruv80dd5ee2022-04-06 13:34:02 +02001783 * truthy, the function filters out entries that already exist in this.ccs.
1784 * When falsy, the function filters entries that exist in this.reviewers.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001785 */
Dhruv80dd5ee2022-04-06 13:34:02 +02001786 filterReviewerSuggestionGenerator(
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001787 isCCs: boolean
1788 ): (input: Suggestion) => boolean {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001789 return suggestion => {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001790 let entry: AccountInfo | GroupInfo;
1791 if (isReviewerAccountSuggestion(suggestion)) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001792 entry = suggestion.account;
Dhruv Srivastavafcb63502022-07-11 14:42:00 +02001793 if (entry._account_id === this.change?.owner?._account_id) {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001794 return false;
1795 }
1796 } else if (isReviewerGroupSuggestion(suggestion)) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001797 entry = suggestion.group;
1798 } else {
Ben Rohlfsc0ca5552021-06-08 09:47:40 +02001799 this.reporting.error(
Kamil Musinf0ece022022-10-14 15:22:44 +02001800 'Reviewer Suggestion',
Ben Rohlfsc0ca5552021-06-08 09:47:40 +02001801 new Error(`Suggestion is neither account nor group: ${suggestion}`)
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001802 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001803 return false;
1804 }
1805
Dhruv Srivastavaa1882db2022-08-18 10:16:26 +02001806 const key = getUserId(entry);
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001807 const finder = (entry: AccountInfo | GroupInfo) =>
Dhruv Srivastavaa1882db2022-08-18 10:16:26 +02001808 getUserId(entry) === key;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001809 if (isCCs) {
Dhruv80dd5ee2022-04-06 13:34:02 +02001810 return this.ccs.find(finder) === undefined;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001811 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001812 return this.reviewers.find(finder) === undefined;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001813 };
1814 }
1815
Dhruv80dd5ee2022-04-06 13:34:02 +02001816 cancelTapHandler(e: Event) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001817 e.preventDefault();
1818 this.cancel();
1819 }
1820
Dhruv Srivastava82302142022-09-07 16:36:39 +02001821 async cancel() {
Ben Rohlfsc1c6afd2021-02-18 13:13:22 +01001822 assertIsDefined(this.change, 'change');
Dhruv Srivastavafcb63502022-07-11 14:42:00 +02001823 if (!this.change?.owner) throw new Error('missing required owner property');
Ben Rohlfs44f01042023-02-18 13:27:57 +01001824 fireNoBubble(this, 'cancel', {});
Dhruv Srivastavae8b86392022-10-20 17:17:21 +02001825 await this.patchsetLevelGrComment?.save();
Dhruv80dd5ee2022-04-06 13:34:02 +02001826 this.rebuildReviewerArrays();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001827 }
1828
Dhruv80dd5ee2022-04-06 13:34:02 +02001829 saveClickHandler(e: Event) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001830 e.preventDefault();
Dhruv80dd5ee2022-04-06 13:34:02 +02001831 if (!this.ccsList?.submitEntryText()) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001832 // Do not proceed with the save if there is an invalid email entry in
1833 // the text field of the CC entry.
1834 return;
1835 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001836 this.send(this.includeComments, false);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001837 }
1838
Dhruv80dd5ee2022-04-06 13:34:02 +02001839 sendTapHandler(e: Event) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001840 e.preventDefault();
Dhruv80dd5ee2022-04-06 13:34:02 +02001841 this.submit();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001842 }
1843
Dhruv80dd5ee2022-04-06 13:34:02 +02001844 submit() {
1845 if (!this.ccsList?.submitEntryText()) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001846 // Do not proceed with the send if there is an invalid email entry in
1847 // the text field of the CC entry.
1848 return;
1849 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001850 if (this.sendDisabled) {
Milutin Kristofic860fe4d2020-11-23 16:13:45 +01001851 fireAlert(this, EMPTY_REPLY_MESSAGE);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001852 return;
1853 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001854 return this.send(this.includeComments, this.canBeStarted).catch(err => {
Ben Rohlfs6bb90532023-02-17 18:55:56 +01001855 fireError(this, `Error submitting review ${err}`);
Dhruv Srivastava57ae7472021-05-05 11:42:03 +02001856 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001857 }
1858
Dhruv80dd5ee2022-04-06 13:34:02 +02001859 saveReview(review: ReviewInput, errFn?: ErrorCallback) {
Ben Rohlfsc1c6afd2021-02-18 13:13:22 +01001860 assertIsDefined(this.change, 'change');
Paladox noned1e16172023-02-22 19:00:06 +00001861 assertIsDefined(this.latestPatchNum, 'latestPatchNum');
Ben Rohlfs43935a42020-12-01 19:14:09 +01001862 return this.restApiService.saveChangeReview(
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001863 this.change._number,
Paladox noned1e16172023-02-22 19:00:06 +00001864 this.latestPatchNum,
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001865 review,
1866 errFn
1867 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001868 }
1869
Dhruv80dd5ee2022-04-06 13:34:02 +02001870 pendingConfirmationUpdated(reviewer: RawAccountInput | null) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001871 if (reviewer === null) {
Dhruv Srivastavad6bd9df2022-11-14 19:46:47 +05301872 this.reviewerConfirmationModal?.close();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001873 } else {
Dhruv80dd5ee2022-04-06 13:34:02 +02001874 this.pendingConfirmationDetails =
1875 this.ccPendingConfirmation || this.reviewerPendingConfirmation;
Dhruv Srivastavad6bd9df2022-11-14 19:46:47 +05301876 this.reviewerConfirmationModal?.showModal();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001877 }
1878 }
1879
Dhruv80dd5ee2022-04-06 13:34:02 +02001880 confirmPendingReviewer() {
Dhruv Srivastavac2e38722023-01-02 11:46:27 +01001881 this.reviewerConfirmationModal?.close();
Dhruv80dd5ee2022-04-06 13:34:02 +02001882 if (this.ccPendingConfirmation) {
1883 this.ccsList?.confirmGroup(this.ccPendingConfirmation.group);
1884 this.focusOn(FocusTarget.CCS);
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001885 return;
1886 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001887 if (this.reviewerPendingConfirmation) {
1888 this.reviewersList?.confirmGroup(this.reviewerPendingConfirmation.group);
1889 this.focusOn(FocusTarget.REVIEWERS);
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001890 return;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001891 }
Milutin Kristofic380b9f62020-12-03 09:24:17 +01001892 this.reporting.error(
Kamil Musinf0ece022022-10-14 15:22:44 +02001893 'confirmPendingReviewer',
Dhruv80dd5ee2022-04-06 13:34:02 +02001894 new Error('confirmPendingReviewer called without pending confirm')
Milutin Kristofic380b9f62020-12-03 09:24:17 +01001895 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001896 }
1897
Dhruv80dd5ee2022-04-06 13:34:02 +02001898 cancelPendingReviewer() {
Dhruv Srivastavac2e38722023-01-02 11:46:27 +01001899 this.reviewerConfirmationModal?.close();
Dhruv80dd5ee2022-04-06 13:34:02 +02001900 this.ccPendingConfirmation = null;
1901 this.reviewerPendingConfirmation = null;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001902
Dhruv80dd5ee2022-04-06 13:34:02 +02001903 const target = this.ccPendingConfirmation
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001904 ? FocusTarget.CCS
1905 : FocusTarget.REVIEWERS;
Dhruv80dd5ee2022-04-06 13:34:02 +02001906 this.focusOn(target);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001907 }
1908
Dhruv80dd5ee2022-04-06 13:34:02 +02001909 handleAccountTextEntry() {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001910 // When either of the account entries has input added to the autocomplete,
1911 // it should trigger the save button to enable/
1912 //
1913 // Note: if the text is removed, the save button will not get disabled.
Dhruv80dd5ee2022-04-06 13:34:02 +02001914 this.reviewersMutated = true;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001915 }
1916
Dhruv Srivastavadb01c602022-07-18 15:34:34 +02001917 private alreadyExists(ccs: AccountInput[], user: AccountInfoInput) {
1918 return ccs
1919 .filter(cc => isAccount(cc))
Dhruv Srivastava664cbb82022-09-19 11:43:27 +02001920 .some(cc => getUserId(cc) === getUserId(user));
Dhruv Srivastavadb01c602022-07-18 15:34:34 +02001921 }
1922
1923 private isAlreadyReviewerOrCC(user: AccountInfo) {
1924 return (
1925 this.alreadyExists(this.reviewers, user) ||
1926 this.alreadyExists(this._ccs, user)
1927 );
1928 }
1929
Dhruv80dd5ee2022-04-06 13:34:02 +02001930 getLabelScores(): GrLabelScores {
1931 return this.labelScores || queryAndAssert(this, 'gr-label-scores');
Dhruv Srivastava5dbf4672021-06-29 09:06:20 +02001932 }
1933
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001934 _handleLabelsChanged() {
Dhruv80dd5ee2022-04-06 13:34:02 +02001935 this.labelsChanged =
Dhruv Srivastava5dbf4672021-06-29 09:06:20 +02001936 Object.keys(this.getLabelScores().getLabelValues(false)).length !== 0;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001937 }
1938
Dhruv80dd5ee2022-04-06 13:34:02 +02001939 handleReviewersChanged(e: ValueChangedEvent<(AccountInfo | GroupInfo)[]>) {
Dhruv Srivastavadb01c602022-07-18 15:34:34 +02001940 this.reviewers = [...e.detail.value];
Dhruv80dd5ee2022-04-06 13:34:02 +02001941 this.reviewersMutated = true;
Milutin Kristofic508d3eb2022-04-04 14:15:43 +02001942 }
1943
Dhruv80dd5ee2022-04-06 13:34:02 +02001944 handleCcsChanged(e: ValueChangedEvent<(AccountInfo | GroupInfo)[]>) {
Dhruv Srivastavadb01c602022-07-18 15:34:34 +02001945 this.ccs = [...e.detail.value];
Dhruv80dd5ee2022-04-06 13:34:02 +02001946 this.reviewersMutated = true;
Milutin Kristofic508d3eb2022-04-04 14:15:43 +02001947 }
1948
Dhruv80dd5ee2022-04-06 13:34:02 +02001949 handleReviewersConfirmationChanged(
Milutin Kristofic508d3eb2022-04-04 14:15:43 +02001950 e: ValueChangedEvent<SuggestedReviewerGroupInfo | null>
1951 ) {
Dhruv80dd5ee2022-04-06 13:34:02 +02001952 this.reviewerPendingConfirmation = e.detail.value;
Milutin Kristofic508d3eb2022-04-04 14:15:43 +02001953 }
1954
Dhruv80dd5ee2022-04-06 13:34:02 +02001955 handleCcsConfirmationChanged(
Milutin Kristofic508d3eb2022-04-04 14:15:43 +02001956 e: ValueChangedEvent<SuggestedReviewerGroupInfo | null>
1957 ) {
Dhruv80dd5ee2022-04-06 13:34:02 +02001958 this.ccPendingConfirmation = e.detail.value;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001959 }
1960
1961 _reload() {
Ben Rohlfs66367b62021-04-30 10:06:32 +02001962 fireReload(this, true);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001963 this.cancel();
1964 }
1965
Dhruv80dd5ee2022-04-06 13:34:02 +02001966 computeSendButtonLabel() {
1967 this.sendButtonLabel = this.canBeStarted
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001968 ? ButtonLabels.SEND + ' and ' + ButtonLabels.START_REVIEW
1969 : ButtonLabels.SEND;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001970 }
1971
Dhruv80dd5ee2022-04-06 13:34:02 +02001972 computeSendButtonTooltip(canBeStarted?: boolean, commentEditing?: boolean) {
Milutin Kristofic216e8852020-12-17 09:18:11 +01001973 if (commentEditing) {
1974 return ButtonTooltips.DISABLED_COMMENT_EDITING;
1975 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001976 return canBeStarted ? ButtonTooltips.START_REVIEW : ButtonTooltips.SEND;
1977 }
1978
Dhruv80dd5ee2022-04-06 13:34:02 +02001979 computeSavingLabelClass(savingComments: boolean) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001980 return savingComments ? 'saving' : '';
1981 }
1982
Dhruv80dd5ee2022-04-06 13:34:02 +02001983 computeSendButtonDisabled() {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001984 if (
Dhruv80dd5ee2022-04-06 13:34:02 +02001985 this.canBeStarted === undefined ||
Chris Poucetafd0f7c2022-10-04 10:04:43 +00001986 this.patchsetLevelDraftMessage === undefined ||
Dhruv80dd5ee2022-04-06 13:34:02 +02001987 this.reviewersMutated === undefined ||
1988 this.labelsChanged === undefined ||
1989 this.includeComments === undefined ||
1990 this.disabled === undefined ||
1991 this.commentEditing === undefined ||
1992 this.change?.labels === undefined ||
1993 this.account === undefined
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001994 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001995 return undefined;
1996 }
Dhruv80dd5ee2022-04-06 13:34:02 +02001997 if (this.commentEditing || this.disabled) {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02001998 return true;
1999 }
Dhruv80dd5ee2022-04-06 13:34:02 +02002000 if (this.canBeStarted === true) {
Ben Rohlfsbadc1342020-09-22 10:04:23 +02002001 return false;
2002 }
Dhruv80dd5ee2022-04-06 13:34:02 +02002003 const existingVote = Object.values(this.change.labels).some(
2004 label =>
2005 isDetailedLabelInfo(label) && getApprovalInfo(label, this.account!)
Frank Borden891b73e2021-01-29 16:43:50 -08002006 );
Dhruv80dd5ee2022-04-06 13:34:02 +02002007 const revotingOrNewVote = this.labelsChanged || existingVote;
Dhruv Srivastava0b1f2292022-09-21 10:25:43 +02002008 const hasDrafts =
2009 (this.includeComments && this.draftCommentThreads.length > 0) ||
Chris Poucetafd0f7c2022-10-04 10:04:43 +00002010 this.patchsetLevelDraftMessage.length > 0;
2011 return (
2012 !hasDrafts &&
2013 !this.patchsetLevelDraftMessage.length &&
2014 !this.reviewersMutated &&
2015 !revotingOrNewVote
2016 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01002017 }
2018
Dhruv80dd5ee2022-04-06 13:34:02 +02002019 computePatchSetWarning() {
Paladox noned1e16172023-02-22 19:00:06 +00002020 let str = `Patch ${this.latestPatchNum} is not latest.`;
Dhruv80dd5ee2022-04-06 13:34:02 +02002021 if (this.labelsChanged) {
Dhruv Srivastava520ecb92021-06-17 10:56:26 +02002022 str += ' Voting may have no effect.';
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01002023 }
2024 return str;
2025 }
2026
Ben Rohlfsbadc1342020-09-22 10:04:23 +02002027 setPluginMessage(message: string) {
Dhruv80dd5ee2022-04-06 13:34:02 +02002028 this.pluginMessage = message;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01002029 }
2030
Dhruv80dd5ee2022-04-06 13:34:02 +02002031 sendDisabledChanged() {
Ben Rohlfs44f01042023-02-18 13:27:57 +01002032 fireNoBubbleNoCompose(this, 'send-disabled-changed', {});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01002033 }
2034
Dhruv Srivastavaed173952022-08-08 09:37:36 +02002035 getReviewerSuggestionsProvider(change?: ChangeInfo | ParsedChangeInfo) {
Dhruv80dd5ee2022-04-06 13:34:02 +02002036 if (!change) return;
Frank Borden59801032022-05-06 14:37:18 +02002037 const provider = new GrReviewerSuggestionsProvider(
Ben Rohlfs43935a42020-12-01 19:14:09 +01002038 this.restApiService,
Frank Bordende52af02022-05-12 10:14:14 +02002039 ReviewerState.REVIEWER,
Frank Borden59801032022-05-06 14:37:18 +02002040 this.serverConfig,
2041 this.isLoggedIn,
Frank Borden84da89b2022-06-15 14:28:05 +02002042 change
Ben Rohlfsbadc1342020-09-22 10:04:23 +02002043 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01002044 return provider;
2045 }
2046
Dhruv Srivastavaed173952022-08-08 09:37:36 +02002047 getCcSuggestionsProvider(change?: ChangeInfo | ParsedChangeInfo) {
Dhruv80dd5ee2022-04-06 13:34:02 +02002048 if (!change) return;
Frank Borden59801032022-05-06 14:37:18 +02002049 const provider = new GrReviewerSuggestionsProvider(
Ben Rohlfs43935a42020-12-01 19:14:09 +01002050 this.restApiService,
Frank Bordende52af02022-05-12 10:14:14 +02002051 ReviewerState.CC,
Frank Borden59801032022-05-06 14:37:18 +02002052 this.serverConfig,
2053 this.isLoggedIn,
Frank Borden84da89b2022-06-15 14:28:05 +02002054 change
Ben Rohlfsbadc1342020-09-22 10:04:23 +02002055 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01002056 return provider;
2057 }
2058
Ben Rohlfsbadc1342020-09-22 10:04:23 +02002059 reportAttentionSetChanges(
2060 modified: boolean,
2061 addedSet?: AttentionSetInput[],
2062 removedSet?: AttentionSetInput[]
2063 ) {
Ben Rohlfsebf63542020-07-03 12:13:07 +02002064 const actions = modified ? ['MODIFIED'] : ['NOT_MODIFIED'];
Ben Rohlfsbadc1342020-09-22 10:04:23 +02002065 const ownerId =
2066 (this.change && this.change.owner && this.change.owner._account_id) || -1;
Dhruv80dd5ee2022-04-06 13:34:02 +02002067 const selfId = (this.account && this.account._account_id) || -1;
Ben Rohlfsbadc1342020-09-22 10:04:23 +02002068 for (const added of addedSet || []) {
Ben Rohlfsebf63542020-07-03 12:13:07 +02002069 const addedId = added.user;
2070 const self = addedId === selfId ? '_SELF' : '';
Dhruv80dd5ee2022-04-06 13:34:02 +02002071 const role = addedId === ownerId ? 'OWNER' : '_REVIEWER';
Ben Rohlfsebf63542020-07-03 12:13:07 +02002072 actions.push('ADD' + self + role);
2073 }
Ben Rohlfsbadc1342020-09-22 10:04:23 +02002074 for (const removed of removedSet || []) {
Ben Rohlfsebf63542020-07-03 12:13:07 +02002075 const removedId = removed.user;
2076 const self = removedId === selfId ? '_SELF' : '';
Dhruv80dd5ee2022-04-06 13:34:02 +02002077 const role = removedId === ownerId ? 'OWNER' : '_REVIEWER';
Ben Rohlfsebf63542020-07-03 12:13:07 +02002078 actions.push('REMOVE' + self + role);
2079 }
2080 this.reporting.reportInteraction('attention-set-actions', {actions});
2081 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01002082}
2083
Ben Rohlfsbadc1342020-09-22 10:04:23 +02002084declare global {
2085 interface HTMLElementTagNameMap {
2086 'gr-reply-dialog': GrReplyDialog;
2087 }
Ben Rohlfs5b3c6552023-02-18 13:02:46 +01002088 interface HTMLElementEventMap {
2089 /** Fired when the user presses the cancel button. */
Ben Rohlfse4217512023-02-20 22:38:24 +01002090 // prettier-ignore
Ben Rohlfs5b3c6552023-02-18 13:02:46 +01002091 'cancel': CustomEvent<{}>;
2092 /**
2093 * Fires when the reply dialog believes that the server side diff drafts
2094 * have been updated and need to be refreshed.
2095 */
2096 'comment-refresh': CustomEvent<{}>;
2097 /** Fired when a reply is successfully sent. */
Ben Rohlfse4217512023-02-20 22:38:24 +01002098 // prettier-ignore
Ben Rohlfs5b3c6552023-02-18 13:02:46 +01002099 'send': CustomEvent<{}>;
2100 /** Fires when the state of the send button (enabled/disabled) changes. */
2101 'send-disabled-changed': CustomEvent<{}>;
2102 }
Ben Rohlfsbadc1342020-09-22 10:04:23 +02002103}