@backstage-community/plugin-cicd-statistics 0.1.37 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/apis/cicd-statistics.esm.js +8 -0
  3. package/dist/apis/cicd-statistics.esm.js.map +1 -0
  4. package/dist/apis/types.esm.js +22 -0
  5. package/dist/apis/types.esm.js.map +1 -0
  6. package/dist/charts/colors.esm.js +28 -0
  7. package/dist/charts/colors.esm.js.map +1 -0
  8. package/dist/charts/logic/analysis.esm.js +37 -0
  9. package/dist/charts/logic/analysis.esm.js.map +1 -0
  10. package/dist/charts/logic/conversions.esm.js +66 -0
  11. package/dist/charts/logic/conversions.esm.js.map +1 -0
  12. package/dist/charts/logic/count-builds-per-day.esm.js +24 -0
  13. package/dist/charts/logic/count-builds-per-day.esm.js.map +1 -0
  14. package/dist/charts/logic/daily-summary.esm.js +72 -0
  15. package/dist/charts/logic/daily-summary.esm.js.map +1 -0
  16. package/dist/charts/logic/finalize-stage.esm.js +45 -0
  17. package/dist/charts/logic/finalize-stage.esm.js.map +1 -0
  18. package/dist/charts/logic/utils.esm.js +59 -0
  19. package/dist/charts/logic/utils.esm.js.map +1 -0
  20. package/dist/charts/stage-chart.esm.js +168 -0
  21. package/dist/charts/stage-chart.esm.js.map +1 -0
  22. package/dist/charts/status-chart.esm.js +142 -0
  23. package/dist/charts/status-chart.esm.js.map +1 -0
  24. package/dist/charts/zoom.esm.js +142 -0
  25. package/dist/charts/zoom.esm.js.map +1 -0
  26. package/dist/components/button-switch.esm.js +86 -0
  27. package/dist/components/button-switch.esm.js.map +1 -0
  28. package/dist/components/chart-filters.esm.js +379 -0
  29. package/dist/components/chart-filters.esm.js.map +1 -0
  30. package/dist/components/duration-slider.esm.js +85 -0
  31. package/dist/components/duration-slider.esm.js.map +1 -0
  32. package/dist/components/label.esm.js +22 -0
  33. package/dist/components/label.esm.js.map +1 -0
  34. package/dist/components/progress.esm.js +78 -0
  35. package/dist/components/progress.esm.js.map +1 -0
  36. package/dist/components/toggle.esm.js +23 -0
  37. package/dist/components/toggle.esm.js.map +1 -0
  38. package/dist/components/utils.esm.js +92 -0
  39. package/dist/components/utils.esm.js.map +1 -0
  40. package/dist/entity-page.esm.js +140 -0
  41. package/dist/entity-page.esm.js.map +1 -0
  42. package/dist/hooks/use-cicd-configuration.esm.js +40 -0
  43. package/dist/hooks/use-cicd-configuration.esm.js.map +1 -0
  44. package/dist/hooks/use-cicd-statistics-api.esm.js +13 -0
  45. package/dist/hooks/use-cicd-statistics-api.esm.js.map +1 -0
  46. package/dist/hooks/use-cicd-statistics.esm.js +100 -0
  47. package/dist/hooks/use-cicd-statistics.esm.js.map +1 -0
  48. package/dist/index.esm.js +3 -44
  49. package/dist/index.esm.js.map +1 -1
  50. package/dist/plugin.esm.js +21 -0
  51. package/dist/plugin.esm.js.map +1 -0
  52. package/dist/utils/api.esm.js +16 -0
  53. package/dist/utils/api.esm.js.map +1 -0
  54. package/dist/utils/stage-names.esm.js +36 -0
  55. package/dist/utils/stage-names.esm.js.map +1 -0
  56. package/package.json +10 -6
  57. package/dist/esm/entity-page-CNIrdNBp.esm.js +0 -1797
  58. package/dist/esm/entity-page-CNIrdNBp.esm.js.map +0 -1
@@ -1,1797 +0,0 @@
1
- import React, { useState, useEffect, useCallback, useMemo, useContext, Fragment } from 'react';
2
- import Grid from '@material-ui/core/Grid';
3
- import { makeStyles } from '@material-ui/core/styles';
4
- import Alert from '@material-ui/lab/Alert';
5
- import { useEntity } from '@backstage/plugin-catalog-react';
6
- import { useApi, useApp, errorApiRef } from '@backstage/core-plugin-api';
7
- import { DateTime, Duration } from 'luxon';
8
- import { throttle, groupBy, countBy, capitalize, debounce } from 'lodash';
9
- import { cicdStatisticsApiRef, AbortError, statusTypes } from '../index.esm.js';
10
- import { map } from 'already';
11
- import { ReferenceArea, ResponsiveContainer, ComposedChart, Legend, CartesianGrid, XAxis, YAxis, Tooltip, Area, Line, Bar } from 'recharts';
12
- import Accordion from '@material-ui/core/Accordion';
13
- import AccordionSummary from '@material-ui/core/AccordionSummary';
14
- import AccordionDetails from '@material-ui/core/AccordionDetails';
15
- import Typography from '@material-ui/core/Typography';
16
- import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
17
- import humanizeDuration from 'humanize-duration';
18
- import Button from '@material-ui/core/Button';
19
- import Card from '@material-ui/core/Card';
20
- import CardHeader from '@material-ui/core/CardHeader';
21
- import CardContent from '@material-ui/core/CardContent';
22
- import FormControl from '@material-ui/core/FormControl';
23
- import FormGroup from '@material-ui/core/FormGroup';
24
- import FormControlLabel from '@material-ui/core/FormControlLabel';
25
- import Switch from '@material-ui/core/Switch';
26
- import Tooltip$1 from '@material-ui/core/Tooltip';
27
- import ShowChartIcon from '@material-ui/icons/ShowChart';
28
- import BarChartIcon from '@material-ui/icons/BarChart';
29
- import MuiPickersUtilsProvider from '@material-ui/pickers/MuiPickersUtilsProvider';
30
- import { KeyboardDatePicker } from '@material-ui/pickers/DatePicker';
31
- import LuxonUtils from '@date-io/luxon';
32
- import ButtonGroup from '@material-ui/core/ButtonGroup';
33
- import Zoom from '@material-ui/core/Zoom';
34
- import Slider from '@material-ui/core/Slider';
35
- import useAsync from 'react-use/esm/useAsync';
36
- import Box from '@material-ui/core/Box';
37
- import LinearProgress from '@material-ui/core/LinearProgress';
38
- import Timeline from '@material-ui/lab/Timeline';
39
- import TimelineItem from '@material-ui/lab/TimelineItem';
40
- import TimelineSeparator from '@material-ui/lab/TimelineSeparator';
41
- import TimelineConnector from '@material-ui/lab/TimelineConnector';
42
- import TimelineContent from '@material-ui/lab/TimelineContent';
43
- import TimelineOppositeContent from '@material-ui/lab/TimelineOppositeContent';
44
- import TimelineDot from '@material-ui/lab/TimelineDot';
45
-
46
- function useCicdStatisticsApi() {
47
- try {
48
- return useApi(cicdStatisticsApiRef);
49
- } catch (err) {
50
- return void 0;
51
- }
52
- }
53
-
54
- function useCicdStatistics(options) {
55
- const {
56
- entity,
57
- abortController,
58
- timeFrom,
59
- timeTo,
60
- filterStatus,
61
- filterType
62
- } = options;
63
- const [state, setState] = useState({
64
- loading: true
65
- });
66
- const cicdStatisticsApi = useCicdStatisticsApi();
67
- useEffect(() => {
68
- if (!cicdStatisticsApi) {
69
- setState({ error: new Error("No CI/CD Statistics API installed") });
70
- return () => {
71
- };
72
- }
73
- let mounted = true;
74
- let completed = false;
75
- const updateProgressImpl = (_count, _total, _started) => {
76
- if (!mounted || completed) {
77
- return;
78
- }
79
- if (Array.isArray(_count)) {
80
- setState({
81
- loading: true,
82
- steps: _count.map((step) => {
83
- var _a;
84
- return {
85
- title: step.title,
86
- progress: !step.total ? 0 : step.completed / step.total,
87
- progressBuffer: !step.total ? 0 : ((_a = step.started) != null ? _a : 0) / step.total
88
- };
89
- })
90
- });
91
- } else {
92
- const count = _count;
93
- const total = _total;
94
- const started = _started != null ? _started : 0;
95
- setState({
96
- loading: true,
97
- progress: !total ? 0 : count / total,
98
- progressBuffer: !total ? 0 : started / total
99
- });
100
- }
101
- };
102
- const updateProgress = throttle(
103
- updateProgressImpl,
104
- 200
105
- // throttle doesn't handle types of multi-signature functions
106
- );
107
- const fetchOptions = {
108
- entity,
109
- updateProgress,
110
- abortSignal: abortController.signal,
111
- timeFrom,
112
- timeTo,
113
- filterStatus,
114
- filterType
115
- };
116
- (async () => {
117
- return cicdStatisticsApi.fetchBuilds(fetchOptions);
118
- })().then((builds) => {
119
- completed = true;
120
- if (mounted) {
121
- setState({
122
- value: builds
123
- });
124
- }
125
- }).catch((err) => {
126
- completed = true;
127
- if (mounted) {
128
- setState({
129
- error: abortController.signal.aborted ? new AbortError() : err
130
- });
131
- }
132
- });
133
- return () => {
134
- mounted = false;
135
- abortController.abort();
136
- };
137
- }, [
138
- abortController,
139
- entity,
140
- timeFrom,
141
- timeTo,
142
- filterStatus,
143
- filterType,
144
- cicdStatisticsApi
145
- ]);
146
- return state;
147
- }
148
-
149
- function defaultFormatStageName(parentNames, stageName) {
150
- let name = stageName;
151
- parentNames.forEach((parentName) => {
152
- if (name.startsWith(parentName)) {
153
- const newName = name.slice(parentName.length).replace(/^[^\w\d]+/g, "");
154
- if (newName) {
155
- name = newName;
156
- }
157
- }
158
- });
159
- return name.replace(/((pulling|(running)) [^:/]*\/.*?):.*/g, "$1");
160
- }
161
- async function cleanupBuildTree(builds, opts) {
162
- const { formatStageName, lowerCase } = opts;
163
- const recurseStage = (stage, parentNames) => {
164
- var _a;
165
- const name = formatStageName(
166
- parentNames,
167
- lowerCase ? stage.name.toLocaleLowerCase("en-US") : stage.name
168
- );
169
- const ancestry = [...parentNames, name];
170
- return {
171
- ...stage,
172
- name,
173
- stages: (_a = stage.stages) == null ? void 0 : _a.map((subStage) => recurseStage(subStage, ancestry))
174
- };
175
- };
176
- return map(builds, { chunk: "idle" }, (build) => ({
177
- ...build,
178
- stages: build.stages.map((stage) => recurseStage(stage, []))
179
- }));
180
- }
181
-
182
- function useCicdConfiguration() {
183
- const cicdStatisticsApi = useCicdStatisticsApi();
184
- const { entity } = useEntity();
185
- const [state, setState] = useState({
186
- loading: true
187
- });
188
- useEffect(() => {
189
- if (!cicdStatisticsApi) {
190
- setState({ error: new Error("No CI/CD Statistics API installed") });
191
- return;
192
- }
193
- cicdStatisticsApi.getConfiguration({ entity }).then((configuration) => {
194
- const {
195
- availableStatuses = statusTypes,
196
- formatStageName = defaultFormatStageName,
197
- defaults = {}
198
- } = configuration;
199
- setState({
200
- value: {
201
- availableStatuses,
202
- formatStageName,
203
- defaults
204
- }
205
- });
206
- }).catch((error) => {
207
- setState({ error });
208
- });
209
- }, [cicdStatisticsApi, entity]);
210
- return state;
211
- }
212
-
213
- function average(values) {
214
- return !values.length ? 0 : Math.round(values.reduce((prev, cur) => prev + cur, 0) / values.length);
215
- }
216
- function getOrSetStage(stages, name) {
217
- const stage = stages.get(name);
218
- if (stage)
219
- return stage;
220
- const newStage = makeStage(name);
221
- stages.set(name, newStage);
222
- return newStage;
223
- }
224
- function makeStage(name) {
225
- return {
226
- analysis: {
227
- unknown: { avg: 0, med: 0, max: 0, min: 0 },
228
- enqueued: { avg: 0, med: 0, max: 0, min: 0 },
229
- scheduled: { avg: 0, med: 0, max: 0, min: 0 },
230
- running: { avg: 0, med: 0, max: 0, min: 0 },
231
- aborted: { avg: 0, med: 0, max: 0, min: 0 },
232
- succeeded: { avg: 0, med: 0, max: 0, min: 0 },
233
- failed: { avg: 0, med: 0, max: 0, min: 0 },
234
- stalled: { avg: 0, med: 0, max: 0, min: 0 },
235
- expired: { avg: 0, med: 0, max: 0, min: 0 }
236
- },
237
- combinedAnalysis: { avg: 0, med: 0, max: 0, min: 0 },
238
- statusSet: /* @__PURE__ */ new Set(),
239
- name,
240
- values: [],
241
- stages: /* @__PURE__ */ new Map()
242
- };
243
- }
244
- function startOfDay$1(date) {
245
- if (typeof date === "number") {
246
- return DateTime.fromMillis(date).startOf("day").toMillis();
247
- }
248
- return DateTime.fromJSDate(date).startOf("day").toMillis();
249
- }
250
- function sortTriggerReasons(reasons) {
251
- return reasons.sort((a, b) => {
252
- if (a === "manual")
253
- return -1;
254
- else if (b === "manual")
255
- return 1;
256
- else if (a === "scm")
257
- return -1;
258
- else if (b === "scm")
259
- return 1;
260
- else if (a === "other")
261
- return -1;
262
- else if (b === "other")
263
- return 1;
264
- return a.localeCompare(b);
265
- });
266
- }
267
- function sortStatuses(statuses) {
268
- return [
269
- ...statusTypes.filter((status) => statuses.includes(status)),
270
- ...statuses.filter((status) => !statusTypes.includes(status)).sort((a, b) => a.localeCompare(b))
271
- ];
272
- }
273
-
274
- function countBuildsPerDay(values) {
275
- const days = groupBy(values, (value) => startOfDay$1(value.__epoch));
276
- Object.entries(days).forEach(([_startOfDay, valuesThisDay]) => {
277
- const counts = Object.fromEntries(
278
- statusTypes.map(
279
- (type) => [
280
- type,
281
- valuesThisDay.filter((value) => value[type] !== void 0).length
282
- ]
283
- ).filter(([_type, count]) => count > 0).map(([type, count]) => [
284
- `${type} count`,
285
- count
286
- ])
287
- );
288
- Object.assign(valuesThisDay[0], counts);
289
- });
290
- }
291
-
292
- function getAnalysis(values, status) {
293
- var _a, _b, _c;
294
- const analysis = {
295
- max: 0,
296
- min: 0,
297
- avg: 0,
298
- med: 0
299
- };
300
- const definedValues = values.filter((value) => typeof value[status] !== "undefined").map((value) => value[status]).sort((a, b) => a - b);
301
- analysis.max = (_a = definedValues[definedValues.length - 1]) != null ? _a : 0;
302
- analysis.min = (_b = definedValues[0]) != null ? _b : 0;
303
- analysis.avg = definedValues.length === 0 ? 0 : definedValues.reduce((prev, cur) => prev + cur, 0) / values.length;
304
- analysis.med = (_c = definedValues[Math.ceil(definedValues.length / 2)]) != null ? _c : 0;
305
- return analysis;
306
- }
307
- function makeCombinedAnalysis(analysis, allDurations) {
308
- var _a;
309
- if (analysis.succeeded) {
310
- return analysis.succeeded;
311
- }
312
- const analysisValues = Object.values(analysis);
313
- const max = analysisValues.reduce((prev, cur) => Math.max(prev, cur.max), 0);
314
- const min = analysisValues.reduce(
315
- (prev, cur) => Math.min(prev, cur.min),
316
- max
317
- );
318
- const avg = !allDurations.length ? 0 : allDurations.reduce((prev, cur) => prev + cur, 0) / allDurations.length;
319
- allDurations.sort((a, b) => a - b);
320
- const med = (_a = allDurations[Math.ceil(allDurations.length / 2)]) != null ? _a : 0;
321
- return {
322
- max,
323
- min,
324
- avg,
325
- med
326
- };
327
- }
328
-
329
- function finalizeStage(stage, options) {
330
- const { averageWidth, allEpochs } = options;
331
- const { values, analysis, combinedAnalysis } = stage;
332
- if (allEpochs.length > 0) {
333
- const valueEpochs = new Set(values.map((value) => value.__epoch));
334
- allEpochs.forEach((epoch) => {
335
- if (!valueEpochs.has(epoch)) {
336
- values.push({ __epoch: epoch });
337
- }
338
- });
339
- }
340
- values.sort((a, b) => a.__epoch - b.__epoch);
341
- countBuildsPerDay(values);
342
- const allDurations = [];
343
- statusTypes.forEach((status) => {
344
- analysis[status] = getAnalysis(values, status);
345
- const durationsIndexes = values.map((value) => value[status]).map((duration, index) => ({ index, duration })).filter(({ duration }) => typeof duration !== "undefined").map(({ index }) => index);
346
- const durationsDense = values.map((value) => value[status]).filter(
347
- (duration) => typeof duration !== "undefined"
348
- );
349
- durationsDense.forEach((dur) => allDurations.push(dur));
350
- const averages = durationsDense.map(
351
- (_, i) => average(
352
- durationsDense.slice(
353
- Math.max(i - averageWidth, 0),
354
- Math.min(i + averageWidth, durationsDense.length)
355
- )
356
- )
357
- );
358
- averages.forEach((avg, index) => {
359
- const key = `${status} avg`;
360
- values[durationsIndexes[index]][key] = avg;
361
- });
362
- });
363
- Object.assign(combinedAnalysis, makeCombinedAnalysis(analysis, allDurations));
364
- stage.stages.forEach((subStage) => finalizeStage(subStage, options));
365
- }
366
-
367
- function dailySummary(builds) {
368
- const triggersDaily = countTriggersPerDay(builds);
369
- const statusesDaily = countStatusesPerDay(builds);
370
- const { triggerReasons } = triggersDaily;
371
- const { statuses } = statusesDaily;
372
- const reasonMap = new Map(
373
- triggersDaily.values.map((value) => [value.__epoch, value])
374
- );
375
- const statusMap = new Map(
376
- statusesDaily.values.map((value) => [value.__epoch, value])
377
- );
378
- const days = Object.keys(
379
- groupBy(builds, (value) => startOfDay$1(value.requestedAt))
380
- ).map((epoch) => parseInt(epoch, 10)).sort();
381
- return {
382
- values: days.map((epoch) => ({
383
- __epoch: epoch,
384
- ...reasonMap.get(epoch),
385
- ...statusMap.get(epoch)
386
- })),
387
- triggerReasons,
388
- statuses
389
- };
390
- }
391
- function countTriggersPerDay(builds) {
392
- const days = groupBy(builds, (value) => startOfDay$1(value.requestedAt));
393
- const triggerReasons = sortTriggerReasons([
394
- ...new Set(
395
- builds.map(({ triggeredBy }) => triggeredBy).filter((v) => !!v)
396
- )
397
- ]);
398
- const values = Object.entries(days).map(([epoch, buildsThisDay]) => {
399
- const datapoint = Object.fromEntries(
400
- triggerReasons.map(
401
- (reason) => [
402
- reason,
403
- buildsThisDay.filter((build) => build.triggeredBy === reason).length
404
- ]
405
- ).filter(([_type, count]) => count > 0)
406
- );
407
- const value = Object.assign(datapoint, {
408
- __epoch: parseInt(epoch, 10)
409
- });
410
- return value;
411
- });
412
- return { triggerReasons, values };
413
- }
414
- function countStatusesPerDay(builds) {
415
- const days = groupBy(builds, (value) => startOfDay$1(value.requestedAt));
416
- const foundStatuses = /* @__PURE__ */ new Set();
417
- const values = Object.entries(days).map(([epoch, buildsThisDay]) => {
418
- const byStatus = countBy(buildsThisDay, "status");
419
- const value = {
420
- __epoch: parseInt(epoch, 10),
421
- ...byStatus
422
- };
423
- Object.keys(byStatus).forEach((status) => {
424
- foundStatuses.add(status);
425
- });
426
- return value;
427
- });
428
- return {
429
- statuses: sortStatuses([...foundStatuses]),
430
- values
431
- };
432
- }
433
-
434
- async function buildsToChartableStages(builds, options) {
435
- const { normalizeTimeRange } = options;
436
- const total = makeStage("Total");
437
- const recurseDown = (stageMap, stage, __epoch) => {
438
- var _a;
439
- const { name, status, duration } = stage;
440
- const subChartableStage = getOrSetStage(stageMap, name);
441
- subChartableStage.statusSet.add(status);
442
- subChartableStage.values.push({
443
- __epoch,
444
- [status]: duration,
445
- [`${status} avg`]: duration
446
- });
447
- (_a = stage.stages) == null ? void 0 : _a.forEach((subStage) => {
448
- recurseDown(subChartableStage.stages, subStage, __epoch);
449
- });
450
- };
451
- const stages = /* @__PURE__ */ new Map();
452
- await map(builds, { chunk: "idle" }, (build) => {
453
- var _a;
454
- const { duration, requestedAt, status } = build;
455
- const __epoch = requestedAt.getTime();
456
- total.statusSet.add(status);
457
- total.values.push({
458
- __epoch,
459
- [status]: duration,
460
- [`${status} avg`]: duration
461
- });
462
- (_a = build.stages) == null ? void 0 : _a.forEach((subStage) => {
463
- recurseDown(stages, subStage, __epoch);
464
- });
465
- });
466
- const allEpochs = normalizeTimeRange ? builds.map((build) => build.requestedAt.getTime()) : [];
467
- await map(
468
- [...stages.values()],
469
- { chunk: "idle" },
470
- (stage) => finalizeStage(stage, { allEpochs, averageWidth: 10 })
471
- );
472
- finalizeStage(total, { allEpochs, averageWidth: 10 });
473
- const daily = dailySummary(builds);
474
- const statuses = findStatuses(total, [...stages.values()]);
475
- return { daily, total, stages, statuses };
476
- }
477
- function findStatuses(total, stages) {
478
- const statuses = /* @__PURE__ */ new Set();
479
- const addStatuses = (set) => {
480
- set.forEach((status) => {
481
- statuses.add(status);
482
- });
483
- };
484
- addStatuses(total.statusSet);
485
- const recurse = (subStages) => {
486
- subStages.forEach((stage) => {
487
- addStatuses(stage.statusSet);
488
- recurse([...stage.stages.values()]);
489
- });
490
- };
491
- recurse(stages);
492
- return sortStatuses([...statuses]);
493
- }
494
-
495
- const context = React.createContext(void 0);
496
- function ZoomProvider({ children }) {
497
- const [registeredSelectors, setRegisteredSelectors] = useState([]);
498
- const [selectState, setSelectState] = useState({});
499
- const [zoomState, setZoomState] = useState({});
500
- const registerSelection = useCallback(
501
- (selector) => {
502
- setRegisteredSelectors((old) => [...old, selector]);
503
- return () => {
504
- setRegisteredSelectors((old) => old.filter((sel) => sel === selector));
505
- };
506
- },
507
- [setRegisteredSelectors]
508
- );
509
- const callSelectors = useCallback(
510
- (state) => {
511
- registeredSelectors.forEach((selector) => {
512
- selector(state);
513
- });
514
- },
515
- [registeredSelectors]
516
- );
517
- const throttledCallSelectors = useMemo(
518
- () => throttle(callSelectors, 200),
519
- [callSelectors]
520
- );
521
- useEffect(() => {
522
- throttledCallSelectors({
523
- left: selectState.left,
524
- right: selectState.right
525
- });
526
- }, [selectState.left, selectState.right, throttledCallSelectors]);
527
- const resetZoom = useCallback(() => {
528
- setSelectState({});
529
- setZoomState({});
530
- }, [setSelectState, setZoomState]);
531
- const value = useMemo(
532
- () => ({
533
- registerSelection,
534
- setSelectState,
535
- zoomState,
536
- setZoomState,
537
- resetZoom
538
- }),
539
- [registerSelection, setSelectState, zoomState, setZoomState, resetZoom]
540
- );
541
- return /* @__PURE__ */ React.createElement(context.Provider, { value, children });
542
- }
543
- function useZoom() {
544
- const { zoomState, resetZoom } = useContext(context);
545
- const zoomFilterValues = useCallback(
546
- (values) => {
547
- const { left, right } = zoomState;
548
- return left === void 0 || right === void 0 ? values : values.filter(({ __epoch }) => __epoch > left && __epoch < right);
549
- },
550
- [zoomState]
551
- );
552
- return useMemo(
553
- () => ({
554
- resetZoom,
555
- zoomState,
556
- zoomFilterValues
557
- }),
558
- [resetZoom, zoomState, zoomFilterValues]
559
- );
560
- }
561
- function useZoomArea() {
562
- const [showSelection, setShowSelection] = useState(false);
563
- const [state, setState] = useState({});
564
- const { setSelectState, setZoomState, registerSelection } = useContext(context);
565
- const onMouseDown = useCallback(
566
- (e) => {
567
- if (!(e == null ? void 0 : e.activeLabel))
568
- return;
569
- setSelectState({ left: e.activeLabel });
570
- setShowSelection(true);
571
- },
572
- [setSelectState, setShowSelection]
573
- );
574
- const onMouseMove = useCallback(
575
- (e) => {
576
- if (!(e == null ? void 0 : e.activeLabel))
577
- return;
578
- setSelectState((area) => {
579
- if (!area.left) {
580
- return area;
581
- }
582
- return { ...area, right: e.activeLabel };
583
- });
584
- },
585
- [setSelectState]
586
- );
587
- const doZoom = useCallback(() => {
588
- setSelectState((old) => {
589
- const { left, right } = old;
590
- if (left === void 0 || right === void 0 || left === right) {
591
- setZoomState({});
592
- } else if (left < right) {
593
- setZoomState({ left, right });
594
- } else if (left > right) {
595
- setZoomState({ left: right, right: left });
596
- }
597
- return {};
598
- });
599
- setShowSelection(false);
600
- }, [setSelectState, setZoomState, setShowSelection]);
601
- const zoomProps = useMemo(
602
- () => ({
603
- onMouseDown,
604
- onMouseMove,
605
- onMouseUp: doZoom
606
- }),
607
- [onMouseDown, onMouseMove, doZoom]
608
- );
609
- useEffect(() => {
610
- if (!showSelection) {
611
- return void 0;
612
- }
613
- return registerSelection(setState);
614
- }, [registerSelection, setState, showSelection]);
615
- const getZoomArea = useCallback(
616
- (props) => /* @__PURE__ */ React.createElement(Fragment, { key: "zoom-area" }, showSelection && state.left && state.right ? /* @__PURE__ */ React.createElement(
617
- ReferenceArea,
618
- {
619
- x1: state.left,
620
- x2: state.right,
621
- strokeOpacity: 0.5,
622
- ...props
623
- }
624
- ) : null),
625
- [showSelection, state.left, state.right]
626
- );
627
- return {
628
- zoomProps,
629
- getZoomArea
630
- };
631
- }
632
-
633
- const infoText = { color: "InfoText" };
634
- function pickElements(arr, num) {
635
- if (arr.length <= num) {
636
- return [...arr];
637
- }
638
- if (num < 2) {
639
- return [arr[arr.length / 2]];
640
- }
641
- const step = arr.length / (num - 1);
642
- return [
643
- ...Array.from(Array(num - 1)).map(
644
- (_, index) => arr[Math.round(index * step)]
645
- ),
646
- arr[arr.length - 1]
647
- ];
648
- }
649
- function formatDateShort(milliseconds) {
650
- if (milliseconds === "auto") {
651
- return "";
652
- }
653
- return DateTime.fromMillis(milliseconds).toLocaleString(DateTime.DATE_SHORT);
654
- }
655
- function formatDateTimeShort(milliseconds) {
656
- if (milliseconds === "auto") {
657
- return "";
658
- }
659
- return DateTime.fromMillis(milliseconds).toLocaleString(
660
- DateTime.DATETIME_SHORT
661
- );
662
- }
663
- function labelFormatter(epoch) {
664
- return /* @__PURE__ */ React.createElement(Typography, { component: "span", style: infoText }, formatDateTimeShort(epoch));
665
- }
666
- function labelFormatterWithoutTime(epoch) {
667
- return /* @__PURE__ */ React.createElement(Typography, { component: "span", style: infoText }, formatDateShort(epoch));
668
- }
669
- function tickFormatterX(epoch) {
670
- return formatDateShort(epoch);
671
- }
672
- function tickFormatterY(duration) {
673
- if (duration === 0) {
674
- return "0";
675
- } else if (duration < 500) {
676
- return `${duration} ms`;
677
- }
678
- return formatDuration(duration).replace(/second.*/, "sec").replace(/minute.*/, "min").replace(/hour.*/, "h").replace(/day.*/, "d").replace(/month.*/, "m").replace(/year.*/, "y");
679
- }
680
- function tooltipValueFormatter(durationOrCount, name) {
681
- return [
682
- // TODO(Rugvip): Types don't allow returning elements, but it was here before so presumably works
683
- /* @__PURE__ */ React.createElement(Typography, { component: "span", style: infoText }, capitalize(name), ":", " ", name.endsWith(" count") ? durationOrCount : formatDuration(durationOrCount)),
684
- null
685
- ];
686
- }
687
- function formatDuration(millis) {
688
- let rest = Math.round(millis);
689
- const days = Math.floor(rest / (1e3 * 60 * 60 * 24));
690
- rest -= days * (1e3 * 60 * 60 * 24);
691
- const hours = Math.floor(rest / (1e3 * 60 * 60));
692
- rest -= hours * (1e3 * 60 * 60);
693
- const minutes = Math.floor(rest / (1e3 * 60));
694
- rest -= minutes * (1e3 * 60);
695
- const seconds = Math.floor(rest / 1e3);
696
- rest -= seconds * 1e3;
697
- const milliseconds = rest;
698
- if (!days && !hours && !minutes) {
699
- if (seconds < 1) {
700
- return `${milliseconds}ms`;
701
- } else if (seconds < 2) {
702
- return `${((milliseconds + seconds * 1e3) / 1e3).toFixed(1)}s`;
703
- }
704
- }
705
- const dur = Duration.fromObject({
706
- ...days && { days },
707
- ...hours && { hours },
708
- ...minutes && !days && { minutes },
709
- ...seconds && !days && !hours && { seconds }
710
- });
711
- return humanizeDuration(dur.toMillis(), { round: true });
712
- }
713
- function formatDurationFromSeconds(seconds) {
714
- return formatDuration(seconds * 1e3);
715
- }
716
-
717
- const statusColorMap = {
718
- unknown: "#3d01a4",
719
- enqueued: "#7ad1b9",
720
- scheduled: "#0391ce",
721
- running: "#f3f318",
722
- aborted: "#8600af",
723
- succeeded: "#66b032",
724
- failed: "#fe2712",
725
- stalled: "#fb9904",
726
- expired: "#a7194b"
727
- };
728
- const triggerColorMap = {
729
- scm: "#0391ce",
730
- manual: "#a7194b",
731
- internal: "#82ca9d",
732
- other: "#f3f318"
733
- };
734
- const fireColors = [
735
- ["5%", "#e19678"],
736
- ["30%", "#dfe178"],
737
- ["50%", "#82ca9d"],
738
- ["95%", "#82ca9d"]
739
- ];
740
- const colorStroke = "#c0c0c0";
741
- const colorStrokeAvg = "#788ee1";
742
-
743
- const fullWidth = { width: "100%" };
744
- const noUserSelect = { userSelect: "none" };
745
- const transitionProps = { unmountOnExit: true };
746
- function StageChart(props) {
747
- const { stage, ...chartOptions } = props;
748
- const {
749
- chartTypes,
750
- defaultCollapsed = 0,
751
- defaultHidden = 0,
752
- zeroYAxis = false
753
- } = chartOptions;
754
- const { zoomFilterValues } = useZoom();
755
- const { zoomProps, getZoomArea } = useZoomArea();
756
- const ticks = useMemo(
757
- () => pickElements(stage.values, 8).map((val) => val.__epoch),
758
- [stage.values]
759
- );
760
- const domainY = useMemo(
761
- () => [zeroYAxis ? 0 : "auto", "auto"],
762
- [zeroYAxis]
763
- );
764
- const statuses = useMemo(
765
- () => statusTypes.filter((status) => stage.statusSet.has(status)),
766
- [stage.statusSet]
767
- );
768
- const legendPayload = useMemo(
769
- () => statuses.map((status) => ({
770
- value: capitalize(status),
771
- type: "line",
772
- id: status,
773
- color: statusColorMap[status]
774
- })),
775
- [statuses]
776
- );
777
- const subStages = useMemo(
778
- () => new Map(
779
- [...stage.stages.entries()].filter(
780
- ([_name, subStage]) => subStage.combinedAnalysis.max > defaultHidden
781
- )
782
- ),
783
- [stage.stages, defaultHidden]
784
- );
785
- const zoomFilteredValues = useMemo(
786
- () => zoomFilterValues(stage.values),
787
- [stage.values, zoomFilterValues]
788
- );
789
- return stage.combinedAnalysis.max < defaultHidden ? null : /* @__PURE__ */ React.createElement(
790
- Accordion,
791
- {
792
- defaultExpanded: stage.combinedAnalysis.max > defaultCollapsed,
793
- TransitionProps: transitionProps
794
- },
795
- /* @__PURE__ */ React.createElement(AccordionSummary, { expandIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, null) }, /* @__PURE__ */ React.createElement(Typography, null, stage.name, " (med ", formatDuration(stage.combinedAnalysis.med), ", avg", " ", formatDuration(stage.combinedAnalysis.avg), ")")),
796
- /* @__PURE__ */ React.createElement(AccordionDetails, null, stage.values.length === 0 ? /* @__PURE__ */ React.createElement(Alert, { severity: "info" }, "No data") : /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "column" }, /* @__PURE__ */ React.createElement(Grid, { item: true, style: noUserSelect }, /* @__PURE__ */ React.createElement(ResponsiveContainer, { width: "100%", height: 140 }, /* @__PURE__ */ React.createElement(ComposedChart, { data: zoomFilteredValues, ...zoomProps }, /* @__PURE__ */ React.createElement("defs", null, /* @__PURE__ */ React.createElement("linearGradient", { id: "colorDur", x1: "0", y1: "0", x2: "0", y2: "1" }, fireColors.map(([percent, color]) => /* @__PURE__ */ React.createElement(
797
- "stop",
798
- {
799
- key: percent,
800
- offset: percent,
801
- stopColor: color,
802
- stopOpacity: 0.8
803
- }
804
- )))), statuses.length > 1 && /* @__PURE__ */ React.createElement(Legend, { payload: legendPayload }), /* @__PURE__ */ React.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React.createElement(
805
- XAxis,
806
- {
807
- dataKey: "__epoch",
808
- type: "category",
809
- ticks,
810
- tickFormatter: tickFormatterX
811
- }
812
- ), /* @__PURE__ */ React.createElement(
813
- YAxis,
814
- {
815
- yAxisId: 1,
816
- tickFormatter: tickFormatterY,
817
- type: "number",
818
- tickCount: 5,
819
- name: "Duration",
820
- domain: domainY
821
- }
822
- ), /* @__PURE__ */ React.createElement(
823
- YAxis,
824
- {
825
- yAxisId: 2,
826
- orientation: "right",
827
- type: "number",
828
- tickCount: 5,
829
- name: "Count"
830
- }
831
- ), /* @__PURE__ */ React.createElement(
832
- Tooltip,
833
- {
834
- formatter: tooltipValueFormatter,
835
- labelFormatter
836
- }
837
- ), statuses.reverse().map((status) => {
838
- var _a, _b;
839
- return /* @__PURE__ */ React.createElement(Fragment, { key: status }, !chartTypes[status].includes("duration") ? null : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
840
- Area,
841
- {
842
- isAnimationActive: false,
843
- yAxisId: 1,
844
- type: "monotone",
845
- dataKey: status,
846
- stackId: status,
847
- stroke: statuses.length > 1 ? statusColorMap[status] : colorStroke,
848
- fillOpacity: statuses.length > 1 ? 0.5 : 1,
849
- fill: statuses.length > 1 ? statusColorMap[status] : "url(#colorDur)",
850
- connectNulls: true
851
- }
852
- ), /* @__PURE__ */ React.createElement(
853
- Line,
854
- {
855
- isAnimationActive: false,
856
- yAxisId: 1,
857
- type: "monotone",
858
- dataKey: `${status} avg`,
859
- stroke: statuses.length > 1 ? statusColorMap[status] : colorStrokeAvg,
860
- opacity: 0.8,
861
- strokeWidth: 2,
862
- dot: false,
863
- connectNulls: true
864
- }
865
- )), !chartTypes[status].includes("count") ? null : /* @__PURE__ */ React.createElement(
866
- Bar,
867
- {
868
- isAnimationActive: false,
869
- yAxisId: 2,
870
- type: "monotone",
871
- dataKey: `${status} count`,
872
- stackId: "1",
873
- stroke: (_a = statusColorMap[status]) != null ? _a : "",
874
- fillOpacity: 0.5,
875
- fill: (_b = statusColorMap[status]) != null ? _b : ""
876
- }
877
- ));
878
- }), getZoomArea({ yAxisId: 1 })))), subStages.size === 0 ? null : /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
879
- Accordion,
880
- {
881
- defaultExpanded: false,
882
- TransitionProps: transitionProps
883
- },
884
- /* @__PURE__ */ React.createElement(AccordionSummary, { expandIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, null) }, /* @__PURE__ */ React.createElement(Typography, null, "Sub stages (", subStages.size, ")")),
885
- /* @__PURE__ */ React.createElement(AccordionDetails, null, /* @__PURE__ */ React.createElement("div", { style: fullWidth }, [...subStages.values()].map((subStage) => /* @__PURE__ */ React.createElement(
886
- StageChart,
887
- {
888
- key: subStage.name,
889
- ...chartOptions,
890
- stage: subStage
891
- }
892
- ))))
893
- ))))
894
- );
895
- }
896
-
897
- function StatusChart(props) {
898
- const { analysis } = props;
899
- const { zoomFilterValues } = useZoom();
900
- const { zoomProps, getZoomArea } = useZoomArea();
901
- const values = useMemo(() => {
902
- return analysis.daily.values.map((value) => {
903
- const totTriggers = analysis.daily.triggerReasons.reduce(
904
- (prev, cur) => {
905
- var _a;
906
- return prev + ((_a = value[cur]) != null ? _a : 0);
907
- },
908
- 0
909
- );
910
- if (!totTriggers) {
911
- return value;
912
- }
913
- return {
914
- ...value,
915
- ...Object.fromEntries(
916
- analysis.daily.triggerReasons.map((reason) => {
917
- var _a;
918
- return [
919
- reason,
920
- ((_a = value[reason]) != null ? _a : 0) / totTriggers
921
- ];
922
- })
923
- )
924
- };
925
- });
926
- }, [analysis.daily]);
927
- const triggerReasonLegendPayload = useMemo(
928
- () => analysis.daily.triggerReasons.map((reason) => {
929
- var _a;
930
- return {
931
- value: humanTriggerReason(reason),
932
- type: "line",
933
- id: reason,
934
- color: (_a = triggerColorMap[reason]) != null ? _a : ""
935
- };
936
- }),
937
- [analysis.daily.triggerReasons]
938
- );
939
- const statusesLegendPayload = useMemo(
940
- () => analysis.daily.statuses.map((status) => {
941
- var _a;
942
- return {
943
- value: capitalize(status),
944
- type: "line",
945
- id: status,
946
- color: (_a = statusColorMap[status]) != null ? _a : ""
947
- };
948
- }),
949
- [analysis.daily.statuses]
950
- );
951
- const legendPayload = useMemo(
952
- () => [
953
- ...triggerReasonLegendPayload,
954
- ...statusesLegendPayload
955
- ],
956
- [statusesLegendPayload, triggerReasonLegendPayload]
957
- );
958
- const tooltipFormatter = useMemo(() => {
959
- const reasonSet = new Set(analysis.daily.triggerReasons);
960
- return (percentOrCount, name) => {
961
- const label = reasonSet.has(name) ? humanTriggerReason(name) : capitalize(name);
962
- const valueText = reasonSet.has(name) ? `${(percentOrCount * 100).toFixed(0)}%` : percentOrCount;
963
- return [
964
- // TODO(Rugvip): Types don't allow returning elements, but it was here before so presumably works
965
- /* @__PURE__ */ React.createElement(Typography, { component: "span" }, label, ": ", valueText),
966
- null
967
- ];
968
- };
969
- }, [analysis.daily.triggerReasons]);
970
- const zoomFilteredValues = useMemo(
971
- () => zoomFilterValues(values),
972
- [values, zoomFilterValues]
973
- );
974
- const barSize = getBarSize(analysis.daily.values.length);
975
- return /* @__PURE__ */ React.createElement(Accordion, { defaultExpanded: analysis.daily.statuses.length > 1 }, /* @__PURE__ */ React.createElement(AccordionSummary, { expandIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, null) }, /* @__PURE__ */ React.createElement(Typography, null, "Build count per status over build trigger reason")), /* @__PURE__ */ React.createElement(AccordionDetails, null, values.length === 0 ? /* @__PURE__ */ React.createElement(Alert, { severity: "info" }, "No data") : /* @__PURE__ */ React.createElement(ResponsiveContainer, { width: "100%", height: 140 }, /* @__PURE__ */ React.createElement(ComposedChart, { data: zoomFilteredValues, ...zoomProps }, /* @__PURE__ */ React.createElement(Legend, { payload: legendPayload }), /* @__PURE__ */ React.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React.createElement(
976
- XAxis,
977
- {
978
- dataKey: "__epoch",
979
- type: "category",
980
- tickFormatter: tickFormatterX
981
- }
982
- ), /* @__PURE__ */ React.createElement(YAxis, { yAxisId: 1, type: "number", tickCount: 5, name: "Count" }), /* @__PURE__ */ React.createElement(YAxis, { yAxisId: 2, type: "number", name: "Triggers", hide: true }), /* @__PURE__ */ React.createElement(
983
- Tooltip,
984
- {
985
- labelFormatter: labelFormatterWithoutTime,
986
- formatter: tooltipFormatter
987
- }
988
- ), triggerReasonLegendPayload.map((reason) => {
989
- var _a, _b;
990
- return /* @__PURE__ */ React.createElement(Fragment, { key: reason.id }, /* @__PURE__ */ React.createElement(
991
- Area,
992
- {
993
- isAnimationActive: false,
994
- type: "monotone",
995
- dataKey: reason.id,
996
- stackId: "triggers",
997
- yAxisId: 2,
998
- stroke: (_a = triggerColorMap[reason.id]) != null ? _a : "",
999
- fillOpacity: 0.5,
1000
- fill: (_b = triggerColorMap[reason.id]) != null ? _b : ""
1001
- }
1002
- ));
1003
- }), [...analysis.daily.statuses].reverse().map((status) => {
1004
- var _a, _b;
1005
- return /* @__PURE__ */ React.createElement(Fragment, { key: status }, /* @__PURE__ */ React.createElement(
1006
- Bar,
1007
- {
1008
- isAnimationActive: false,
1009
- type: "monotone",
1010
- barSize,
1011
- dataKey: status,
1012
- stackId: "statuses",
1013
- yAxisId: 1,
1014
- stroke: (_a = statusColorMap[status]) != null ? _a : "",
1015
- fillOpacity: 0.8,
1016
- fill: (_b = statusColorMap[status]) != null ? _b : ""
1017
- }
1018
- ));
1019
- }), getZoomArea({ yAxisId: 1 })))));
1020
- }
1021
- function humanTriggerReason(reason) {
1022
- if (reason === "manual") {
1023
- return "Triggered manually";
1024
- } else if (reason === "scm") {
1025
- return "Triggered by SCM";
1026
- } else if (reason === "internal") {
1027
- return "Triggered internally";
1028
- } else if (reason === "other") {
1029
- return "Triggered by another reason";
1030
- }
1031
- return `Triggered by ${reason}`;
1032
- }
1033
- function getBarSize(count) {
1034
- if (count < 20) {
1035
- return 10;
1036
- } else if (count < 40) {
1037
- return 8;
1038
- }
1039
- return 5;
1040
- }
1041
-
1042
- function switchValue(value) {
1043
- return typeof value === "object" ? value.value : value;
1044
- }
1045
- function switchText(value) {
1046
- var _a;
1047
- return typeof value === "object" ? (_a = value.text) != null ? _a : value.value : value;
1048
- }
1049
- function findParent(tagName, elem) {
1050
- let node = elem;
1051
- while (node.tagName !== tagName) {
1052
- node = node.parentElement;
1053
- if (!node) {
1054
- throw new Error(`Couldn't find ${tagName} parent`);
1055
- }
1056
- }
1057
- return node;
1058
- }
1059
- function ButtonSwitch(props) {
1060
- const { values, vertical = false } = props;
1061
- const onClick = useCallback(
1062
- (ev) => {
1063
- const btn = findParent("BUTTON", ev.target);
1064
- const index = [...btn.parentElement.children].findIndex(
1065
- (child) => child === btn
1066
- );
1067
- const value = switchValue(values[index]);
1068
- if (props.multi) {
1069
- props.onChange(
1070
- props.selection.includes(value) ? props.selection.filter((val) => val !== value) : [...props.selection, value]
1071
- );
1072
- } else {
1073
- props.onChange(value);
1074
- }
1075
- },
1076
- // eslint-disable-next-line react-hooks/exhaustive-deps
1077
- [values, props.selection, props.multi, props.onChange]
1078
- );
1079
- const hasSelection = (value) => {
1080
- if (props.multi) {
1081
- return props.selection.includes(value);
1082
- }
1083
- return props.selection === value;
1084
- };
1085
- const tooltipify = (value, elem) => typeof value === "object" && value.tooltip ? /* @__PURE__ */ React.createElement(
1086
- Tooltip$1,
1087
- {
1088
- key: value.value,
1089
- TransitionComponent: Zoom,
1090
- title: value.tooltip,
1091
- arrow: true
1092
- },
1093
- elem
1094
- ) : elem;
1095
- return /* @__PURE__ */ React.createElement(
1096
- ButtonGroup,
1097
- {
1098
- disableElevation: true,
1099
- orientation: vertical ? "vertical" : "horizontal",
1100
- variant: "outlined",
1101
- size: "small"
1102
- },
1103
- values.map(
1104
- (value) => tooltipify(
1105
- value,
1106
- /* @__PURE__ */ React.createElement(
1107
- Button,
1108
- {
1109
- key: switchValue(value),
1110
- color: hasSelection(switchValue(value)) ? "primary" : "default",
1111
- variant: hasSelection(switchValue(value)) ? "contained" : "outlined",
1112
- onClick
1113
- },
1114
- switchText(value)
1115
- )
1116
- )
1117
- )
1118
- );
1119
- }
1120
-
1121
- function Toggle({
1122
- checked,
1123
- setChecked,
1124
- children
1125
- }) {
1126
- const toggler = useCallback(() => {
1127
- setChecked(!checked);
1128
- }, [checked, setChecked]);
1129
- return /* @__PURE__ */ React.createElement(
1130
- FormControlLabel,
1131
- {
1132
- control: /* @__PURE__ */ React.createElement(Switch, { checked, onChange: toggler }),
1133
- label: children
1134
- }
1135
- );
1136
- }
1137
-
1138
- const useStyles$2 = makeStyles(
1139
- (theme) => ({
1140
- label: {
1141
- fontWeight: "normal",
1142
- margin: theme.spacing(0)
1143
- }
1144
- }),
1145
- {
1146
- name: "CicdStatisticsLabel"
1147
- }
1148
- );
1149
- function Label({ children }) {
1150
- const classes = useStyles$2();
1151
- return /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle2", className: classes.label }, children);
1152
- }
1153
-
1154
- const marks = [
1155
- 0,
1156
- 1,
1157
- 2,
1158
- 3,
1159
- 4,
1160
- 5,
1161
- 10,
1162
- 20,
1163
- 30,
1164
- 60,
1165
- 2 * 60,
1166
- 3 * 60,
1167
- 4 * 60,
1168
- 5 * 60,
1169
- 10 * 60,
1170
- 15 * 60,
1171
- 30 * 60,
1172
- 45 * 60,
1173
- 60 * 60
1174
- ].map((value, index) => ({
1175
- value: index,
1176
- label: formatDurationFromSeconds(value),
1177
- seconds: value
1178
- }));
1179
- function findMarkIndex(seconds) {
1180
- if (marks[0].seconds > seconds) {
1181
- return 0;
1182
- } else if (marks[marks.length - 1].seconds < seconds) {
1183
- return marks.length - 1;
1184
- }
1185
- for (let i = 0; i < marks.length - 1; ++i) {
1186
- const a = marks[i];
1187
- const b = marks[i + 1];
1188
- if (seconds === a.seconds) {
1189
- return i;
1190
- } else if (seconds === b.seconds) {
1191
- return i + 1;
1192
- } else if (a.seconds < seconds && b.seconds > seconds) {
1193
- return seconds - a.seconds < b.seconds - seconds ? i : i - 1;
1194
- }
1195
- }
1196
- return 0;
1197
- }
1198
- function formatDurationFromIndex(index) {
1199
- return formatDurationFromSeconds(marks[index].seconds);
1200
- }
1201
- function DurationSlider(props) {
1202
- const { header, value, setValue } = props;
1203
- const [curValue, setCurValue] = useState(value);
1204
- const debouncedSetValue = useMemo(() => debounce(setValue, 1e3), [setValue]);
1205
- const onChange = useCallback(
1206
- (_, index) => {
1207
- const millis = marks[index].seconds * 1e3;
1208
- setCurValue(millis);
1209
- debouncedSetValue(millis);
1210
- },
1211
- [debouncedSetValue]
1212
- );
1213
- const indexValue = useMemo(() => findMarkIndex(curValue / 1e3), [curValue]);
1214
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Label, null, header, " ", formatDuration(curValue)), /* @__PURE__ */ React.createElement(
1215
- Slider,
1216
- {
1217
- value: indexValue,
1218
- min: 0,
1219
- step: 1,
1220
- max: marks.length - 1,
1221
- marks: true,
1222
- getAriaValueText: formatDurationFromIndex,
1223
- valueLabelFormat: formatDurationFromIndex,
1224
- onChange,
1225
- valueLabelDisplay: "auto",
1226
- "aria-labelledby": "slider-hide-limit"
1227
- }
1228
- ));
1229
- }
1230
-
1231
- const useStyles$1 = makeStyles(
1232
- (theme) => ({
1233
- rootCard: {
1234
- padding: theme.spacing(0, 0, 0, 0),
1235
- margin: theme.spacing(0, 0, 2, 0)
1236
- },
1237
- updateButton: {
1238
- margin: theme.spacing(1, 0, 0, 0)
1239
- },
1240
- header: {
1241
- margin: theme.spacing(0, 0, 0, 0),
1242
- textTransform: "uppercase",
1243
- fontSize: 12,
1244
- fontWeight: "bold"
1245
- },
1246
- title: {
1247
- margin: theme.spacing(3, 0, 1, 0),
1248
- textTransform: "uppercase",
1249
- fontSize: 12,
1250
- fontWeight: "bold",
1251
- "&:first-child": {
1252
- margin: theme.spacing(1, 0, 1, 0)
1253
- }
1254
- },
1255
- buttonDescription: {
1256
- textTransform: "uppercase",
1257
- margin: theme.spacing(1, 0, 0, 1)
1258
- }
1259
- }),
1260
- {
1261
- name: "CicdStatistics"
1262
- }
1263
- );
1264
- function getDefaultChartFilter(cicdConfiguration) {
1265
- var _a, _b, _c, _d, _e, _f, _g, _h;
1266
- const toDate = (_b = (_a = cicdConfiguration.defaults) == null ? void 0 : _a.timeTo) != null ? _b : /* @__PURE__ */ new Date();
1267
- return {
1268
- fromDate: (_d = (_c = cicdConfiguration.defaults) == null ? void 0 : _c.timeFrom) != null ? _d : DateTime.fromJSDate(toDate).minus({ months: 1 }).toJSDate(),
1269
- toDate,
1270
- branch: (_f = (_e = cicdConfiguration.defaults) == null ? void 0 : _e.filterType) != null ? _f : "branch",
1271
- status: (_h = (_g = cicdConfiguration.defaults) == null ? void 0 : _g.filterStatus) != null ? _h : cicdConfiguration.availableStatuses.filter(
1272
- (status) => status === "succeeded" || status === "failed"
1273
- )
1274
- };
1275
- }
1276
- function isSameChartFilter(a, b) {
1277
- return a.branch === b.branch && [...a.status].sort().join(" ") === [...b.status].sort().join(" ") && DateTime.fromJSDate(a.fromDate).hasSame(
1278
- DateTime.fromJSDate(b.fromDate),
1279
- "day"
1280
- ) && DateTime.fromJSDate(a.toDate).hasSame(DateTime.fromJSDate(b.toDate), "day");
1281
- }
1282
- function getDefaultViewOptions(cicdConfiguration) {
1283
- var _a, _b, _c, _d;
1284
- return {
1285
- lowercaseNames: (_b = (_a = cicdConfiguration.defaults) == null ? void 0 : _a.lowercaseNames) != null ? _b : false,
1286
- normalizeTimeRange: (_d = (_c = cicdConfiguration.defaults) == null ? void 0 : _c.normalizeTimeRange) != null ? _d : true,
1287
- collapsedLimit: 60 * 1e3,
1288
- // 1m
1289
- hideLimit: 20 * 1e3,
1290
- // 20s
1291
- chartTypes: {
1292
- succeeded: ["duration"],
1293
- failed: ["count"],
1294
- enqueued: ["count"],
1295
- scheduled: ["count"],
1296
- running: ["count"],
1297
- aborted: ["count"],
1298
- stalled: ["count"],
1299
- expired: ["count"],
1300
- unknown: ["count"]
1301
- }
1302
- };
1303
- }
1304
- const branchValues = [
1305
- "master",
1306
- "branch",
1307
- {
1308
- value: "all",
1309
- tooltip: "NOTE; If the build pipelines are very different between master and branch builds, viewing them combined might not result in a very useful chart"
1310
- }
1311
- ];
1312
- const chartTypeValues = [
1313
- { value: "duration", text: /* @__PURE__ */ React.createElement(ShowChartIcon, null), tooltip: "Duration" },
1314
- { value: "count", text: /* @__PURE__ */ React.createElement(BarChartIcon, null), tooltip: "Count per day" }
1315
- ];
1316
- function ChartFilters(props) {
1317
- var _a;
1318
- const {
1319
- analysis,
1320
- cicdConfiguration,
1321
- initialFetchFilter,
1322
- currentFetchFilter,
1323
- onChangeFetchFilter,
1324
- updateFetchFilter,
1325
- initialViewOptions,
1326
- onChangeViewOptions
1327
- } = props;
1328
- const classes = useStyles$1();
1329
- const [internalRef] = useState({ first: true });
1330
- const [useNowAsToDate, setUseNowAsToDate] = useState(true);
1331
- const [toDate, setToDate] = useState(initialFetchFilter.toDate);
1332
- const [fromDate, setFromDate] = useState(initialFetchFilter.fromDate);
1333
- const [branch, setBranch] = useState(initialFetchFilter.branch);
1334
- const statusValues = cicdConfiguration.availableStatuses;
1335
- const [selectedStatus, setSelectedStatus] = useState(
1336
- initialFetchFilter.status
1337
- );
1338
- const [viewOptions, setViewOptions] = useState(initialViewOptions);
1339
- const setLowercaseNames = useCallback(
1340
- (lowercaseNames) => {
1341
- setViewOptions((old) => ({ ...old, lowercaseNames }));
1342
- },
1343
- [setViewOptions]
1344
- );
1345
- const setNormalizeTimeRange = useCallback(
1346
- (normalizeTimeRange) => {
1347
- setViewOptions((old) => ({ ...old, normalizeTimeRange }));
1348
- },
1349
- [setViewOptions]
1350
- );
1351
- const setHideLimit = useCallback(
1352
- (value) => {
1353
- setViewOptions((old) => ({ ...old, hideLimit: value }));
1354
- },
1355
- [setViewOptions]
1356
- );
1357
- const setCollapseLimit = useCallback(
1358
- (value) => {
1359
- setViewOptions((old) => ({ ...old, collapsedLimit: value }));
1360
- },
1361
- [setViewOptions]
1362
- );
1363
- const setChartType = useCallback(
1364
- (statusType, chartTypes) => {
1365
- setViewOptions((old) => ({
1366
- ...old,
1367
- chartTypes: { ...old.chartTypes, [statusType]: chartTypes }
1368
- }));
1369
- },
1370
- [setViewOptions]
1371
- );
1372
- const setChartTypeSpecific = useMemo(
1373
- () => Object.fromEntries(
1374
- statusTypes.map(
1375
- (status) => [
1376
- status,
1377
- (chartTypes) => setChartType(status, chartTypes)
1378
- ]
1379
- )
1380
- ),
1381
- [setChartType]
1382
- );
1383
- useEffect(() => {
1384
- onChangeViewOptions(viewOptions);
1385
- }, [onChangeViewOptions, viewOptions]);
1386
- useEffect(() => {
1387
- if (internalRef.first) {
1388
- internalRef.first = false;
1389
- return;
1390
- }
1391
- onChangeFetchFilter({
1392
- toDate,
1393
- fromDate,
1394
- branch,
1395
- status: selectedStatus
1396
- });
1397
- }, [
1398
- internalRef,
1399
- toDate,
1400
- fromDate,
1401
- branch,
1402
- selectedStatus,
1403
- onChangeFetchFilter
1404
- ]);
1405
- const toggleUseNowAsDate = useCallback(() => {
1406
- setUseNowAsToDate(!useNowAsToDate);
1407
- if (!DateTime.fromJSDate(toDate).hasSame(DateTime.now(), "day")) {
1408
- setToDate(/* @__PURE__ */ new Date());
1409
- }
1410
- }, [useNowAsToDate, toDate]);
1411
- const hasFetchFilterChanges = useMemo(
1412
- () => !currentFetchFilter || !isSameChartFilter(
1413
- {
1414
- toDate,
1415
- fromDate,
1416
- branch,
1417
- status: selectedStatus
1418
- },
1419
- currentFetchFilter
1420
- ),
1421
- [toDate, fromDate, branch, selectedStatus, currentFetchFilter]
1422
- );
1423
- const updateFilter = useCallback(() => {
1424
- updateFetchFilter({
1425
- toDate,
1426
- fromDate,
1427
- branch,
1428
- status: selectedStatus
1429
- });
1430
- }, [toDate, fromDate, branch, selectedStatus, updateFetchFilter]);
1431
- const inrefferedStatuses = (_a = analysis == null ? void 0 : analysis.statuses) != null ? _a : selectedStatus;
1432
- return /* @__PURE__ */ React.createElement(MuiPickersUtilsProvider, { utils: LuxonUtils }, /* @__PURE__ */ React.createElement(Card, { className: classes.rootCard }, /* @__PURE__ */ React.createElement(
1433
- CardHeader,
1434
- {
1435
- action: /* @__PURE__ */ React.createElement(
1436
- Button,
1437
- {
1438
- size: "small",
1439
- color: "secondary",
1440
- variant: "contained",
1441
- onClick: updateFilter,
1442
- disabled: !hasFetchFilterChanges
1443
- },
1444
- "Update"
1445
- ),
1446
- title: /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle2", className: classes.header }, "Fetching options")
1447
- }
1448
- ), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
1449
- Typography,
1450
- {
1451
- variant: "subtitle2",
1452
- className: `${classes.title} ${classes.title}`
1453
- },
1454
- "Date range"
1455
- ), /* @__PURE__ */ React.createElement(
1456
- KeyboardDatePicker,
1457
- {
1458
- autoOk: true,
1459
- variant: "inline",
1460
- inputVariant: "outlined",
1461
- label: "From date",
1462
- format: "yyyy-MM-dd",
1463
- value: fromDate,
1464
- InputAdornmentProps: { position: "start" },
1465
- onChange: (date) => {
1466
- var _a2;
1467
- return setFromDate((_a2 = date == null ? void 0 : date.toJSDate()) != null ? _a2 : /* @__PURE__ */ new Date());
1468
- }
1469
- }
1470
- ), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement(FormControl, { component: "fieldset" }, /* @__PURE__ */ React.createElement(FormGroup, null, /* @__PURE__ */ React.createElement(
1471
- FormControlLabel,
1472
- {
1473
- control: /* @__PURE__ */ React.createElement(
1474
- Switch,
1475
- {
1476
- checked: useNowAsToDate,
1477
- onChange: toggleUseNowAsDate
1478
- }
1479
- ),
1480
- label: /* @__PURE__ */ React.createElement(Label, null, "To today")
1481
- }
1482
- ), useNowAsToDate ? null : /* @__PURE__ */ React.createElement(
1483
- KeyboardDatePicker,
1484
- {
1485
- autoOk: true,
1486
- variant: "inline",
1487
- inputVariant: "outlined",
1488
- label: "To date",
1489
- format: "yyyy-MM-dd",
1490
- value: toDate,
1491
- InputAdornmentProps: { position: "start" },
1492
- onChange: (date) => {
1493
- var _a2;
1494
- return setToDate((_a2 = date == null ? void 0 : date.toJSDate()) != null ? _a2 : /* @__PURE__ */ new Date());
1495
- }
1496
- }
1497
- ))), /* @__PURE__ */ React.createElement(
1498
- Typography,
1499
- {
1500
- variant: "subtitle2",
1501
- className: `${classes.title} ${classes.title}`
1502
- },
1503
- "Branch"
1504
- ), /* @__PURE__ */ React.createElement(
1505
- ButtonSwitch,
1506
- {
1507
- values: branchValues,
1508
- selection: branch,
1509
- onChange: setBranch
1510
- }
1511
- ), /* @__PURE__ */ React.createElement(
1512
- Typography,
1513
- {
1514
- variant: "subtitle2",
1515
- className: `${classes.title} ${classes.title}`
1516
- },
1517
- "Status"
1518
- ), /* @__PURE__ */ React.createElement(
1519
- ButtonSwitch,
1520
- {
1521
- values: statusValues,
1522
- multi: true,
1523
- vertical: true,
1524
- selection: selectedStatus,
1525
- onChange: setSelectedStatus
1526
- }
1527
- ))), /* @__PURE__ */ React.createElement(Card, { className: classes.rootCard }, /* @__PURE__ */ React.createElement(
1528
- CardHeader,
1529
- {
1530
- title: /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle2", className: classes.header }, "View options")
1531
- }
1532
- ), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
1533
- Toggle,
1534
- {
1535
- checked: viewOptions.lowercaseNames,
1536
- setChecked: setLowercaseNames
1537
- },
1538
- /* @__PURE__ */ React.createElement(
1539
- Tooltip$1,
1540
- {
1541
- arrow: true,
1542
- title: "Lowercasing names can reduce duplications when stage names have changed casing"
1543
- },
1544
- /* @__PURE__ */ React.createElement(Label, null, "Lowercase names")
1545
- )
1546
- ), /* @__PURE__ */ React.createElement(
1547
- Toggle,
1548
- {
1549
- checked: viewOptions.normalizeTimeRange,
1550
- setChecked: setNormalizeTimeRange
1551
- },
1552
- /* @__PURE__ */ React.createElement(
1553
- Tooltip$1,
1554
- {
1555
- arrow: true,
1556
- title: "All charts will use the same x-axis. This reduces confusion when stages have been altered over time and only appear in a part of the time range."
1557
- },
1558
- /* @__PURE__ */ React.createElement(Label, null, "Normalize time range")
1559
- )
1560
- ), /* @__PURE__ */ React.createElement(
1561
- DurationSlider,
1562
- {
1563
- header: "Hide under peak",
1564
- value: viewOptions.hideLimit,
1565
- setValue: setHideLimit
1566
- }
1567
- ), /* @__PURE__ */ React.createElement(
1568
- DurationSlider,
1569
- {
1570
- header: "Collapse under peak",
1571
- value: viewOptions.collapsedLimit,
1572
- setValue: setCollapseLimit
1573
- }
1574
- ), /* @__PURE__ */ React.createElement(
1575
- Typography,
1576
- {
1577
- variant: "subtitle2",
1578
- className: `${classes.title} ${classes.title}`
1579
- },
1580
- "Chart styles"
1581
- ), inrefferedStatuses.map((status) => /* @__PURE__ */ React.createElement(Grid, { key: status, container: true, spacing: 0 }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
1582
- ButtonSwitch,
1583
- {
1584
- values: chartTypeValues,
1585
- selection: viewOptions.chartTypes[status],
1586
- onChange: setChartTypeSpecific[status],
1587
- multi: true
1588
- }
1589
- )), /* @__PURE__ */ React.createElement(Grid, { item: true, className: classes.buttonDescription }, /* @__PURE__ */ React.createElement("div", null, status)))))));
1590
- }
1591
-
1592
- const stepProgressStyle = {
1593
- marginTop: 6
1594
- };
1595
- const sentry = Symbol();
1596
- function useAsyncChain(parentState, fn, deps) {
1597
- const childState = useAsync(
1598
- async () => !parentState.value ? sentry : fn(parentState.value),
1599
- [!parentState.error, !parentState.loading, parentState.value, ...deps]
1600
- );
1601
- if (!parentState.value) {
1602
- return parentState;
1603
- } else if (childState.value === sentry) {
1604
- return { loading: true };
1605
- }
1606
- return childState;
1607
- }
1608
- function renderFallbacks(state, success) {
1609
- if (state.loading) {
1610
- return /* @__PURE__ */ React.createElement(ViewProgress, { state });
1611
- } else if (state.error) {
1612
- return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, state.error.stack);
1613
- }
1614
- return success(state.value);
1615
- }
1616
- function ViewProgress({
1617
- state
1618
- }) {
1619
- var _a, _b;
1620
- const { Progress } = useApp().getComponents();
1621
- const stateAsSingleProgress = state;
1622
- const stateAsStepProgress = state;
1623
- if (!stateAsSingleProgress.progress && !stateAsSingleProgress.progressBuffer && !stateAsStepProgress.steps) {
1624
- return /* @__PURE__ */ React.createElement(Progress, null);
1625
- } else if (stateAsSingleProgress.progress !== void 0) {
1626
- return /* @__PURE__ */ React.createElement(Box, { sx: { width: "100%" } }, /* @__PURE__ */ React.createElement(
1627
- LinearProgress,
1628
- {
1629
- variant: "buffer",
1630
- value: ((_a = stateAsSingleProgress.progress) != null ? _a : 0) * 100,
1631
- valueBuffer: ((_b = stateAsSingleProgress.progressBuffer) != null ? _b : 0) * 100
1632
- }
1633
- ));
1634
- }
1635
- return /* @__PURE__ */ React.createElement(Box, { sx: { width: "100%" } }, /* @__PURE__ */ React.createElement(Timeline, null, stateAsStepProgress.steps.map((step, index) => {
1636
- var _a2, _b2;
1637
- return /* @__PURE__ */ React.createElement(TimelineItem, { key: index }, /* @__PURE__ */ React.createElement(TimelineOppositeContent, null, step.title), /* @__PURE__ */ React.createElement(TimelineSeparator, null, /* @__PURE__ */ React.createElement(TimelineDot, { color: getDotColor(step) }), index < stateAsStepProgress.steps.length - 1 ? /* @__PURE__ */ React.createElement(TimelineConnector, null) : null), /* @__PURE__ */ React.createElement(TimelineContent, null, !step.progress && !step.progressBuffer ? null : /* @__PURE__ */ React.createElement(
1638
- LinearProgress,
1639
- {
1640
- style: stepProgressStyle,
1641
- variant: "buffer",
1642
- value: ((_a2 = step.progress) != null ? _a2 : 0) * 100,
1643
- valueBuffer: ((_b2 = step.progressBuffer) != null ? _b2 : 0) * 100
1644
- }
1645
- )));
1646
- })));
1647
- }
1648
- function getDotColor(step) {
1649
- var _a;
1650
- const progress = (_a = step.progress) != null ? _a : 0;
1651
- if (progress >= 1) {
1652
- return "primary";
1653
- } else if (progress > 0) {
1654
- return "secondary";
1655
- }
1656
- return "grey";
1657
- }
1658
-
1659
- function sortFilterStatusType(statuses) {
1660
- const statusSet = new Set(statuses);
1661
- const sorted = ["all", ...statusTypes].filter((status) => {
1662
- if (statusSet.has(status)) {
1663
- statusSet.delete(status);
1664
- return true;
1665
- }
1666
- return false;
1667
- });
1668
- return [...sorted, ...statusSet];
1669
- }
1670
-
1671
- function EntityPageCicdCharts() {
1672
- const state = useCicdConfiguration();
1673
- return renderFallbacks(state, (value) => /* @__PURE__ */ React.createElement(ZoomProvider, null, /* @__PURE__ */ React.createElement(CicdCharts, { cicdConfiguration: value })));
1674
- }
1675
- const useStyles = makeStyles(
1676
- (theme) => ({
1677
- pane: {
1678
- padding: theme.spacing(1, 1, 1, 1)
1679
- }
1680
- }),
1681
- {
1682
- name: "CicdStatisticsView"
1683
- }
1684
- );
1685
- function startOfDay(date) {
1686
- return DateTime.fromJSDate(date).startOf("day").toJSDate();
1687
- }
1688
- function endOfDay(date) {
1689
- return DateTime.fromJSDate(date).endOf("day").toJSDate();
1690
- }
1691
- function cleanChartFilter(filter) {
1692
- return {
1693
- ...filter,
1694
- status: sortFilterStatusType(filter.status)
1695
- };
1696
- }
1697
- function CicdCharts(props) {
1698
- const { cicdConfiguration } = props;
1699
- const errorApi = useApi(errorApiRef);
1700
- const { entity } = useEntity();
1701
- const classes = useStyles();
1702
- const { resetZoom } = useZoom();
1703
- const [chartFilter, setChartFilter] = useState(
1704
- getDefaultChartFilter(cicdConfiguration)
1705
- );
1706
- const [fetchedChartData, setFetchedChartData] = useState({
1707
- abortController: null,
1708
- chartFilter
1709
- });
1710
- const [viewOptions, setViewOptions] = useState(
1711
- getDefaultViewOptions(cicdConfiguration)
1712
- );
1713
- const fetchStatisticsOptions = useMemo(() => {
1714
- const abortController = new AbortController();
1715
- fetchedChartData.abortController = abortController;
1716
- return {
1717
- abortController,
1718
- entity,
1719
- timeFrom: startOfDay(fetchedChartData.chartFilter.fromDate),
1720
- timeTo: endOfDay(fetchedChartData.chartFilter.toDate),
1721
- filterStatus: fetchedChartData.chartFilter.status,
1722
- filterType: fetchedChartData.chartFilter.branch
1723
- };
1724
- }, [entity, fetchedChartData]);
1725
- const statisticsState = useCicdStatistics(fetchStatisticsOptions);
1726
- const updateFilter = useCallback(() => {
1727
- var _a;
1728
- (_a = fetchedChartData.abortController) == null ? void 0 : _a.abort();
1729
- setFetchedChartData({ abortController: null, chartFilter });
1730
- }, [fetchedChartData, setFetchedChartData, chartFilter]);
1731
- const chartableStagesState = useAsyncChain(
1732
- statisticsState,
1733
- async (value) => buildsToChartableStages(
1734
- await cleanupBuildTree(value.builds, {
1735
- formatStageName: cicdConfiguration.formatStageName,
1736
- lowerCase: viewOptions.lowercaseNames
1737
- }),
1738
- { normalizeTimeRange: viewOptions.normalizeTimeRange }
1739
- ),
1740
- [statisticsState, cicdConfiguration, viewOptions]
1741
- );
1742
- useEffect(() => {
1743
- resetZoom();
1744
- }, [resetZoom, statisticsState.value]);
1745
- const onFilterChange = useCallback((filter) => {
1746
- setChartFilter(cleanChartFilter(filter));
1747
- }, []);
1748
- const onViewOptionsChange = useCallback(
1749
- (options) => {
1750
- setViewOptions(options);
1751
- },
1752
- [setViewOptions]
1753
- );
1754
- useEffect(() => {
1755
- var _a;
1756
- if (!chartableStagesState.error || ((_a = chartableStagesState.error) == null ? void 0 : _a.name) === "AbortError") {
1757
- return;
1758
- }
1759
- errorApi.post(chartableStagesState.error);
1760
- }, [errorApi, chartableStagesState.error]);
1761
- return /* @__PURE__ */ React.createElement(Grid, { container: true }, /* @__PURE__ */ React.createElement(Grid, { item: true, lg: 2, className: classes.pane }, /* @__PURE__ */ React.createElement(
1762
- ChartFilters,
1763
- {
1764
- analysis: chartableStagesState.value,
1765
- cicdConfiguration,
1766
- initialFetchFilter: chartFilter,
1767
- currentFetchFilter: fetchedChartData.chartFilter,
1768
- onChangeFetchFilter: onFilterChange,
1769
- updateFetchFilter: updateFilter,
1770
- initialViewOptions: viewOptions,
1771
- onChangeViewOptions: onViewOptionsChange
1772
- }
1773
- )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, lg: 10, className: classes.pane }, renderFallbacks(chartableStagesState, (chartableStages) => {
1774
- var _a, _b, _c, _d, _e;
1775
- return /* @__PURE__ */ React.createElement(React.Fragment, null, chartableStages.stages.size > 0 ? null : /* @__PURE__ */ React.createElement(Alert, { severity: "info" }, "No data"), !((_b = (_a = statisticsState.value) == null ? void 0 : _a.builds) == null ? void 0 : _b.length) || !((_e = (_d = (_c = chartableStagesState.value) == null ? void 0 : _c.daily) == null ? void 0 : _d.values) == null ? void 0 : _e.length) ? null : /* @__PURE__ */ React.createElement(StatusChart, { analysis: chartableStagesState.value }), /* @__PURE__ */ React.createElement(
1776
- StageChart,
1777
- {
1778
- stage: chartableStages.total,
1779
- defaultCollapsed: 0,
1780
- defaultHidden: viewOptions.hideLimit,
1781
- chartTypes: viewOptions.chartTypes
1782
- }
1783
- ), [...chartableStages.stages.entries()].map(([name, stage]) => /* @__PURE__ */ React.createElement(
1784
- StageChart,
1785
- {
1786
- key: name,
1787
- stage,
1788
- defaultCollapsed: viewOptions.collapsedLimit,
1789
- defaultHidden: viewOptions.hideLimit,
1790
- chartTypes: viewOptions.chartTypes
1791
- }
1792
- )));
1793
- })));
1794
- }
1795
-
1796
- export { EntityPageCicdCharts };
1797
- //# sourceMappingURL=entity-page-CNIrdNBp.esm.js.map