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