@backstage-community/plugin-gocd 0.1.41

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/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # GoCD
2
+
3
+ This plugin is an open-source tool which is used in software development to help teams and organizations automate the continuous delivery of software.
4
+
5
+ - View recent GoCD Builds
6
+
7
+ ![gocd-builds-card](./docs/gocd-plugin-screenshot.png)
8
+
9
+ ## Installation
10
+
11
+ GoCD Plugin exposes an entity tab component named `EntityGoCdContent`. You can include it in the
12
+ [`EntityPage.tsx`](https://github.com/backstage/backstage/blob/master/packages/app/src/components/catalog/EntityPage.tsx)`:
13
+
14
+ ```tsx
15
+ // At the top imports
16
+ import { EntityGoCdContent } from '@backstage-community/plugin-gocd';
17
+
18
+ // Farther down at the component declaration
19
+ const componentEntityPage = (
20
+ <EntityLayout>
21
+ {/* Place the following section where you want the tab to appear */}
22
+ <EntityLayout.Route path="/go-cd" title="GoCD">
23
+ <EntityGoCdContent />
24
+ </EntityLayout.Route>
25
+ ```
26
+
27
+ Now your plugin should be visible as a tab at the top of the entity pages,
28
+ specifically for components that are of the type `component`.
29
+ However, it warns of a missing `gocd.org/pipelines` annotation.
30
+
31
+ Add the annotation to your component [catalog-info.yaml](https://github.com/backstage/backstage/blob/master/catalog-info.yaml). You can refer to multiple GoCD pipelines by defining their names separated by commas, as shown in the highlighted example below:
32
+
33
+ ```yaml
34
+ metadata:
35
+ annotations:
36
+ gocd.org/pipelines: '<NAME OF THE PIPELINE 1>[,<NAME OF PIPELINE 2>]'
37
+ ```
38
+
39
+ The plugin requires to configure a GoCD API proxy with a `GOCD_AUTH_CREDENTIALS` for authentication in the [app-config.yaml](https://github.com/backstage/backstage/blob/master/app-config.yaml). Its value is an opaque token you can obtain directly from your GoCD instance, in the shape `base64(user + ':' + pass)`. For example, a user "root" and password "root" would become `base64('root:root') = cm9vdDpyb290`:
40
+
41
+ ```yaml
42
+ proxy:
43
+ '/gocd':
44
+ target: '<go cd server host>/go/api'
45
+ allowedMethods: ['GET']
46
+ allowedHeaders: ['Authorization']
47
+ headers:
48
+ Authorization: Basic ${GOCD_AUTH_CREDENTIALS}
49
+ ```
50
+
51
+ You should also include the `gocd` section to allow for the plugin to redirect back to GoCD pipelines in your deployed instance:
52
+
53
+ ```yaml
54
+ gocd:
55
+ baseUrl: <go cd server host>
56
+ ```
package/config.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /*
2
+ * Copyright 2021 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export interface Config {
17
+ /** Configurations for the GoCD plugin */
18
+ gocd: {
19
+ /**
20
+ * The base url of the GoCD installation.
21
+ * @visibility frontend
22
+ */
23
+ baseUrl: string;
24
+ };
25
+ }
@@ -0,0 +1,458 @@
1
+ import { ResponseError } from '@backstage/errors';
2
+ import { createApiRef, createPlugin, createApiFactory, discoveryApiRef, createComponentExtension, useApi, configApiRef } from '@backstage/core-plugin-api';
3
+ import React, { useState } from 'react';
4
+ import { useEntity, MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
5
+ import { Table, SubvalueCell, StatusWarning, StatusPending, StatusAborted, StatusRunning, StatusError, StatusOK, Link, EmptyState, Page, Content, ContentHeader } from '@backstage/core-components';
6
+ import useAsync from 'react-use/esm/useAsync';
7
+ import { getEntitySourceLocation } from '@backstage/catalog-model';
8
+ import Alert from '@material-ui/lab/Alert';
9
+ import Button from '@material-ui/core/Button';
10
+ import GitHubIcon from '@material-ui/icons/GitHub';
11
+ import { DateTime, Duration } from 'luxon';
12
+ import { mean, groupBy } from 'lodash';
13
+ import Box from '@material-ui/core/Box';
14
+ import Grid from '@material-ui/core/Grid';
15
+ import Card from '@material-ui/core/Card';
16
+ import CardContent from '@material-ui/core/CardContent';
17
+ import Tooltip from '@material-ui/core/Tooltip';
18
+ import Typography from '@material-ui/core/Typography';
19
+ import FormControl from '@material-ui/core/FormControl';
20
+ import InputLabel from '@material-ui/core/InputLabel';
21
+ import MenuItem from '@material-ui/core/MenuItem';
22
+ import Select from '@material-ui/core/Select';
23
+ import { makeStyles, createStyles } from '@material-ui/core/styles';
24
+
25
+ const DEFAULT_PAGE_SIZE = 100;
26
+ class GoCdClientApi {
27
+ constructor(discoveryApi) {
28
+ this.discoveryApi = discoveryApi;
29
+ }
30
+ async getPipelineHistory(pipelineName) {
31
+ const baseUrl = await this.discoveryApi.getBaseUrl("proxy");
32
+ const pipelineHistoryResponse = await fetch(
33
+ `${baseUrl}/gocd/pipelines/${pipelineName}/history?page_size=${DEFAULT_PAGE_SIZE}`,
34
+ {
35
+ headers: {
36
+ Accept: "application/vnd.go.cd+json"
37
+ }
38
+ }
39
+ );
40
+ if (!pipelineHistoryResponse.ok) {
41
+ throw await ResponseError.fromResponse(pipelineHistoryResponse);
42
+ }
43
+ return await pipelineHistoryResponse.json();
44
+ }
45
+ }
46
+
47
+ const gocdApiRef = createApiRef({
48
+ id: "plugin.gocd.service"
49
+ });
50
+ const gocdPlugin = createPlugin({
51
+ id: "gocd",
52
+ apis: [
53
+ createApiFactory({
54
+ api: gocdApiRef,
55
+ deps: { discoveryApi: discoveryApiRef },
56
+ factory: ({ discoveryApi }) => {
57
+ return new GoCdClientApi(discoveryApi);
58
+ }
59
+ })
60
+ ]
61
+ });
62
+
63
+ const EntityGoCdContent = gocdPlugin.provide(
64
+ createComponentExtension({
65
+ name: "EntityGoCdContent",
66
+ component: {
67
+ lazy: () => import('./index-CA4IGciN.esm.js').then(
68
+ (m) => m.GoCdBuildsComponent
69
+ )
70
+ }
71
+ })
72
+ );
73
+
74
+ var GoCdBuildResultStatus = /* @__PURE__ */ ((GoCdBuildResultStatus2) => {
75
+ GoCdBuildResultStatus2[GoCdBuildResultStatus2["running"] = 0] = "running";
76
+ GoCdBuildResultStatus2[GoCdBuildResultStatus2["successful"] = 1] = "successful";
77
+ GoCdBuildResultStatus2[GoCdBuildResultStatus2["warning"] = 2] = "warning";
78
+ GoCdBuildResultStatus2[GoCdBuildResultStatus2["aborted"] = 3] = "aborted";
79
+ GoCdBuildResultStatus2[GoCdBuildResultStatus2["error"] = 4] = "error";
80
+ GoCdBuildResultStatus2[GoCdBuildResultStatus2["pending"] = 5] = "pending";
81
+ return GoCdBuildResultStatus2;
82
+ })(GoCdBuildResultStatus || {});
83
+ const toBuildResultStatus = (status) => {
84
+ switch (status.toLocaleLowerCase("en-US")) {
85
+ case "passed":
86
+ return 1 /* successful */;
87
+ case "failed":
88
+ return 4 /* error */;
89
+ case "aborted":
90
+ return 3 /* aborted */;
91
+ case "building":
92
+ return 0 /* running */;
93
+ case "pending":
94
+ return 5 /* pending */;
95
+ default:
96
+ return 3 /* aborted */;
97
+ }
98
+ };
99
+
100
+ const renderTrigger = (build) => {
101
+ const subvalue = /* @__PURE__ */ React.createElement(React.Fragment, null, build.pipeline, /* @__PURE__ */ React.createElement("br", null), build.author);
102
+ return /* @__PURE__ */ React.createElement(SubvalueCell, { value: build.message, subvalue });
103
+ };
104
+ const renderStages = (build) => {
105
+ return build.stages.map((s) => {
106
+ switch (s.status) {
107
+ case GoCdBuildResultStatus.successful: {
108
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(StatusOK, null, s.text), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("br", null));
109
+ }
110
+ case GoCdBuildResultStatus.error: {
111
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(StatusError, null, s.text), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("br", null));
112
+ }
113
+ case GoCdBuildResultStatus.running: {
114
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(StatusRunning, null, s.text), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("br", null));
115
+ }
116
+ case GoCdBuildResultStatus.aborted: {
117
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(StatusAborted, null, s.text), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("br", null));
118
+ }
119
+ case GoCdBuildResultStatus.pending: {
120
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(StatusPending, null, s.text), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("br", null));
121
+ }
122
+ default: {
123
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(StatusWarning, null, s.text), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("br", null));
124
+ }
125
+ }
126
+ });
127
+ };
128
+ const renderSource = (build) => {
129
+ return /* @__PURE__ */ React.createElement(
130
+ Button,
131
+ {
132
+ href: build.source,
133
+ target: "_blank",
134
+ rel: "noreferrer",
135
+ startIcon: /* @__PURE__ */ React.createElement(GitHubIcon, null)
136
+ },
137
+ build.commitHash
138
+ );
139
+ };
140
+ const renderId = (goCdBaseUrl, build) => {
141
+ const goCdBuildUrl = `${goCdBaseUrl}/go/pipelines/value_stream_map/${build.buildSlug}`;
142
+ return /* @__PURE__ */ React.createElement(
143
+ SubvalueCell,
144
+ {
145
+ value: /* @__PURE__ */ React.createElement(Link, { to: goCdBuildUrl, target: "_blank", rel: "noreferrer" }, build.id),
146
+ subvalue: build.triggerTime && DateTime.fromMillis(build.triggerTime).toLocaleString(
147
+ DateTime.DATETIME_MED
148
+ )
149
+ }
150
+ );
151
+ };
152
+ const renderError = (error) => {
153
+ return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, error.message);
154
+ };
155
+ const toBuildResults = (entity, builds) => {
156
+ const entitySourceLocation = getEntitySourceLocation(entity).target.split("/tree")[0];
157
+ return builds.map((build) => {
158
+ var _a, _b, _c, _d, _e, _f, _g;
159
+ return {
160
+ id: build.counter,
161
+ source: `${entitySourceLocation}/commit/${(_b = (_a = build.build_cause) == null ? void 0 : _a.material_revisions[0]) == null ? void 0 : _b.modifications[0].revision}`,
162
+ stages: build.stages.map((s) => ({
163
+ status: toBuildResultStatus(s.status),
164
+ text: s.name
165
+ })),
166
+ buildSlug: `${build.name}/${build.counter}`,
167
+ message: (_e = (_d = (_c = build.build_cause) == null ? void 0 : _c.material_revisions[0]) == null ? void 0 : _d.modifications[0].comment) != null ? _e : "",
168
+ pipeline: build.name,
169
+ author: (_g = (_f = build.build_cause) == null ? void 0 : _f.material_revisions[0]) == null ? void 0 : _g.modifications[0].user_name,
170
+ commitHash: build.label,
171
+ triggerTime: build.scheduled_date
172
+ };
173
+ });
174
+ };
175
+ const GoCdBuildsTable = (props) => {
176
+ const { pipelineHistory, loading, error } = props;
177
+ const { entity } = useEntity();
178
+ const [, setSelectedSearchTerm] = useState("");
179
+ const columns = [
180
+ {
181
+ title: "ID",
182
+ field: "id",
183
+ highlight: true,
184
+ render: (build) => renderId(props.goCdBaseUrl, build)
185
+ },
186
+ {
187
+ title: "Trigger",
188
+ customFilterAndSearch: (query, row) => `${row.message} ${row.pipeline} ${row.author}`.toLocaleUpperCase("en-US").includes(query.toLocaleUpperCase("en-US")),
189
+ field: "message",
190
+ highlight: true,
191
+ render: (build) => renderTrigger(build)
192
+ },
193
+ {
194
+ title: "Stages",
195
+ field: "status",
196
+ render: (build) => renderStages(build)
197
+ },
198
+ {
199
+ title: "Source",
200
+ field: "source",
201
+ render: (build) => renderSource(build)
202
+ }
203
+ ];
204
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, error && renderError(error), !error && /* @__PURE__ */ React.createElement(
205
+ Table,
206
+ {
207
+ title: "GoCD builds",
208
+ isLoading: loading,
209
+ options: {
210
+ search: true,
211
+ searchAutoFocus: true,
212
+ debounceInterval: 800,
213
+ paging: true,
214
+ padding: "dense",
215
+ pageSizeOptions: [5, 10, 20, 50],
216
+ showFirstLastPageButtons: false,
217
+ pageSize: 20
218
+ },
219
+ columns,
220
+ data: pipelineHistory ? toBuildResults(entity, pipelineHistory.pipelines) || [] : [],
221
+ onSearchChange: (searchTerm) => setSelectedSearchTerm(searchTerm)
222
+ }
223
+ ));
224
+ };
225
+
226
+ function runFrequency(pipelineHistory) {
227
+ const lastMonth = DateTime.now().minus({ month: 1 });
228
+ const buildLastMonth = pipelineHistory.pipelines.map((p) => p.scheduled_date).filter((d) => !!d).map((d) => DateTime.fromMillis(d)).filter((d) => d > lastMonth).length;
229
+ return `${buildLastMonth} last month`;
230
+ }
231
+ function meanTimeBetweenFailures(jobs) {
232
+ const timeBetweenFailures = [];
233
+ for (let index = 1; index < jobs.length; index++) {
234
+ const job = jobs[index];
235
+ if (!job.result) {
236
+ continue;
237
+ }
238
+ if (toBuildResultStatus(job.result) === GoCdBuildResultStatus.error) {
239
+ let previousFailedJob = null;
240
+ for (let j = index - 1; j >= 0; j--) {
241
+ const candidateJob = jobs[j];
242
+ if (!candidateJob.result) {
243
+ continue;
244
+ }
245
+ if (toBuildResultStatus(candidateJob.result) === GoCdBuildResultStatus.error) {
246
+ previousFailedJob = candidateJob;
247
+ break;
248
+ }
249
+ }
250
+ if (!previousFailedJob || !job.scheduled_date || !previousFailedJob.scheduled_date) {
251
+ continue;
252
+ }
253
+ const failedJobDate = DateTime.fromMillis(job.scheduled_date);
254
+ const previousFailedJobDate = DateTime.fromMillis(
255
+ previousFailedJob.scheduled_date
256
+ );
257
+ const timeBetweenFailure = failedJobDate.diff(previousFailedJobDate);
258
+ timeBetweenFailures.push(timeBetweenFailure);
259
+ }
260
+ }
261
+ return formatMean(timeBetweenFailures);
262
+ }
263
+ function meanTimeToRecovery(jobs) {
264
+ const timeToRecoverIntervals = [];
265
+ for (let index = 0; index < jobs.length; index++) {
266
+ const job = jobs[index];
267
+ if (!job.result) {
268
+ continue;
269
+ }
270
+ if (toBuildResultStatus(job.result) === GoCdBuildResultStatus.error) {
271
+ let nextSuccessfulJob = null;
272
+ for (let j = index + 1; j < jobs.length; j++) {
273
+ const candidateJob = jobs[j];
274
+ if (!candidateJob.result) {
275
+ continue;
276
+ }
277
+ if (toBuildResultStatus(candidateJob.result) === GoCdBuildResultStatus.successful) {
278
+ nextSuccessfulJob = candidateJob;
279
+ break;
280
+ }
281
+ }
282
+ if (!nextSuccessfulJob || !job.scheduled_date || !nextSuccessfulJob.scheduled_date) {
283
+ continue;
284
+ }
285
+ const failedJobDate = DateTime.fromMillis(job.scheduled_date);
286
+ const successfulJobDate = DateTime.fromMillis(
287
+ nextSuccessfulJob.scheduled_date
288
+ );
289
+ const timeToRecovery = successfulJobDate.diff(failedJobDate);
290
+ timeToRecoverIntervals.push(timeToRecovery);
291
+ }
292
+ }
293
+ return formatMean(timeToRecoverIntervals);
294
+ }
295
+ function formatMean(durations) {
296
+ if (durations.length === 0) {
297
+ return "N/A";
298
+ }
299
+ const mttr = Duration.fromMillis(
300
+ mean(durations.map((i) => i.milliseconds))
301
+ );
302
+ return mttr.toFormat("d'd' h'h' m'm' s's'");
303
+ }
304
+ function failureRate(jobs) {
305
+ const resultGroups = new Map(
306
+ Object.entries(groupBy(jobs, "result")).map(([key, value]) => [
307
+ toBuildResultStatus(key),
308
+ value.flat()
309
+ ])
310
+ );
311
+ const failedJobs = resultGroups.get(GoCdBuildResultStatus.error);
312
+ if (!failedJobs) {
313
+ return {
314
+ title: "0",
315
+ subtitle: "(no failed jobs found)"
316
+ };
317
+ }
318
+ resultGroups.delete(GoCdBuildResultStatus.error);
319
+ const nonFailedJobs = Array.from(resultGroups.values()).flat();
320
+ const totalJobs = failedJobs.length + nonFailedJobs.length;
321
+ const percentage = failedJobs.length / totalJobs * 100;
322
+ const decimalPercentage = (Math.round(percentage * 100) / 100).toFixed(2);
323
+ return {
324
+ title: `${decimalPercentage}%`,
325
+ subtitle: `(${failedJobs.length} out of ${totalJobs} jobs)`
326
+ };
327
+ }
328
+ const GoCdBuildsInsights = (props) => {
329
+ const { pipelineHistory, loading, error } = props;
330
+ if (loading || error || !pipelineHistory) {
331
+ return /* @__PURE__ */ React.createElement(React.Fragment, null);
332
+ }
333
+ const stages = pipelineHistory.pipelines.slice().reverse().map((p) => p.stages).flat();
334
+ const jobs = stages.map((s) => s.jobs).flat();
335
+ const failureRateObj = failureRate(jobs);
336
+ return /* @__PURE__ */ React.createElement(Box, { "data-testid": "GoCdBuildsInsightsBox", sx: { mb: 1 } }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 1 }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React.createElement(Tooltip, { title: "What is your deployment frequency?" }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Run Frequency"), /* @__PURE__ */ React.createElement(Typography, { variant: "h4" }, runFrequency(pipelineHistory)))))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React.createElement(Tooltip, { title: "How long does it take to fix a failure?" }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Mean Time to Recovery"), /* @__PURE__ */ React.createElement(Typography, { variant: "h4" }, meanTimeToRecovery(jobs)))))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React.createElement(Tooltip, { title: "How often do changes fail?" }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Mean Time Between Failures"), /* @__PURE__ */ React.createElement(Typography, { variant: "h4" }, meanTimeBetweenFailures(jobs)))))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React.createElement(Tooltip, { title: "What percentage of changes result in a failure?" }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Failure Rate"), /* @__PURE__ */ React.createElement(Typography, { variant: "h4" }, failureRateObj.title), /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, failureRateObj.subtitle)))))));
337
+ };
338
+
339
+ const useStyles = makeStyles(
340
+ (theme) => createStyles({
341
+ formControl: {
342
+ margin: theme.spacing(1),
343
+ minWidth: 120
344
+ },
345
+ selectEmpty: {
346
+ marginTop: theme.spacing(2)
347
+ }
348
+ })
349
+ );
350
+ const renderItems = (items) => {
351
+ return items.map((item) => {
352
+ return /* @__PURE__ */ React.createElement(MenuItem, { value: item.value, key: item.label }, item.label);
353
+ });
354
+ };
355
+ const SelectComponent = ({
356
+ value,
357
+ items,
358
+ label,
359
+ onChange
360
+ }) => {
361
+ const classes = useStyles();
362
+ const handleChange = (event) => {
363
+ const val = event.target.value;
364
+ onChange(val);
365
+ };
366
+ return /* @__PURE__ */ React.createElement(
367
+ FormControl,
368
+ {
369
+ variant: "outlined",
370
+ className: classes.formControl,
371
+ disabled: items.length === 0
372
+ },
373
+ /* @__PURE__ */ React.createElement(InputLabel, null, label),
374
+ /* @__PURE__ */ React.createElement(Select, { label, value, onChange: handleChange }, renderItems(items))
375
+ );
376
+ };
377
+
378
+ const GOCD_PIPELINES_ANNOTATION = "gocd.org/pipelines";
379
+ const isGoCdAvailable = (entity) => {
380
+ var _a;
381
+ return Boolean((_a = entity.metadata.annotations) == null ? void 0 : _a[GOCD_PIPELINES_ANNOTATION]);
382
+ };
383
+ const GoCdBuildsComponent = () => {
384
+ var _a, _b;
385
+ const { entity } = useEntity();
386
+ const config = useApi(configApiRef);
387
+ const rawPipelines = (_b = (_a = entity.metadata.annotations) == null ? void 0 : _a[GOCD_PIPELINES_ANNOTATION]) == null ? void 0 : _b.split(",").map((p) => p.trim());
388
+ const gocdApi = useApi(gocdApiRef);
389
+ const [selectedPipeline, setSelectedPipeline] = useState(
390
+ rawPipelines ? rawPipelines[0] : ""
391
+ );
392
+ const {
393
+ value: pipelineHistory,
394
+ loading,
395
+ error
396
+ } = useAsync(async () => {
397
+ return await gocdApi.getPipelineHistory(selectedPipeline);
398
+ }, [selectedPipeline]);
399
+ const onSelectedPipelineChanged = (pipeline) => {
400
+ setSelectedPipeline(pipeline);
401
+ };
402
+ const getSelectionItems = (pipelines) => {
403
+ return pipelines.map((p) => ({ label: p, value: p }));
404
+ };
405
+ function isError(apiResult) {
406
+ return (apiResult == null ? void 0 : apiResult.message) !== void 0;
407
+ }
408
+ if (!rawPipelines) {
409
+ return /* @__PURE__ */ React.createElement(MissingAnnotationEmptyState, { annotation: GOCD_PIPELINES_ANNOTATION });
410
+ }
411
+ if (isError(pipelineHistory)) {
412
+ return /* @__PURE__ */ React.createElement(
413
+ EmptyState,
414
+ {
415
+ title: "GoCD pipelines",
416
+ description: `Could not fetch pipelines defined for entity ${entity.metadata.name}. Error: ${pipelineHistory.message}`,
417
+ missing: "content"
418
+ }
419
+ );
420
+ }
421
+ if (!loading && !pipelineHistory) {
422
+ return /* @__PURE__ */ React.createElement(
423
+ EmptyState,
424
+ {
425
+ title: "GoCD pipelines",
426
+ description: `We could not find pipelines defined for entity ${entity.metadata.name}.`,
427
+ missing: "data"
428
+ }
429
+ );
430
+ }
431
+ return /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(Content, { noPadding: true }, /* @__PURE__ */ React.createElement(ContentHeader, { title: entity.metadata.name }, /* @__PURE__ */ React.createElement(
432
+ SelectComponent,
433
+ {
434
+ value: selectedPipeline,
435
+ onChange: (pipeline) => onSelectedPipelineChanged(pipeline),
436
+ label: "Pipeline",
437
+ items: getSelectionItems(rawPipelines)
438
+ }
439
+ )), /* @__PURE__ */ React.createElement(
440
+ GoCdBuildsInsights,
441
+ {
442
+ pipelineHistory,
443
+ loading,
444
+ error
445
+ }
446
+ ), /* @__PURE__ */ React.createElement(
447
+ GoCdBuildsTable,
448
+ {
449
+ goCdBaseUrl: config.getString("gocd.baseUrl"),
450
+ pipelineHistory,
451
+ loading,
452
+ error
453
+ }
454
+ )));
455
+ };
456
+
457
+ export { EntityGoCdContent as E, GoCdBuildsComponent as G, GOCD_PIPELINES_ANNOTATION as a, gocdPlugin as g, isGoCdAvailable as i };
458
+ //# sourceMappingURL=index-AZ5ua7UM.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-AZ5ua7UM.esm.js","sources":["../../src/api/gocdApi.client.ts","../../src/plugin.ts","../../src/extensions.ts","../../src/api/gocdApi.model.ts","../../src/components/GoCdBuildsTable/GoCdBuildsTable.tsx","../../src/components/GoCdBuildsInsights/GoCdBuildsInsights.tsx","../../src/components/Select/Select.tsx","../../src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { GoCdApi } from './gocdApi';\nimport { GoCdApiError, PipelineHistory } from './gocdApi.model';\nimport { DiscoveryApi } from '@backstage/core-plugin-api';\nimport { ResponseError } from '@backstage/errors';\n\nconst DEFAULT_PAGE_SIZE = 100;\n\nexport class GoCdClientApi implements GoCdApi {\n constructor(private readonly discoveryApi: DiscoveryApi) {}\n\n async getPipelineHistory(\n pipelineName: string,\n ): Promise<PipelineHistory | GoCdApiError> {\n const baseUrl = await this.discoveryApi.getBaseUrl('proxy');\n const pipelineHistoryResponse = await fetch(\n `${baseUrl}/gocd/pipelines/${pipelineName}/history?page_size=${DEFAULT_PAGE_SIZE}`,\n {\n headers: {\n Accept: 'application/vnd.go.cd+json',\n },\n },\n );\n\n if (!pipelineHistoryResponse.ok) {\n throw await ResponseError.fromResponse(pipelineHistoryResponse);\n }\n\n return await pipelineHistoryResponse.json();\n }\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { GoCdClientApi } from './api/gocdApi.client';\nimport { GoCdApi } from './api/gocdApi';\nimport {\n discoveryApiRef,\n createApiRef,\n createApiFactory,\n createPlugin,\n} from '@backstage/core-plugin-api';\n\nexport const gocdApiRef = createApiRef<GoCdApi>({\n id: 'plugin.gocd.service',\n});\n\n/**\n * Plugin definition.\n *\n * @public\n */\nexport const gocdPlugin = createPlugin({\n id: 'gocd',\n apis: [\n createApiFactory({\n api: gocdApiRef,\n deps: { discoveryApi: discoveryApiRef },\n factory: ({ discoveryApi }) => {\n return new GoCdClientApi(discoveryApi);\n },\n }),\n ],\n});\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { gocdPlugin } from './plugin';\nimport { createComponentExtension } from '@backstage/core-plugin-api';\n\n/**\n * GoCD builds table component.\n *\n * @public\n */\nexport const EntityGoCdContent = gocdPlugin.provide(\n createComponentExtension({\n name: 'EntityGoCdContent',\n component: {\n lazy: () =>\n import('./components/GoCdBuildsComponent').then(\n m => m.GoCdBuildsComponent,\n ),\n },\n }),\n);\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport interface GoCdApiError {\n message: string;\n}\n\nexport interface PipelineHistory {\n _links: Links;\n pipelines: Pipeline[];\n}\n\nexport interface Links {\n next: Next;\n}\n\nexport interface Next {\n href: string;\n}\n\nexport interface Pipeline {\n name: string;\n counter: number;\n label: string;\n natural_order?: number;\n can_run?: boolean;\n preparing_to_schedule?: boolean;\n comment: string | null;\n scheduled_date?: number;\n build_cause?: BuildCause;\n stages: Stage[];\n}\n\nexport interface BuildCause {\n trigger_message: string;\n trigger_forced: boolean;\n approver: string;\n material_revisions: MaterialRevision[];\n}\n\nexport interface MaterialRevision {\n changed: boolean;\n material: Material;\n modifications: Modification[];\n}\n\nexport interface Material {\n name: string;\n fingerprint: string;\n type: string;\n description: string;\n}\n\nexport interface Modification {\n revision: string;\n modified_time: number;\n user_name: string;\n comment: string | null;\n email_address: string | null;\n}\n\nexport interface Stage {\n result?: string;\n status: string;\n rerun_of_counter?: number | null;\n name: string;\n counter: string;\n scheduled: boolean;\n approval_type?: string | null;\n approved_by?: string | null;\n operate_permission?: boolean;\n can_run?: boolean;\n jobs: Job[];\n}\n\nexport interface Job {\n name: string;\n scheduled_date?: number;\n state: string;\n result: string;\n}\n\nexport enum GoCdBuildResultStatus {\n running,\n successful,\n warning,\n aborted,\n error,\n pending,\n}\n\nexport const toBuildResultStatus = (status: string): GoCdBuildResultStatus => {\n switch (status.toLocaleLowerCase('en-US')) {\n case 'passed':\n return GoCdBuildResultStatus.successful;\n case 'failed':\n return GoCdBuildResultStatus.error;\n case 'aborted':\n return GoCdBuildResultStatus.aborted;\n case 'building':\n return GoCdBuildResultStatus.running;\n case 'pending':\n return GoCdBuildResultStatus.pending;\n default:\n return GoCdBuildResultStatus.aborted;\n }\n};\n\nexport interface GoCdBuildStageResult {\n status: GoCdBuildResultStatus;\n text: string;\n}\n\nexport interface GoCdBuildResult {\n id: number;\n source: string;\n stages: GoCdBuildStageResult[];\n buildSlug: string;\n message: string;\n pipeline: string;\n author: string | undefined;\n commitHash: string;\n triggerTime: number | undefined;\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React, { useState } from 'react';\nimport { Entity, getEntitySourceLocation } from '@backstage/catalog-model';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport Alert from '@material-ui/lab/Alert';\nimport Button from '@material-ui/core/Button';\nimport GitHubIcon from '@material-ui/icons/GitHub';\nimport {\n GoCdBuildResult,\n GoCdBuildResultStatus,\n toBuildResultStatus,\n PipelineHistory,\n Pipeline,\n} from '../../api/gocdApi.model';\nimport { DateTime } from 'luxon';\nimport {\n Table,\n TableColumn,\n Link,\n SubvalueCell,\n StatusOK,\n StatusWarning,\n StatusAborted,\n StatusError,\n StatusRunning,\n StatusPending,\n} from '@backstage/core-components';\n\ntype GoCdBuildsProps = {\n goCdBaseUrl: string;\n pipelineHistory: PipelineHistory | undefined;\n loading: boolean;\n error: Error | undefined;\n};\n\nconst renderTrigger = (build: GoCdBuildResult): React.ReactNode => {\n const subvalue = (\n <>\n {build.pipeline}\n <br />\n {build.author}\n </>\n );\n return <SubvalueCell value={build.message} subvalue={subvalue} />;\n};\n\nconst renderStages = (build: GoCdBuildResult): React.ReactNode => {\n return build.stages.map(s => {\n switch (s.status) {\n case GoCdBuildResultStatus.successful: {\n return (\n <>\n <StatusOK>{s.text}</StatusOK>\n <br />\n <br />\n </>\n );\n }\n case GoCdBuildResultStatus.error: {\n return (\n <>\n <StatusError>{s.text}</StatusError>\n <br />\n <br />\n </>\n );\n }\n case GoCdBuildResultStatus.running: {\n return (\n <>\n <StatusRunning>{s.text}</StatusRunning>\n <br />\n <br />\n </>\n );\n }\n case GoCdBuildResultStatus.aborted: {\n return (\n <>\n <StatusAborted>{s.text}</StatusAborted>\n <br />\n <br />\n </>\n );\n }\n case GoCdBuildResultStatus.pending: {\n return (\n <>\n <StatusPending>{s.text}</StatusPending>\n <br />\n <br />\n </>\n );\n }\n default: {\n return (\n <>\n <StatusWarning>{s.text}</StatusWarning>\n <br />\n <br />\n </>\n );\n }\n }\n });\n};\n\nconst renderSource = (build: GoCdBuildResult): React.ReactNode => {\n return (\n <Button\n href={build.source}\n target=\"_blank\"\n rel=\"noreferrer\"\n startIcon={<GitHubIcon />}\n >\n {build.commitHash}\n </Button>\n );\n};\n\nconst renderId = (\n goCdBaseUrl: string,\n build: GoCdBuildResult,\n): React.ReactNode => {\n const goCdBuildUrl = `${goCdBaseUrl}/go/pipelines/value_stream_map/${build.buildSlug}`;\n return (\n <SubvalueCell\n value={\n <Link to={goCdBuildUrl} target=\"_blank\" rel=\"noreferrer\">\n {build.id}\n </Link>\n }\n subvalue={\n build.triggerTime &&\n DateTime.fromMillis(build.triggerTime).toLocaleString(\n DateTime.DATETIME_MED,\n )\n }\n />\n );\n};\n\nconst renderError = (error: Error): JSX.Element => {\n return <Alert severity=\"error\">{error.message}</Alert>;\n};\n\nconst toBuildResults = (\n entity: Entity,\n builds: Pipeline[],\n): GoCdBuildResult[] | undefined => {\n // TODO(julioz): What if not git 'type'?\n const entitySourceLocation =\n getEntitySourceLocation(entity).target.split('/tree')[0];\n return builds.map(build => ({\n id: build.counter,\n source: `${entitySourceLocation}/commit/${build.build_cause?.material_revisions[0]?.modifications[0].revision}`,\n stages: build.stages.map(s => ({\n status: toBuildResultStatus(s.status),\n text: s.name,\n })),\n buildSlug: `${build.name}/${build.counter}`,\n message:\n build.build_cause?.material_revisions[0]?.modifications[0].comment ?? '',\n pipeline: build.name,\n author:\n build.build_cause?.material_revisions[0]?.modifications[0].user_name,\n commitHash: build.label,\n triggerTime: build.scheduled_date,\n }));\n};\n\nexport const GoCdBuildsTable = (props: GoCdBuildsProps): JSX.Element => {\n const { pipelineHistory, loading, error } = props;\n const { entity } = useEntity();\n const [, setSelectedSearchTerm] = useState<string>('');\n\n const columns: TableColumn<GoCdBuildResult>[] = [\n {\n title: 'ID',\n field: 'id',\n highlight: true,\n render: build => renderId(props.goCdBaseUrl, build),\n },\n {\n title: 'Trigger',\n customFilterAndSearch: (query, row: GoCdBuildResult) =>\n `${row.message} ${row.pipeline} ${row.author}`\n .toLocaleUpperCase('en-US')\n .includes(query.toLocaleUpperCase('en-US')),\n field: 'message',\n highlight: true,\n render: build => renderTrigger(build),\n },\n {\n title: 'Stages',\n field: 'status',\n render: build => renderStages(build),\n },\n {\n title: 'Source',\n field: 'source',\n render: build => renderSource(build),\n },\n ];\n\n return (\n <>\n {error && renderError(error)}\n {!error && (\n <Table\n title=\"GoCD builds\"\n isLoading={loading}\n options={{\n search: true,\n searchAutoFocus: true,\n debounceInterval: 800,\n paging: true,\n padding: 'dense',\n pageSizeOptions: [5, 10, 20, 50],\n showFirstLastPageButtons: false,\n pageSize: 20,\n }}\n columns={columns}\n data={\n pipelineHistory\n ? toBuildResults(entity, pipelineHistory.pipelines) || []\n : []\n }\n onSearchChange={(searchTerm: string) =>\n setSelectedSearchTerm(searchTerm)\n }\n />\n )}\n </>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\nimport { groupBy, mean } from 'lodash';\nimport {\n PipelineHistory,\n Job,\n toBuildResultStatus,\n GoCdBuildResultStatus,\n} from '../../api/gocdApi.model';\nimport Box from '@material-ui/core/Box';\nimport Grid from '@material-ui/core/Grid';\nimport Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport Typography from '@material-ui/core/Typography';\nimport { DateTime, Duration } from 'luxon';\n\nexport type GoCdBuildsInsightsProps = {\n pipelineHistory: PipelineHistory | undefined;\n loading: boolean;\n error: Error | undefined;\n};\n\nfunction runFrequency(pipelineHistory: PipelineHistory): string {\n const lastMonth = DateTime.now().minus({ month: 1 });\n const buildLastMonth = pipelineHistory.pipelines\n .map(p => p.scheduled_date)\n .filter((d): d is number => !!d)\n .map(d => DateTime.fromMillis(d))\n .filter(d => d > lastMonth).length;\n return `${buildLastMonth} last month`;\n}\n\nfunction meanTimeBetweenFailures(jobs: Job[]): string {\n const timeBetweenFailures: Duration[] = [];\n for (let index = 1; index < jobs.length; index++) {\n const job = jobs[index];\n if (!job.result) {\n continue;\n }\n if (toBuildResultStatus(job.result) === GoCdBuildResultStatus.error) {\n let previousFailedJob: Job | null = null;\n for (let j = index - 1; j >= 0; j--) {\n const candidateJob = jobs[j];\n if (!candidateJob.result) {\n continue;\n }\n if (\n toBuildResultStatus(candidateJob.result) ===\n GoCdBuildResultStatus.error\n ) {\n previousFailedJob = candidateJob;\n break;\n }\n }\n if (\n !previousFailedJob ||\n !job.scheduled_date ||\n !previousFailedJob.scheduled_date\n ) {\n continue;\n }\n const failedJobDate = DateTime.fromMillis(job.scheduled_date);\n const previousFailedJobDate = DateTime.fromMillis(\n previousFailedJob.scheduled_date,\n );\n const timeBetweenFailure = failedJobDate.diff(previousFailedJobDate);\n timeBetweenFailures.push(timeBetweenFailure);\n }\n }\n return formatMean(timeBetweenFailures);\n}\n\n/**\n * Imagine a sequence like:\n * S - S - S - F - S - F - F - S - S\n *\n * We iterate until finding a failure, and then iterate forward\n * until we find the first immediate success to then\n * calculate the time difference between the scheduling of the jobs.\n */\nfunction meanTimeToRecovery(jobs: Job[]): string {\n const timeToRecoverIntervals: Duration[] = [];\n for (let index = 0; index < jobs.length; index++) {\n const job = jobs[index];\n if (!job.result) {\n continue;\n }\n if (toBuildResultStatus(job.result) === GoCdBuildResultStatus.error) {\n let nextSuccessfulJob: Job | null = null;\n for (let j = index + 1; j < jobs.length; j++) {\n const candidateJob = jobs[j];\n if (!candidateJob.result) {\n continue;\n }\n if (\n toBuildResultStatus(candidateJob.result) ===\n GoCdBuildResultStatus.successful\n ) {\n nextSuccessfulJob = candidateJob;\n break;\n }\n }\n if (\n !nextSuccessfulJob ||\n !job.scheduled_date ||\n !nextSuccessfulJob.scheduled_date\n ) {\n continue;\n }\n const failedJobDate = DateTime.fromMillis(job.scheduled_date);\n const successfulJobDate = DateTime.fromMillis(\n nextSuccessfulJob.scheduled_date,\n );\n const timeToRecovery = successfulJobDate.diff(failedJobDate);\n timeToRecoverIntervals.push(timeToRecovery);\n }\n }\n\n return formatMean(timeToRecoverIntervals);\n}\n\nfunction formatMean(durations: Duration[]): string {\n if (durations.length === 0) {\n return 'N/A';\n }\n\n const mttr: Duration = Duration.fromMillis(\n mean(durations.map(i => i.milliseconds)),\n );\n return mttr.toFormat(\"d'd' h'h' m'm' s's'\");\n}\n\nfunction failureRate(jobs: Job[]): {\n title: string;\n subtitle: string;\n} {\n const resultGroups = new Map(\n Object.entries(groupBy(jobs, 'result')).map(([key, value]) => [\n toBuildResultStatus(key),\n value.flat(),\n ]),\n );\n const failedJobs = resultGroups.get(GoCdBuildResultStatus.error);\n if (!failedJobs) {\n return {\n title: '0',\n subtitle: '(no failed jobs found)',\n };\n }\n\n resultGroups.delete(GoCdBuildResultStatus.error);\n const nonFailedJobs = Array.from(resultGroups.values()).flat();\n\n const totalJobs = failedJobs.length + nonFailedJobs.length;\n const percentage = (failedJobs.length / totalJobs) * 100;\n const decimalPercentage = (Math.round(percentage * 100) / 100).toFixed(2);\n return {\n title: `${decimalPercentage}%`,\n subtitle: `(${failedJobs.length} out of ${totalJobs} jobs)`,\n };\n}\n\nexport const GoCdBuildsInsights = (\n props: GoCdBuildsInsightsProps,\n): JSX.Element => {\n const { pipelineHistory, loading, error } = props;\n\n if (loading || error || !pipelineHistory) {\n return <></>;\n }\n\n // We reverse the array to calculate insights to make sure jobs are ordered\n // by their schedule date.\n const stages = pipelineHistory.pipelines\n .slice()\n .reverse()\n .map(p => p.stages)\n .flat();\n const jobs = stages.map(s => s.jobs).flat();\n\n const failureRateObj: { title: string; subtitle: string } = failureRate(jobs);\n\n return (\n <Box data-testid=\"GoCdBuildsInsightsBox\" sx={{ mb: 1 }}>\n <Grid container spacing={1}>\n <Grid item xs={6} sm={3}>\n <Tooltip title=\"What is your deployment frequency?\">\n <Card>\n <CardContent>\n <Typography variant=\"body2\">Run Frequency</Typography>\n <Typography variant=\"h4\">\n {runFrequency(pipelineHistory)}\n </Typography>\n </CardContent>\n </Card>\n </Tooltip>\n </Grid>\n <Grid item xs={6} sm={3}>\n <Tooltip title=\"How long does it take to fix a failure?\">\n <Card>\n <CardContent>\n <Typography variant=\"body2\">Mean Time to Recovery</Typography>\n <Typography variant=\"h4\">{meanTimeToRecovery(jobs)}</Typography>\n </CardContent>\n </Card>\n </Tooltip>\n </Grid>\n <Grid item xs={6} sm={3}>\n <Tooltip title=\"How often do changes fail?\">\n <Card>\n <CardContent>\n <Typography variant=\"body2\">\n Mean Time Between Failures\n </Typography>\n <Typography variant=\"h4\">\n {meanTimeBetweenFailures(jobs)}\n </Typography>\n </CardContent>\n </Card>\n </Tooltip>\n </Grid>\n <Grid item xs={6} sm={3}>\n <Tooltip title=\"What percentage of changes result in a failure?\">\n <Card>\n <CardContent>\n <Typography variant=\"body2\">Failure Rate</Typography>\n <Typography variant=\"h4\">{failureRateObj.title}</Typography>\n <Typography variant=\"body2\">\n {failureRateObj.subtitle}\n </Typography>\n </CardContent>\n </Card>\n </Tooltip>\n </Grid>\n </Grid>\n </Box>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport FormControl from '@material-ui/core/FormControl';\nimport InputLabel from '@material-ui/core/InputLabel';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport Select from '@material-ui/core/Select';\nimport { createStyles, makeStyles, Theme } from '@material-ui/core/styles';\nimport React from 'react';\n\nexport type Item = {\n label: string;\n value: string | number;\n};\n\nconst useStyles = makeStyles((theme: Theme) =>\n createStyles({\n formControl: {\n margin: theme.spacing(1),\n minWidth: 120,\n },\n selectEmpty: {\n marginTop: theme.spacing(2),\n },\n }),\n);\n\ntype SelectComponentProps = {\n value: string;\n items: Item[];\n label: string;\n onChange: (value: string) => void;\n};\n\nconst renderItems = (items: Item[]) => {\n return items.map(item => {\n return (\n <MenuItem value={item.value} key={item.label}>\n {item.label}\n </MenuItem>\n );\n });\n};\n\nexport const SelectComponent = ({\n value,\n items,\n label,\n onChange,\n}: SelectComponentProps): JSX.Element => {\n const classes = useStyles();\n\n const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {\n const val = event.target.value as string;\n onChange(val);\n };\n\n return (\n <FormControl\n variant=\"outlined\"\n className={classes.formControl}\n disabled={items.length === 0}\n >\n <InputLabel>{label}</InputLabel>\n <Select label={label} value={value} onChange={handleChange}>\n {renderItems(items)}\n </Select>\n </FormControl>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React, { useState } from 'react';\nimport { Entity } from '@backstage/catalog-model';\nimport {\n useEntity,\n MissingAnnotationEmptyState,\n} from '@backstage/plugin-catalog-react';\nimport {\n Content,\n ContentHeader,\n EmptyState,\n Page,\n} from '@backstage/core-components';\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\nimport useAsync from 'react-use/esm/useAsync';\nimport { gocdApiRef } from '../../plugin';\nimport { GoCdBuildsTable } from '../GoCdBuildsTable/GoCdBuildsTable';\nimport { GoCdBuildsInsights } from '../GoCdBuildsInsights/GoCdBuildsInsights';\nimport { GoCdApiError, PipelineHistory } from '../../api/gocdApi.model';\nimport { Item, Select } from '../Select';\n\n/**\n * Constant storing GoCD pipelines annotation.\n *\n * @public\n */\nexport const GOCD_PIPELINES_ANNOTATION = 'gocd.org/pipelines';\n\n/**\n * Returns true if GoCD annotation is present in the given entity.\n *\n * @public\n */\nexport const isGoCdAvailable = (entity: Entity): boolean =>\n Boolean(entity.metadata.annotations?.[GOCD_PIPELINES_ANNOTATION]);\n\nexport const GoCdBuildsComponent = (): JSX.Element => {\n const { entity } = useEntity();\n const config = useApi(configApiRef);\n const rawPipelines: string[] | undefined = (\n entity.metadata.annotations?.[GOCD_PIPELINES_ANNOTATION] as\n | string\n | undefined\n )\n ?.split(',')\n .map(p => p.trim());\n const gocdApi = useApi(gocdApiRef);\n\n const [selectedPipeline, setSelectedPipeline] = useState<string>(\n rawPipelines ? rawPipelines[0] : '',\n );\n\n const {\n value: pipelineHistory,\n loading,\n error,\n } = useAsync(async (): Promise<PipelineHistory | GoCdApiError> => {\n return await gocdApi.getPipelineHistory(selectedPipeline);\n }, [selectedPipeline]);\n\n const onSelectedPipelineChanged = (pipeline: string) => {\n setSelectedPipeline(pipeline);\n };\n\n const getSelectionItems = (pipelines: string[]): Item[] => {\n return pipelines.map(p => ({ label: p, value: p }));\n };\n\n function isError(\n apiResult: PipelineHistory | GoCdApiError | undefined,\n ): apiResult is GoCdApiError {\n return (apiResult as GoCdApiError)?.message !== undefined;\n }\n\n if (!rawPipelines) {\n return (\n <MissingAnnotationEmptyState annotation={GOCD_PIPELINES_ANNOTATION} />\n );\n }\n\n if (isError(pipelineHistory)) {\n return (\n <EmptyState\n title=\"GoCD pipelines\"\n description={`Could not fetch pipelines defined for entity ${entity.metadata.name}. Error: ${pipelineHistory.message}`}\n missing=\"content\"\n />\n );\n }\n\n if (!loading && !pipelineHistory) {\n return (\n <EmptyState\n title=\"GoCD pipelines\"\n description={`We could not find pipelines defined for entity ${entity.metadata.name}.`}\n missing=\"data\"\n />\n );\n }\n\n return (\n <Page themeId=\"tool\">\n <Content noPadding>\n <ContentHeader title={entity.metadata.name}>\n <Select\n value={selectedPipeline}\n onChange={pipeline => onSelectedPipelineChanged(pipeline)}\n label=\"Pipeline\"\n items={getSelectionItems(rawPipelines)}\n />\n </ContentHeader>\n <GoCdBuildsInsights\n pipelineHistory={pipelineHistory}\n loading={loading}\n error={error}\n />\n <GoCdBuildsTable\n goCdBaseUrl={config.getString('gocd.baseUrl')}\n pipelineHistory={pipelineHistory}\n loading={loading}\n error={error}\n />\n </Content>\n </Page>\n );\n};\n"],"names":["GoCdBuildResultStatus","Select"],"mappings":"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"}