blob: c37faa913557d5eeddd3c136ee41409c187f8728 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {assert} from 'chai';
6import {parseColSpec, fieldsForIssue,
7 stringValuesForIssueField} from './issue-fields.js';
8import sinon from 'sinon';
9
10let issue;
11let clock;
12
13describe('parseColSpec', () => {
14 it('empty spec produces empty list', () => {
15 assert.deepEqual(parseColSpec(),
16 []);
17 assert.deepEqual(parseColSpec(''),
18 []);
19 assert.deepEqual(parseColSpec(' + + + '),
20 []);
21 assert.deepEqual(parseColSpec(' '),
22 []);
23 assert.deepEqual(parseColSpec('+++++'),
24 []);
25 });
26
27 it('parses spec correctly', () => {
28 assert.deepEqual(parseColSpec('ID+Summary+AllLabels+Priority'),
29 ['ID', 'Summary', 'AllLabels', 'Priority']);
30 });
31
32 it('parses spaces correctly', () => {
33 assert.deepEqual(parseColSpec('ID Summary AllLabels Priority'),
34 ['ID', 'Summary', 'AllLabels', 'Priority']);
35 assert.deepEqual(parseColSpec('ID + Summary + AllLabels + Priority'),
36 ['ID', 'Summary', 'AllLabels', 'Priority']);
37 assert.deepEqual(parseColSpec('ID Summary AllLabels Priority'),
38 ['ID', 'Summary', 'AllLabels', 'Priority']);
39 });
40
41 it('spec parsing preserves dashed parameters', () => {
42 assert.deepEqual(parseColSpec('ID+Summary+Test-Label+Another-Label'),
43 ['ID', 'Summary', 'Test-Label', 'Another-Label']);
44 });
45});
46
47describe('fieldsForIssue', () => {
48 const issue = {
49 projectName: 'proj',
50 localId: 1,
51 };
52
53 const issueWithLabels = {
54 projectName: 'proj',
55 localId: 1,
56 labelRefs: [
57 {label: 'test'},
58 {label: 'hello-world'},
59 {label: 'multi-label-field'},
60 ],
61 };
62
63 const issueWithFieldValues = {
64 projectName: 'proj',
65 localId: 1,
66 fieldValues: [
67 {fieldRef: {fieldName: 'number', type: 'INT_TYPE'}},
68 {fieldRef: {fieldName: 'string', type: 'STR_TYPE'}},
69 ],
70 };
71
72 const issueWithPhases = {
73 projectName: 'proj',
74 localId: 1,
75 fieldValues: [
76 {fieldRef: {fieldName: 'phase-number', type: 'INT_TYPE'},
77 phaseRef: {phaseName: 'phase1'}},
78 {fieldRef: {fieldName: 'phase-string', type: 'STR_TYPE'},
79 phaseRef: {phaseName: 'phase2'}},
80 ],
81 };
82
83 const issueWithApprovals = {
84 projectName: 'proj',
85 localId: 1,
86 approvalValues: [
87 {fieldRef: {fieldName: 'approval', type: 'APPROVAL_TYPE'}},
88 ],
89 };
90
91 it('empty issue issue produces no field names', () => {
92 assert.deepEqual(fieldsForIssue(issue), []);
93 assert.deepEqual(fieldsForIssue(issue, true), []);
94 });
95
96 it('includes label prefixes', () => {
97 assert.deepEqual(fieldsForIssue(issueWithLabels), [
98 'hello',
99 'multi',
100 'multi-label',
101 ]);
102 });
103
104 it('includes field values', () => {
105 assert.deepEqual(fieldsForIssue(issueWithFieldValues), [
106 'number',
107 'string',
108 ]);
109 });
110
111 it('excludes high cardinality field values', () => {
112 assert.deepEqual(fieldsForIssue(issueWithFieldValues, true), [
113 'number',
114 ]);
115 });
116
117 it('includes phase fields', () => {
118 assert.deepEqual(fieldsForIssue(issueWithPhases), [
119 'phase1.phase-number',
120 'phase2.phase-string',
121 ]);
122 });
123
124 it('excludes high cardinality phase fields', () => {
125 assert.deepEqual(fieldsForIssue(issueWithPhases, true), [
126 'phase1.phase-number',
127 ]);
128 });
129
130 it('includes approval values', () => {
131 assert.deepEqual(fieldsForIssue(issueWithApprovals), [
132 'approval',
133 'approval-Approver',
134 ]);
135 });
136});
137
138describe('stringValuesForIssueField', () => {
139 describe('built-in fields', () => {
140 beforeEach(() => {
141 // Set clock to some specified date for relative time.
142 const initialTime = 365 * 24 * 60 * 60;
143
144 clock = sinon.useFakeTimers({
145 now: new Date(initialTime * 1000),
146 shouldAdvanceTime: false,
147 });
148
149 issue = {
150 localId: 33,
151 projectName: 'chromium',
152 summary: 'Test summary',
153 attachmentCount: 22,
154 starCount: 2,
155 componentRefs: [{path: 'Infra'}, {path: 'Monorail>UI'}],
156 blockedOnIssueRefs: [{localId: 30, projectName: 'chromium'}],
157 blockingIssueRefs: [{localId: 60, projectName: 'chromium'}],
158 labelRefs: [{label: 'Restrict-View-Google'}, {label: 'Type-Defect'}],
159 reporterRef: {displayName: 'test@example.com'},
160 ccRefs: [{displayName: 'test@example.com'}],
161 ownerRef: {displayName: 'owner@example.com'},
162 closedTimestamp: initialTime - 120, // 2 minutes ago
163 modifiedTimestamp: initialTime - 60, // a minute ago
164 openedTimestamp: initialTime - 24 * 60 * 60, // a day ago
165 componentModifiedTimestamp: initialTime - 60, // a minute ago
166 statusModifiedTimestamp: initialTime - 60, // a minute ago
167 ownerModifiedTimestamp: initialTime - 60, // a minute ago
168 statusRef: {status: 'Duplicate'},
169 mergedIntoIssueRef: {localId: 31, projectName: 'chromium'},
170 };
171 });
172
173 afterEach(() => {
174 clock.restore();
175 });
176
177 it('computes strings for ID', () => {
178 const fieldName = 'ID';
179
180 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
181 ['chromium:33']);
182 });
183
184 it('computes strings for Project', () => {
185 const fieldName = 'Project';
186
187 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
188 ['chromium']);
189 });
190
191 it('computes strings for Attachments', () => {
192 const fieldName = 'Attachments';
193
194 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
195 ['22']);
196 });
197
198 it('computes strings for AllLabels', () => {
199 const fieldName = 'AllLabels';
200
201 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
202 ['Restrict-View-Google', 'Type-Defect']);
203 });
204
205 it('computes strings for Blocked when issue is blocked', () => {
206 const fieldName = 'Blocked';
207
208 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
209 ['Yes']);
210 });
211
212 it('computes strings for Blocked when issue is not blocked', () => {
213 const fieldName = 'Blocked';
214 issue.blockedOnIssueRefs = [];
215
216 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
217 ['No']);
218 });
219
220 it('computes strings for BlockedOn', () => {
221 const fieldName = 'BlockedOn';
222
223 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
224 ['chromium:30']);
225 });
226
227 it('computes strings for Blocking', () => {
228 const fieldName = 'Blocking';
229
230 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
231 ['chromium:60']);
232 });
233
234 it('computes strings for CC', () => {
235 const fieldName = 'CC';
236
237 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
238 ['test@example.com']);
239 });
240
241 it('computes strings for Closed', () => {
242 const fieldName = 'Closed';
243
244 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
245 ['2 minutes ago']);
246 });
247
248 it('computes strings for Component', () => {
249 const fieldName = 'Component';
250
251 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
252 ['Infra', 'Monorail>UI']);
253 });
254
255 it('computes strings for ComponentModified', () => {
256 const fieldName = 'ComponentModified';
257
258 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
259 ['a minute ago']);
260 });
261
262 it('computes strings for MergedInto', () => {
263 const fieldName = 'MergedInto';
264
265 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
266 ['chromium:31']);
267 });
268
269 it('computes strings for Modified', () => {
270 const fieldName = 'Modified';
271
272 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
273 ['a minute ago']);
274 });
275
276 it('computes strings for Reporter', () => {
277 const fieldName = 'Reporter';
278
279 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
280 ['test@example.com']);
281 });
282
283 it('computes strings for Stars', () => {
284 const fieldName = 'Stars';
285
286 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
287 ['2']);
288 });
289
290 it('computes strings for Status', () => {
291 const fieldName = 'Status';
292
293 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
294 ['Duplicate']);
295 });
296
297 it('computes strings for StatusModified', () => {
298 const fieldName = 'StatusModified';
299
300 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
301 ['a minute ago']);
302 });
303
304 it('computes strings for Summary', () => {
305 const fieldName = 'Summary';
306
307 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
308 ['Test summary']);
309 });
310
311 it('computes strings for Type', () => {
312 const fieldName = 'Type';
313
314 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
315 ['Defect']);
316 });
317
318 it('computes strings for Owner', () => {
319 const fieldName = 'Owner';
320
321 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
322 ['owner@example.com']);
323 });
324
325 it('computes strings for OwnerModified', () => {
326 const fieldName = 'OwnerModified';
327
328 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
329 ['a minute ago']);
330 });
331
332 it('computes strings for Opened', () => {
333 const fieldName = 'Opened';
334
335 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
336 ['a day ago']);
337 });
338 });
339
340 describe('custom approval fields', () => {
341 beforeEach(() => {
342 issue = {
343 localId: 33,
344 projectName: 'bird',
345 approvalValues: [
346 {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Goose-Approval'},
347 approverRefs: []},
348 {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Chicken-Approval'},
349 status: 'APPROVED'},
350 {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Dodo-Approval'},
351 status: 'NEED_INFO', approverRefs: [{displayName: 'kiwi@bird.test'},
352 {displayName: 'mini-dino@bird.test'}],
353 },
354 ],
355 };
356 });
357
358 it('handles approval approver columns', () => {
359 const projectName = 'bird';
360 assert.deepEqual(stringValuesForIssueField(
361 issue, 'goose-approval-approver',
362 projectName), []);
363 assert.deepEqual(stringValuesForIssueField(
364 issue, 'chicken-approval-approver',
365 projectName), []);
366 assert.deepEqual(stringValuesForIssueField(
367 issue, 'dodo-approval-approver',
368 projectName), ['kiwi@bird.test', 'mini-dino@bird.test']);
369 });
370
371 it('handles approval value columns', () => {
372 const projectName = 'bird';
373 assert.deepEqual(stringValuesForIssueField(issue, 'goose-approval',
374 projectName), ['NotSet']);
375 assert.deepEqual(stringValuesForIssueField(issue, 'chicken-approval',
376 projectName), ['Approved']);
377 assert.deepEqual(stringValuesForIssueField(issue, 'dodo-approval',
378 projectName), ['NeedInfo']);
379 });
380 });
381
382 describe('custom fields', () => {
383 beforeEach(() => {
384 issue = {
385 localId: 33,
386 projectName: 'chromium',
387 fieldValues: [
388 {fieldRef: {type: 'STR_TYPE', fieldName: 'aString'}, value: 'test'},
389 {fieldRef: {type: 'STR_TYPE', fieldName: 'aString'}, value: 'test2'},
390 {fieldRef: {type: 'ENUM_TYPE', fieldName: 'ENUM'}, value: 'a-value'},
391 {fieldRef: {type: 'INT_TYPE', fieldId: '6', fieldName: 'Cow-Number'},
392 phaseRef: {phaseName: 'Cow-Phase'},
393 value: '55'},
394 {fieldRef: {type: 'INT_TYPE', fieldId: '6', fieldName: 'Cow-Number'},
395 phaseRef: {phaseName: 'Cow-Phase'},
396 value: '54'},
397 {fieldRef: {type: 'INT_TYPE', fieldId: '6', fieldName: 'Cow-Number'},
398 phaseRef: {phaseName: 'MilkCow-Phase'},
399 value: '56'},
400 ],
401 };
402 });
403
404 it('gets values for custom fields', () => {
405 const projectName = 'chromium';
406 assert.deepEqual(stringValuesForIssueField(issue, 'aString',
407 projectName), ['test', 'test2']);
408 assert.deepEqual(stringValuesForIssueField(issue, 'enum',
409 projectName), ['a-value']);
410 assert.deepEqual(stringValuesForIssueField(issue, 'cow-phase.cow-number',
411 projectName), ['55', '54']);
412 assert.deepEqual(stringValuesForIssueField(issue,
413 'milkcow-phase.cow-number', projectName), ['56']);
414 });
415
416 it('custom fields get precedence over label fields', () => {
417 const projectName = 'chromium';
418 issue.labelRefs = [{label: 'aString-ignore'}];
419 assert.deepEqual(stringValuesForIssueField(issue, 'aString',
420 projectName), ['test', 'test2']);
421 });
422 });
423
424 describe('label prefix fields', () => {
425 beforeEach(() => {
426 issue = {
427 localId: 33,
428 projectName: 'chromium',
429 labelRefs: [
430 {label: 'test-label'},
431 {label: 'test-label-2'},
432 {label: 'ignore-me'},
433 {label: 'Milestone-UI'},
434 {label: 'Milestone-Goodies'},
435 ],
436 };
437 });
438
439 it('gets values for label prefixes', () => {
440 const projectName = 'chromium';
441 assert.deepEqual(stringValuesForIssueField(issue, 'test',
442 projectName), ['label', 'label-2']);
443 assert.deepEqual(stringValuesForIssueField(issue, 'Milestone',
444 projectName), ['UI', 'Goodies']);
445 assert.deepEqual(stringValuesForIssueField(issue, 'ignore',
446 projectName), ['me']);
447 });
448 });
449
450 describe('composite fields', () => {
451 beforeEach(() => {
452 // Set clock to some specified date for relative time.
453 const initialTime = 365 * 24 * 60 * 60;
454
455 clock = sinon.useFakeTimers({
456 now: new Date(initialTime * 1000),
457 shouldAdvanceTime: false,
458 });
459
460 issue = {
461 localId: 33,
462 projectName: 'chromium',
463 summary: 'Test summary',
464 closedTimestamp: initialTime - 120, // 2 minutes ago
465 modifiedTimestamp: initialTime - 60, // a minute ago
466 openedTimestamp: initialTime - 24 * 60 * 60, // a day ago
467 statusModifiedTimestamp: initialTime - 60, // a minute ago
468 statusRef: {status: 'Duplicate'},
469 };
470 });
471
472 afterEach(() => {
473 clock.restore();
474 });
475
476 it('computes strings for Status/Closed', () => {
477 const fieldName = 'Status/Closed';
478
479 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
480 ['Duplicate', '2 minutes ago']);
481 });
482
483 it('ignores nonexistant fields', () => {
484 const fieldName = 'Owner/Status';
485
486 assert.isFalse(issue.hasOwnProperty('ownerRef'));
487 assert.deepEqual(stringValuesForIssueField(issue, fieldName),
488 ['Duplicate']);
489 });
490 });
491});