@backstage-community/plugin-cicd-statistics 0.1.37
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.
- package/CHANGELOG.md +991 -0
- package/README.md +13 -0
- package/dist/esm/entity-page-CNIrdNBp.esm.js +1797 -0
- package/dist/esm/entity-page-CNIrdNBp.esm.js.map +1 -0
- package/dist/index.d.ts +263 -0
- package/dist/index.esm.js +45 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,1797 @@
|
|
|
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
|