Skip to content

Commit 35e8332

Browse files
authored
Merge pull request #1080 from notoraptor/progress-bar
Add experiment progress bar to dashboard
2 parents 14e614e + 26414c1 commit 35e8332

11 files changed

Lines changed: 986 additions & 131 deletions

File tree

dashboard/src/src/__tests__/ExperimentNavBar.test.js

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,72 @@
11
import { test, expect } from '@playwright/test';
2+
import { StatusToProgress } from '../experiments/components/ExperimentStatusBar/ExperimentStatusBar';
3+
4+
const PROGRESS_BAR_NAMES = [
5+
'success',
6+
'suspended',
7+
'warning',
8+
'danger',
9+
'info',
10+
];
11+
12+
/**
13+
* Check if a normal progress bar has expected non-null sub-bars
14+
* in order given by statusDescendingOrder from longest to smallest.
15+
*
16+
* Any sub-bar non-mentioned in statusDescendingOrder
17+
* is expected to have width zero.
18+
*
19+
* A "normal" progress bar is a bar for an experiment
20+
* whose max_trials is non-infinite.
21+
*
22+
* @param row - container to find experiment name and bar
23+
* @param name {string} - experiment name
24+
* @param statusDescendingOrder {Array} - list of expected bar names
25+
* (success, suspended, warning, danger or info).
26+
* Expected bar names should have non-null width.
27+
* Order of bar names should be from the longest to the smallest bar.
28+
* @returns {Promise<void>}
29+
*/
30+
async function checkNormalBars(row, name, statusDescendingOrder) {
31+
// Check we find experiment name in row.
32+
await expect(await row.getByText(name)).toHaveCount(1);
33+
// Get progress bar.
34+
const bar = await row.locator('.progress');
35+
const barWidths = {};
36+
const expectedNUllBars = [];
37+
for (let barStatus of PROGRESS_BAR_NAMES) {
38+
const subBar = await bar.locator(`.bg-${barStatus}`);
39+
await expect(subBar).toHaveCount(1);
40+
// Collect bar width.
41+
barWidths[barStatus] = (await subBar.boundingBox()).width;
42+
// Collect if bar must have width 0.
43+
if (statusDescendingOrder.indexOf(barStatus) < 0)
44+
expectedNUllBars.push(barStatus);
45+
}
46+
console.log(
47+
`Testing bars: ${name}\nexpected: ${statusDescendingOrder.join(' >= ') ||
48+
'no non-null bars'}, null bars: ${expectedNUllBars.join(', ') ||
49+
'none'}, collected widths: ${PROGRESS_BAR_NAMES.map(
50+
status => status + ': ' + barWidths[status]
51+
).join(', ')}`
52+
);
53+
// Check bar widths.
54+
if (statusDescendingOrder.length) {
55+
// First expected bar must have width > 0.
56+
expect(barWidths[statusDescendingOrder[0]]).toBeGreaterThan(0);
57+
for (let i = 1; i < statusDescendingOrder.length; ++i) {
58+
// Each following bar must be <= previous.
59+
expect(barWidths[statusDescendingOrder[i - 1]]).toBeGreaterThanOrEqual(
60+
barWidths[statusDescendingOrder[i]]
61+
);
62+
expect(barWidths[statusDescendingOrder[i]]).toBeGreaterThan(0);
63+
}
64+
}
65+
// Other bars must have width == 0.
66+
for (let zeroStatus of expectedNUllBars) {
67+
expect(barWidths[zeroStatus]).toBe(0);
68+
}
69+
}
270

371
test.describe('Test experiment nav bar', () => {
472
test.beforeEach(async ({ page }) => {
@@ -169,4 +237,144 @@ test.describe('Test experiment nav bar', () => {
169237
await expect(waiter).toHaveCount(1);
170238
await checkExpectations(navBar, [0, 0, 0, 0, 0, 0, 0]);
171239
});
240+
241+
test('Test small progress bar for experiment 2-dim-shape-exp', async ({
242+
page,
243+
}) => {
244+
const navBar = await page.locator('.experiment-navbar');
245+
// Wait for first experiment to appear.
246+
// This let time for experiments to be loaded.
247+
const firstExperiment = await navBar.getByText(/2-dim-shape-exp/);
248+
await firstExperiment.waitFor();
249+
await expect(firstExperiment).toHaveCount(1);
250+
251+
/** Locate progress bar related to this experiment **/
252+
// Just check experiment element is indeed a span with experiment name as title
253+
expect(
254+
await firstExperiment.evaluate(node => node.tagName.toLowerCase())
255+
).toBe('span');
256+
expect(await firstExperiment.evaluate(node => node.title)).toBe(
257+
'2-dim-shape-exp'
258+
);
259+
// Get experiment row. Span parent is cell, span parent's parent is row
260+
const parent = await firstExperiment.locator('xpath=../..');
261+
expect(await parent.getAttribute('class')).toBe('bx--structured-list-row');
262+
// Get progress bar
263+
const bar = await parent.locator('.progress');
264+
await expect(bar).toHaveCount(1);
265+
// Make sure it's a small progress bar, not complete progress bar.
266+
// Complete progress bar comes with grids to display supplementary info.
267+
// Small progress bar does not have grid around.
268+
// So, we must not find a grid inside experiment row.
269+
await expect(await parent.locator('.bx--grid')).toHaveCount(0);
270+
// Check sub-bars in progress bar. Experiment 2-dim-shape-exp should be fully completed.
271+
// So, only success bar should have a width > 0.
272+
const barSuccess = await bar.locator('.bg-success');
273+
const barSuspended = await bar.locator('.bg-suspended');
274+
const barWarning = await bar.locator('.bg-warning');
275+
const barDanger = await bar.locator('.bg-danger');
276+
const barInfo = await bar.locator('.bg-info');
277+
await expect(barSuccess).toHaveCount(1);
278+
await expect(barSuspended).toHaveCount(1);
279+
await expect(barWarning).toHaveCount(1);
280+
await expect(barDanger).toHaveCount(1);
281+
await expect(barInfo).toHaveCount(1);
282+
await expect((await barSuccess.boundingBox()).width).toBeGreaterThan(40);
283+
await expect((await barSuspended.boundingBox()).width).toBe(0);
284+
await expect((await barWarning.boundingBox()).width).toBe(0);
285+
await expect((await barDanger.boundingBox()).width).toBe(0);
286+
await expect((await barInfo.boundingBox()).width).toBe(0);
287+
});
288+
289+
test('Test small progress bar for uncompleted experiments', async ({
290+
page,
291+
}) => {
292+
// Get nav bar and wait for default experiments to be loaded.
293+
const navBar = await page.locator('.experiment-navbar');
294+
const firstExperiment = await navBar.getByText(/2-dim-shape-exp/);
295+
await firstExperiment.waitFor();
296+
// Search uncompleted experiments
297+
const searchField = await page.getByPlaceholder('Search experiment');
298+
await expect(searchField).toHaveCount(1);
299+
await searchField.type('uncompleted');
300+
// Check we got expected experiments
301+
const uncompletedExperiments = await navBar.locator(
302+
'.bx--structured-list-tbody .bx--structured-list-row'
303+
);
304+
await uncompletedExperiments.first().waitFor();
305+
await expect(uncompletedExperiments).toHaveCount(5);
306+
const expectedNames = [
307+
'uncompleted_experiment',
308+
'uncompleted_max_trials_0',
309+
'uncompleted_max_trials_infinite',
310+
'uncompleted_max_trials_lt_completed_trials',
311+
'uncompleted_no_completed_trials',
312+
];
313+
for (let i = 0; i < expectedNames.length; ++i) {
314+
const row = uncompletedExperiments.nth(i);
315+
await expect(await row.getByText(expectedNames[i])).toHaveCount(1);
316+
}
317+
// Check expected bars.
318+
await checkNormalBars(
319+
uncompletedExperiments.nth(0),
320+
'uncompleted_experiment',
321+
[
322+
StatusToProgress.completed,
323+
StatusToProgress.reserved,
324+
StatusToProgress.suspended,
325+
StatusToProgress.interrupted,
326+
StatusToProgress.broken,
327+
]
328+
);
329+
await checkNormalBars(
330+
uncompletedExperiments.nth(1),
331+
'uncompleted_max_trials_0',
332+
[
333+
StatusToProgress.completed,
334+
StatusToProgress.reserved,
335+
StatusToProgress.suspended,
336+
StatusToProgress.interrupted,
337+
StatusToProgress.broken,
338+
]
339+
);
340+
await checkNormalBars(
341+
uncompletedExperiments.nth(3),
342+
'uncompleted_max_trials_lt_completed_trials',
343+
[
344+
StatusToProgress.completed,
345+
StatusToProgress.reserved,
346+
StatusToProgress.suspended,
347+
StatusToProgress.interrupted,
348+
StatusToProgress.broken,
349+
]
350+
);
351+
await checkNormalBars(
352+
uncompletedExperiments.nth(4),
353+
'uncompleted_no_completed_trials',
354+
[
355+
StatusToProgress.reserved,
356+
StatusToProgress.suspended,
357+
StatusToProgress.interrupted,
358+
StatusToProgress.broken,
359+
]
360+
);
361+
// Check bar for experiment with max_trials infinite
362+
const rowInfinite = uncompletedExperiments.nth(2);
363+
await expect(
364+
await rowInfinite.getByText('uncompleted_max_trials_infinite')
365+
).toHaveCount(1);
366+
const barInfinite = await rowInfinite.locator('.progress');
367+
await expect(barInfinite).toHaveCount(1);
368+
expect(await barInfinite.evaluate(node => node.title)).toBe(
369+
'N/A (max trials ∞)'
370+
);
371+
await expect(await barInfinite.locator('.bg-success')).toHaveCount(0);
372+
await expect(await barInfinite.locator('.bg-suspended')).toHaveCount(0);
373+
await expect(await barInfinite.locator('.bg-warning')).toHaveCount(0);
374+
await expect(await barInfinite.locator('.bg-danger')).toHaveCount(0);
375+
await expect(await barInfinite.locator('.bg-info')).toHaveCount(0);
376+
const subBarRunning = await barInfinite.locator('.bg-running');
377+
await expect(subBarRunning).toHaveCount(1);
378+
await expect(subBarRunning).toHaveText(/^N\/A$/);
379+
});
172380
});

0 commit comments

Comments
 (0)