blob: 04a85bf3d2997c9875bf6c836c1524676455e748 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001/* Copyright 2018 The Chromium Authors. All Rights Reserved.
2 *
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file or at
5 * https://developers.google.com/open-source/licenses/bsd
6 */
7
8/**
9 * This file contains JS functions that support a dialog for adding and removing
10 * issues from hotlists in Monorail.
11 */
12
13(function() {
14 window.__hotlists_dialog = window.__hotlists_dialog || {};
15
16 // An optional IssueRef.
17 // If set, we will not check for selected issues, and only add/remove issueRef
18 // instead.
19 window.__hotlists_dialog.issueRef = null;
20 // A function to be called with the modified hotlists. If issueRef is set, the
21 // hotlists for which the user is owner and the issue is part of will be
22 // passed as well.
23 window.__hotlists_dialog.onResponse = () => {};
24 // A function to be called if there was an error updating the hotlists.
25 window.__hotlists_dialog.onFailure = () => {};
26
27 /**
28 * A function to show the hotlist dialog.
29 * It is the only function exported by this module.
30 */
31 function ShowUpdateHotlistDialog() {
32 _FetchHotlists().then(_BuildDialog);
33 }
34
35 async function _CreateNewHotlistWithIssues() {
36 let selectedIssueRefs;
37 if (window.__hotlists_dialog.issueRef) {
38 selectedIssueRefs = [window.__hotlists_dialog.issueRef];
39 } else {
40 selectedIssueRefs = _GetSelectedIssueRefs();
41 }
42
43 const name = await _CheckNewHotlistName();
44 if (!name) {
45 return;
46 }
47
48 const message = {
49 name: name,
50 summary: 'Hotlist of bulk added issues',
51 issueRefs: selectedIssueRefs,
52 };
53 try {
54 await window.prpcClient.call(
55 'monorail.Features', 'CreateHotlist', message);
56 } catch (error) {
57 window.__hotlists_dialog.onFailure(error);
58 return;
59 }
60
61 const newHotlist = [name, window.CS_env.loggedInUserEmail];
62 const newIssueHotlists = [];
63 window.__hotlists_dialog._issueHotlists.forEach(
64 hotlist => newIssueHotlists.push(hotlist.split('_')));
65 newIssueHotlists.push(newHotlist);
66 window.__hotlists_dialog.onResponse([newHotlist], newIssueHotlists);
67 }
68
69 async function _UpdateIssuesInHotlists() {
70 const hotlistRefsAdd = _GetSelectedHotlists(
71 window.__hotlists_dialog._userHotlists);
72 const hotlistRefsRemove = _GetSelectedHotlists(
73 window.__hotlists_dialog._issueHotlists);
74 if (hotlistRefsAdd.length === 0 && hotlistRefsRemove.length === 0) {
75 alert('Please select/un-select some hotlists');
76 return;
77 }
78
79 let selectedIssueRefs;
80 if (window.__hotlists_dialog.issueRef) {
81 selectedIssueRefs = [window.__hotlists_dialog.issueRef];
82 } else {
83 selectedIssueRefs = _GetSelectedIssueRefs();
84 }
85
86 if (hotlistRefsAdd.length > 0) {
87 const message = {
88 hotlistRefs: hotlistRefsAdd,
89 issueRefs: selectedIssueRefs,
90 };
91 try {
92 await window.prpcClient.call(
93 'monorail.Features', 'AddIssuesToHotlists', message);
94 } catch (error) {
95 window.__hotlists_dialog.onFailure(error);
96 return;
97 }
98 hotlistRefsAdd.forEach(hotlist => {
99 window.__hotlists_dialog._issueHotlists.add(
100 hotlist.name + '_' + hotlist.owner.user_id);
101 });
102 }
103
104 if (hotlistRefsRemove.length > 0) {
105 const message = {
106 hotlistRefs: hotlistRefsRemove,
107 issueRefs: selectedIssueRefs,
108 };
109 try {
110 await window.prpcClient.call(
111 'monorail.Features', 'RemoveIssuesFromHotlists', message);
112 } catch (error) {
113 window.__hotlists_dialog.onFailure(error);
114 return;
115 }
116 hotlistRefsRemove.forEach(hotlist => {
117 window.__hotlists_dialog._issueHotlists.delete(
118 hotlist.name + '_' + hotlist.owner.user_id);
119 });
120 }
121
122 const modifiedHotlists = hotlistRefsAdd.concat(hotlistRefsRemove).map(
123 hotlist => [hotlist.name, hotlist.owner.user_id]);
124 const newIssueHotlists = [];
125 window.__hotlists_dialog._issueHotlists.forEach(
126 hotlist => newIssueHotlists.push(hotlist.split('_')));
127
128 window.__hotlists_dialog.onResponse(modifiedHotlists, newIssueHotlists);
129 }
130
131 async function _FetchHotlists() {
132 const userHotlistsMessage = {
133 user: {
134 display_name: window.CS_env.loggedInUserEmail,
135 }
136 };
137 const userHotlistsResponse = await window.prpcClient.call(
138 'monorail.Features', 'ListHotlistsByUser', userHotlistsMessage);
139
140 // Here we have the list of all hotlists owned by the user. We filter out
141 // the hotlists that already contain issueRef in the next paragraph of code.
142 window.__hotlists_dialog._userHotlists = new Set();
143 (userHotlistsResponse.hotlists || []).forEach(hotlist => {
144 window.__hotlists_dialog._userHotlists.add(
145 hotlist.name + '_' + hotlist.ownerRef.userId);
146 });
147
148 // Here we filter out the hotlists that are owned by the user, and that
149 // contain issueRef from _userHotlists and save them into _issueHotlists.
150 window.__hotlists_dialog._issueHotlists = new Set();
151 if (window.__hotlists_dialog.issueRef) {
152 const issueHotlistsMessage = {
153 issue: window.__hotlists_dialog.issueRef,
154 };
155 const issueHotlistsResponse = await window.prpcClient.call(
156 'monorail.Features', 'ListHotlistsByIssue', issueHotlistsMessage);
157 (issueHotlistsResponse.hotlists || []).forEach(hotlist => {
158 const hotlistRef = hotlist.name + '_' + hotlist.ownerRef.userId;
159 if (window.__hotlists_dialog._userHotlists.has(hotlistRef)) {
160 window.__hotlists_dialog._userHotlists.delete(hotlistRef);
161 window.__hotlists_dialog._issueHotlists.add(hotlistRef);
162 }
163 });
164 }
165 }
166
167 function _BuildDialog() {
168 const table = $('js-hotlists-table');
169
170 while (table.firstChild) {
171 table.removeChild(table.firstChild);
172 }
173
174 if (window.__hotlists_dialog._issueHotlists.size > 0) {
175 _UpdateRows(
176 table, 'Remove issues from:',
177 window.__hotlists_dialog._issueHotlists);
178 }
179 _UpdateRows(table, 'Add issues to:',
180 window.__hotlists_dialog._userHotlists);
181 _BuildCreateNewHotlist(table);
182
183 $('update-issues-hotlists').style.display = 'block';
184 $('save-issues-hotlists').addEventListener(
185 'click', _UpdateIssuesInHotlists);
186 $('cancel-update-hotlists').addEventListener('click', function() {
187 $('update-issues-hotlists').style.display = 'none';
188 });
189
190 }
191
192 function _BuildCreateNewHotlist(table) {
193 const inputTr = document.createElement('tr');
194 inputTr.classList.add('hotlist_rows');
195
196 const inputCell = document.createElement('td');
197 const input = document.createElement('input');
198 input.setAttribute('id', 'text_new_hotlist_name');
199 input.setAttribute('placeholder', 'New hotlist name');
200 // Hotlist changes are automatic and should be ignored by
201 // TKR_currentFormValues() and TKR_isDirty()
202 input.setAttribute('ignore-dirty', true);
203 input.addEventListener('input', _CheckNewHotlistName);
204 inputCell.appendChild(input);
205 inputTr.appendChild(inputCell);
206
207 const buttonCell = document.createElement('td');
208 const button = document.createElement('button');
209 button.setAttribute('id', 'create-new-hotlist');
210 button.addEventListener('click', _CreateNewHotlistWithIssues);
211 button.textContent = 'Create New Hotlist';
212 button.disabled = true;
213 buttonCell.appendChild(button);
214 inputTr.appendChild(buttonCell);
215
216 table.appendChild(inputTr);
217
218 const feedbackTr = document.createElement('tr');
219 feedbackTr.classList.add('hotlist_rows');
220
221 const feedbackCell = document.createElement('td');
222 feedbackCell.setAttribute('colspan', '2');
223 const feedback = document.createElement('span');
224 feedback.classList.add('fielderror');
225 feedback.setAttribute('id', 'hotlistnamefeedback');
226 feedbackCell.appendChild(feedback);
227 feedbackTr.appendChild(feedbackCell);
228
229 table.appendChild(feedbackTr);
230 }
231
232 function _UpdateRows(table, title, hotlists) {
233 const tr = document.createElement('tr');
234 tr.classList.add('hotlist_rows');
235 const addCell = document.createElement('td');
236 const add = document.createElement('b');
237 add.textContent = title;
238 addCell.appendChild(add);
239 tr.appendChild(addCell);
240 table.appendChild(tr);
241
242 hotlists.forEach(hotlist => {
243 const hotlistParts = hotlist.split('_');
244 const name = hotlistParts[0];
245
246 const tr = document.createElement('tr');
247 tr.classList.add('hotlist_rows');
248
249 const cbCell = document.createElement('td');
250 const cb = document.createElement('input');
251 cb.classList.add('checkRangeSelect');
252 cb.setAttribute('id', 'cb_hotlist_' + hotlist);
253 cb.setAttribute('type', 'checkbox');
254 // Hotlist changes are automatic and should be ignored by
255 // TKR_currentFormValues() and TKR_isDirty()
256 cb.setAttribute('ignore-dirty', true);
257 cbCell.appendChild(cb);
258
259 const nameCell = document.createElement('td');
260 const label = document.createElement('label');
261 label.htmlFor = cb.id;
262 label.textContent = name;
263 nameCell.appendChild(label);
264
265 tr.appendChild(cbCell);
266 tr.appendChild(nameCell);
267 table.appendChild(tr);
268 });
269 }
270
271 async function _CheckNewHotlistName() {
272 const name = $('text_new_hotlist_name').value;
273 const checkNameResponse = await window.prpcClient.call(
274 'monorail.Features', 'CheckHotlistName', {name});
275
276 if (checkNameResponse.error) {
277 $('hotlistnamefeedback').textContent = checkNameResponse.error;
278 $('create-new-hotlist').disabled = true;
279 return null;
280 }
281
282 $('hotlistnamefeedback').textContent = '';
283 $('create-new-hotlist').disabled = false;
284 return name;
285 }
286
287 /**
288 * Call GetSelectedIssuesRefs from tracker-editing.js and convert to an Array
289 * of IssueRef PBs.
290 */
291 function _GetSelectedIssueRefs() {
292 return GetSelectedIssuesRefs().map(issueRef => ({
293 project_name: issueRef['project_name'],
294 local_id: issueRef['id'],
295 }));
296 }
297
298 /**
299 * Get HotlistRef PBs for the hotlists that the user wants to add/remove the
300 * selected issues to.
301 */
302 function _GetSelectedHotlists(hotlists) {
303 const selectedHotlistRefs = [];
304 hotlists.forEach(hotlist => {
305 const checkbox = $('cb_hotlist_' + hotlist);
306 const hotlistParts = hotlist.split('_');
307 if (checkbox && checkbox.checked) {
308 selectedHotlistRefs.push({
309 name: hotlistParts[0],
310 owner: {
311 user_id: hotlistParts[1],
312 }
313 });
314 }
315 });
316 return selectedHotlistRefs;
317 }
318
319 Object.assign(window.__hotlists_dialog, {ShowUpdateHotlistDialog});
320})();