@backstage/plugin-scaffolder 0.12.0 → 0.12.2-next.0
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 +45 -0
- package/dist/esm/{Router-5ca674eb.esm.js → Router-5df57206.esm.js} +47 -432
- package/dist/esm/Router-5df57206.esm.js.map +1 -0
- package/dist/esm/{index-8b5d21c1.esm.js → index-71578268.esm.js} +492 -30
- package/dist/esm/index-71578268.esm.js.map +1 -0
- package/dist/index.d.ts +43 -8
- package/dist/index.esm.js +16 -1
- package/dist/index.esm.js.map +1 -1
- package/package.json +12 -10
- package/dist/esm/Router-5ca674eb.esm.js.map +0 -1
- package/dist/esm/index-8b5d21c1.esm.js.map +0 -1
|
@@ -1,29 +1,44 @@
|
|
|
1
|
-
import { createApiRef, useApi, attachComponentData, createExternalRouteRef, createRouteRef, createPlugin, createApiFactory, discoveryApiRef, fetchApiRef, createRoutableExtension, useRouteRef, alertApiRef } from '@backstage/core-plugin-api';
|
|
1
|
+
import { createApiRef, useApi, attachComponentData, createExternalRouteRef, createRouteRef, createPlugin, createApiFactory, discoveryApiRef, fetchApiRef, createRoutableExtension, useRouteRef, alertApiRef, useApp } from '@backstage/core-plugin-api';
|
|
2
2
|
import { ResponseError } from '@backstage/errors';
|
|
3
3
|
import qs from 'qs';
|
|
4
4
|
import ObservableImpl from 'zen-observable';
|
|
5
|
-
import { catalogApiRef, formatEntityRefTitle, useOwnedEntities, useStarredEntity, getEntityRelations, getEntitySourceLocation, EntityRefLinks, useEntityListProvider, useEntityTypeFilter } from '@backstage/plugin-catalog-react';
|
|
6
|
-
import { TextField, FormControl as FormControl$1, withStyles, makeStyles, IconButton, Tooltip, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, Link, FormControlLabel, Checkbox } from '@material-ui/core';
|
|
5
|
+
import { catalogApiRef, formatEntityRefTitle, useOwnedEntities, useStarredEntity, getEntityRelations, getEntitySourceLocation, EntityRefLinks, useEntityListProvider, useEntityTypeFilter, entityRouteRef } from '@backstage/plugin-catalog-react';
|
|
6
|
+
import { TextField, FormControl as FormControl$1, withStyles, makeStyles, IconButton, Tooltip, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, Link, FormControlLabel, Checkbox, Grid, StepButton, Paper, Button as Button$1, CircularProgress } from '@material-ui/core';
|
|
7
7
|
import FormControl from '@material-ui/core/FormControl';
|
|
8
8
|
import Autocomplete from '@material-ui/lab/Autocomplete';
|
|
9
|
-
import React, { useCallback, useEffect, useState, useMemo } from 'react';
|
|
9
|
+
import React, { useCallback, useEffect, useState, createContext, useContext, useMemo, memo } from 'react';
|
|
10
10
|
import useAsync from 'react-use/lib/useAsync';
|
|
11
|
-
import { KubernetesValidatorFunctions, makeValidator, RELATION_OWNED_BY, stringifyEntityRef } from '@backstage/catalog-model';
|
|
11
|
+
import { KubernetesValidatorFunctions, makeValidator, RELATION_OWNED_BY, stringifyEntityRef, parseEntityName } from '@backstage/catalog-model';
|
|
12
12
|
import useEffectOnce from 'react-use/lib/useEffectOnce';
|
|
13
13
|
import { Autocomplete as Autocomplete$1 } from '@material-ui/lab';
|
|
14
|
-
import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
|
|
14
|
+
import { scmIntegrationsApiRef, scmAuthApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
|
|
15
15
|
import FormHelperText from '@material-ui/core/FormHelperText';
|
|
16
16
|
import Input from '@material-ui/core/Input';
|
|
17
17
|
import InputLabel from '@material-ui/core/InputLabel';
|
|
18
|
-
import { Select, Progress, ItemCardHeader, Button, ContentHeader, WarningPanel, Link as Link$1, Content, ItemCardGrid } from '@backstage/core-components';
|
|
18
|
+
import { Select, Progress, ItemCardHeader, Button, ContentHeader, WarningPanel, Link as Link$1, Content, ItemCardGrid, Page, Header, Lifecycle, ErrorPage, LogViewer } from '@backstage/core-components';
|
|
19
|
+
import useDebounce from 'react-use/lib/useDebounce';
|
|
19
20
|
import Star from '@material-ui/icons/Star';
|
|
20
21
|
import StarBorder from '@material-ui/icons/StarBorder';
|
|
21
22
|
import WarningIcon from '@material-ui/icons/Warning';
|
|
22
|
-
import { generatePath } from 'react-router';
|
|
23
|
+
import { generatePath, useNavigate, useParams } from 'react-router';
|
|
23
24
|
import capitalize from 'lodash/capitalize';
|
|
24
25
|
import CheckBoxIcon from '@material-ui/icons/CheckBox';
|
|
25
26
|
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
|
|
26
27
|
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
|
28
|
+
import Grid$1 from '@material-ui/core/Grid';
|
|
29
|
+
import Step from '@material-ui/core/Step';
|
|
30
|
+
import StepLabel from '@material-ui/core/StepLabel';
|
|
31
|
+
import Stepper from '@material-ui/core/Stepper';
|
|
32
|
+
import { makeStyles as makeStyles$1, createStyles } from '@material-ui/core/styles';
|
|
33
|
+
import Typography$1 from '@material-ui/core/Typography';
|
|
34
|
+
import Cancel from '@material-ui/icons/Cancel';
|
|
35
|
+
import Check from '@material-ui/icons/Check';
|
|
36
|
+
import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
|
|
37
|
+
import classNames from 'classnames';
|
|
38
|
+
import { DateTime, Interval } from 'luxon';
|
|
39
|
+
import useInterval from 'react-use/lib/useInterval';
|
|
40
|
+
import { useImmerReducer } from 'use-immer';
|
|
41
|
+
import LanguageIcon from '@material-ui/icons/Language';
|
|
27
42
|
|
|
28
43
|
const scaffolderApiRef = createApiRef({
|
|
29
44
|
id: "plugin.scaffolder.service"
|
|
@@ -493,11 +508,15 @@ const RepoUrlPickerHost = (props) => {
|
|
|
493
508
|
});
|
|
494
509
|
});
|
|
495
510
|
useEffect(() => {
|
|
496
|
-
if (
|
|
497
|
-
|
|
511
|
+
if (!host) {
|
|
512
|
+
if (hosts == null ? void 0 : hosts.length) {
|
|
513
|
+
onChange(hosts[0]);
|
|
514
|
+
} else if (integrations == null ? void 0 : integrations.length) {
|
|
515
|
+
onChange(integrations[0].host);
|
|
516
|
+
}
|
|
498
517
|
}
|
|
499
|
-
}, [hosts, host, onChange]);
|
|
500
|
-
const hostsOptions = integrations ? integrations.filter((i) => hosts == null ? void 0 : hosts.includes(i.host)).map((i) => ({ label: i.title, value: i.host })) : [{ label: "Loading...", value: "loading" }];
|
|
518
|
+
}, [hosts, host, onChange, integrations]);
|
|
519
|
+
const hostsOptions = integrations ? integrations.filter((i) => (hosts == null ? void 0 : hosts.length) ? hosts == null ? void 0 : hosts.includes(i.host) : true).map((i) => ({ label: i.title, value: i.host })) : [{ label: "Loading...", value: "loading" }];
|
|
501
520
|
if (loading) {
|
|
502
521
|
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
503
522
|
}
|
|
@@ -539,32 +558,53 @@ function serializeRepoPickerUrl(data) {
|
|
|
539
558
|
return `${data.host}?${params.toString()}`;
|
|
540
559
|
}
|
|
541
560
|
function parseRepoPickerUrl(url) {
|
|
542
|
-
let host =
|
|
543
|
-
let owner =
|
|
544
|
-
let repoName =
|
|
545
|
-
let organization =
|
|
546
|
-
let workspace =
|
|
547
|
-
let project =
|
|
561
|
+
let host = "";
|
|
562
|
+
let owner = "";
|
|
563
|
+
let repoName = "";
|
|
564
|
+
let organization = "";
|
|
565
|
+
let workspace = "";
|
|
566
|
+
let project = "";
|
|
548
567
|
try {
|
|
549
568
|
if (url) {
|
|
550
569
|
const parsed = new URL(`https://${url}`);
|
|
551
570
|
host = parsed.host;
|
|
552
|
-
owner = parsed.searchParams.get("owner") ||
|
|
553
|
-
repoName = parsed.searchParams.get("repo") ||
|
|
554
|
-
organization = parsed.searchParams.get("organization") ||
|
|
555
|
-
workspace = parsed.searchParams.get("workspace") ||
|
|
556
|
-
project = parsed.searchParams.get("project") ||
|
|
571
|
+
owner = parsed.searchParams.get("owner") || "";
|
|
572
|
+
repoName = parsed.searchParams.get("repo") || "";
|
|
573
|
+
organization = parsed.searchParams.get("organization") || "";
|
|
574
|
+
workspace = parsed.searchParams.get("workspace") || "";
|
|
575
|
+
project = parsed.searchParams.get("project") || "";
|
|
557
576
|
}
|
|
558
577
|
} catch {
|
|
559
578
|
}
|
|
560
579
|
return { host, owner, repoName, organization, workspace, project };
|
|
561
580
|
}
|
|
562
581
|
|
|
582
|
+
const SecretsContext = createContext(void 0);
|
|
583
|
+
const SecretsContextProvider = ({ children }) => {
|
|
584
|
+
const [secrets, setSecrets] = useState({});
|
|
585
|
+
return /* @__PURE__ */ React.createElement(SecretsContext.Provider, {
|
|
586
|
+
value: { secrets, setSecrets }
|
|
587
|
+
}, children);
|
|
588
|
+
};
|
|
589
|
+
const useTemplateSecrets = () => {
|
|
590
|
+
const value = useContext(SecretsContext);
|
|
591
|
+
if (!value) {
|
|
592
|
+
throw new Error("useTemplateSecrets must be used within a SecretsContextProvider");
|
|
593
|
+
}
|
|
594
|
+
const { setSecrets } = value;
|
|
595
|
+
const setSecret = useCallback((input) => {
|
|
596
|
+
setSecrets((currentSecrets) => ({ ...currentSecrets, ...input }));
|
|
597
|
+
}, [setSecrets]);
|
|
598
|
+
return { setSecret };
|
|
599
|
+
};
|
|
600
|
+
|
|
563
601
|
const RepoUrlPicker = (props) => {
|
|
564
602
|
var _a, _b;
|
|
565
603
|
const { uiSchema, onChange, rawErrors, formData } = props;
|
|
566
604
|
const [state, setState] = useState(parseRepoPickerUrl(formData));
|
|
567
605
|
const integrationApi = useApi(scmIntegrationsApiRef);
|
|
606
|
+
const scmAuthApi = useApi(scmAuthApiRef);
|
|
607
|
+
const { setSecret } = useTemplateSecrets();
|
|
568
608
|
const allowedHosts = useMemo(() => {
|
|
569
609
|
var _a2, _b2;
|
|
570
610
|
return (_b2 = (_a2 = uiSchema == null ? void 0 : uiSchema["ui:options"]) == null ? void 0 : _a2.allowedHosts) != null ? _b2 : [];
|
|
@@ -584,6 +624,26 @@ const RepoUrlPicker = (props) => {
|
|
|
584
624
|
const updateLocalState = useCallback((newState) => {
|
|
585
625
|
setState((prevState) => ({ ...prevState, ...newState }));
|
|
586
626
|
}, [setState]);
|
|
627
|
+
useDebounce(async () => {
|
|
628
|
+
var _a2;
|
|
629
|
+
const { requestUserCredentials } = (_a2 = uiSchema == null ? void 0 : uiSchema["ui:options"]) != null ? _a2 : {};
|
|
630
|
+
if (!requestUserCredentials || !(state.host && state.owner && state.repoName)) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const [host, owner, repoName] = [
|
|
634
|
+
state.host,
|
|
635
|
+
state.owner,
|
|
636
|
+
state.repoName
|
|
637
|
+
].map(encodeURIComponent);
|
|
638
|
+
const { token } = await scmAuthApi.getCredentials({
|
|
639
|
+
url: `https://${host}/${owner}/${repoName}`,
|
|
640
|
+
additionalScope: {
|
|
641
|
+
repoWrite: true,
|
|
642
|
+
customScopes: requestUserCredentials.additionalScopes
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
setSecret({ [requestUserCredentials.secretsKey]: token });
|
|
646
|
+
}, 500, [state, uiSchema]);
|
|
587
647
|
const hostType = (_b = state.host && ((_a = integrationApi.byHost(state.host)) == null ? void 0 : _a.type)) != null ? _b : null;
|
|
588
648
|
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(RepoUrlPickerHost, {
|
|
589
649
|
host: state.host,
|
|
@@ -747,7 +807,7 @@ const OwnerPickerFieldExtension = scaffolderPlugin.provide(createScaffolderField
|
|
|
747
807
|
}));
|
|
748
808
|
const ScaffolderPage = scaffolderPlugin.provide(createRoutableExtension({
|
|
749
809
|
name: "ScaffolderPage",
|
|
750
|
-
component: () => import('./Router-
|
|
810
|
+
component: () => import('./Router-5df57206.esm.js').then((m) => m.Router),
|
|
751
811
|
mountPoint: rootRouteRef
|
|
752
812
|
}));
|
|
753
813
|
const OwnedEntityPickerFieldExtension = scaffolderPlugin.provide(createScaffolderFieldExtension({
|
|
@@ -769,7 +829,7 @@ const WhiteBorderStar = withStyles({
|
|
|
769
829
|
color: "#ffffff"
|
|
770
830
|
}
|
|
771
831
|
})(StarBorder);
|
|
772
|
-
const useStyles$
|
|
832
|
+
const useStyles$3 = makeStyles((theme) => ({
|
|
773
833
|
starButton: {
|
|
774
834
|
position: "absolute",
|
|
775
835
|
top: theme.spacing(0.5),
|
|
@@ -780,7 +840,7 @@ const useStyles$1 = makeStyles((theme) => ({
|
|
|
780
840
|
const favouriteTemplateTooltip = (isStarred) => isStarred ? "Remove from favorites" : "Add to favorites";
|
|
781
841
|
const favouriteTemplateIcon = (isStarred) => isStarred ? /* @__PURE__ */ React.createElement(YellowStar, null) : /* @__PURE__ */ React.createElement(WhiteBorderStar, null);
|
|
782
842
|
const FavouriteTemplate = (props) => {
|
|
783
|
-
const classes = useStyles$
|
|
843
|
+
const classes = useStyles$3();
|
|
784
844
|
const { toggleStarredEntity, isStarredEntity } = useStarredEntity(props.entity);
|
|
785
845
|
return /* @__PURE__ */ React.createElement(IconButton, {
|
|
786
846
|
color: "inherit",
|
|
@@ -792,7 +852,7 @@ const FavouriteTemplate = (props) => {
|
|
|
792
852
|
}, favouriteTemplateIcon(isStarredEntity)));
|
|
793
853
|
};
|
|
794
854
|
|
|
795
|
-
const useStyles = makeStyles((theme) => ({
|
|
855
|
+
const useStyles$2 = makeStyles((theme) => ({
|
|
796
856
|
cardHeader: {
|
|
797
857
|
position: "relative"
|
|
798
858
|
},
|
|
@@ -864,7 +924,7 @@ const TemplateCard = ({ template, deprecated }) => {
|
|
|
864
924
|
const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
|
|
865
925
|
const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) ? templateProps.type : "other";
|
|
866
926
|
const theme = backstageTheme.getPageTheme({ themeId });
|
|
867
|
-
const classes = useStyles({ backgroundImage: theme.backgroundImage });
|
|
927
|
+
const classes = useStyles$2({ backgroundImage: theme.backgroundImage });
|
|
868
928
|
const href = generatePath(`${rootLink()}/templates/:templateName`, {
|
|
869
929
|
templateName: templateProps.name
|
|
870
930
|
});
|
|
@@ -989,5 +1049,407 @@ const TemplateTypePicker = () => {
|
|
|
989
1049
|
}));
|
|
990
1050
|
};
|
|
991
1051
|
|
|
992
|
-
|
|
993
|
-
|
|
1052
|
+
function reducer(draft, action) {
|
|
1053
|
+
var _a, _b, _c;
|
|
1054
|
+
switch (action.type) {
|
|
1055
|
+
case "INIT": {
|
|
1056
|
+
draft.steps = action.data.spec.steps.reduce((current, next) => {
|
|
1057
|
+
current[next.id] = { status: "open", id: next.id };
|
|
1058
|
+
return current;
|
|
1059
|
+
}, {});
|
|
1060
|
+
draft.stepLogs = action.data.spec.steps.reduce((current, next) => {
|
|
1061
|
+
current[next.id] = [];
|
|
1062
|
+
return current;
|
|
1063
|
+
}, {});
|
|
1064
|
+
draft.loading = false;
|
|
1065
|
+
draft.error = void 0;
|
|
1066
|
+
draft.completed = false;
|
|
1067
|
+
draft.task = action.data;
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
case "LOGS": {
|
|
1071
|
+
const entries = action.data;
|
|
1072
|
+
for (const entry of entries) {
|
|
1073
|
+
const logLine = `${entry.createdAt} ${entry.body.message}`;
|
|
1074
|
+
if (!entry.body.stepId || !((_a = draft.steps) == null ? void 0 : _a[entry.body.stepId])) {
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
const currentStepLog = (_b = draft.stepLogs) == null ? void 0 : _b[entry.body.stepId];
|
|
1078
|
+
const currentStep = (_c = draft.steps) == null ? void 0 : _c[entry.body.stepId];
|
|
1079
|
+
if (entry.body.status && entry.body.status !== currentStep.status) {
|
|
1080
|
+
currentStep.status = entry.body.status;
|
|
1081
|
+
if (currentStep.status === "processing") {
|
|
1082
|
+
currentStep.startedAt = entry.createdAt;
|
|
1083
|
+
}
|
|
1084
|
+
if (["cancelled", "failed", "completed"].includes(currentStep.status)) {
|
|
1085
|
+
currentStep.endedAt = entry.createdAt;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
currentStepLog == null ? void 0 : currentStepLog.push(logLine);
|
|
1089
|
+
}
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
case "COMPLETED": {
|
|
1093
|
+
draft.completed = true;
|
|
1094
|
+
draft.output = action.data.body.output;
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
case "ERROR": {
|
|
1098
|
+
draft.error = action.data;
|
|
1099
|
+
draft.loading = false;
|
|
1100
|
+
draft.completed = true;
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
default:
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
const useTaskEventStream = (taskId) => {
|
|
1108
|
+
const scaffolderApi = useApi(scaffolderApiRef);
|
|
1109
|
+
const [state, dispatch] = useImmerReducer(reducer, {
|
|
1110
|
+
loading: true,
|
|
1111
|
+
completed: false,
|
|
1112
|
+
stepLogs: {},
|
|
1113
|
+
steps: {}
|
|
1114
|
+
});
|
|
1115
|
+
useEffect(() => {
|
|
1116
|
+
let didCancel = false;
|
|
1117
|
+
let subscription;
|
|
1118
|
+
let logPusher;
|
|
1119
|
+
scaffolderApi.getTask(taskId).then((task) => {
|
|
1120
|
+
if (didCancel) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
dispatch({ type: "INIT", data: task });
|
|
1124
|
+
const observable = scaffolderApi.streamLogs({ taskId });
|
|
1125
|
+
const collectedLogEvents = new Array();
|
|
1126
|
+
function emitLogs() {
|
|
1127
|
+
if (collectedLogEvents.length) {
|
|
1128
|
+
const logs = collectedLogEvents.splice(0, collectedLogEvents.length);
|
|
1129
|
+
dispatch({ type: "LOGS", data: logs });
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
logPusher = setInterval(emitLogs, 500);
|
|
1133
|
+
subscription = observable.subscribe({
|
|
1134
|
+
next: (event) => {
|
|
1135
|
+
switch (event.type) {
|
|
1136
|
+
case "log":
|
|
1137
|
+
return collectedLogEvents.push(event);
|
|
1138
|
+
case "completion":
|
|
1139
|
+
emitLogs();
|
|
1140
|
+
dispatch({ type: "COMPLETED", data: event });
|
|
1141
|
+
return void 0;
|
|
1142
|
+
default:
|
|
1143
|
+
throw new Error(`Unhandled event type ${event.type} in observer`);
|
|
1144
|
+
}
|
|
1145
|
+
},
|
|
1146
|
+
error: (error) => {
|
|
1147
|
+
emitLogs();
|
|
1148
|
+
dispatch({ type: "ERROR", data: error });
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
}, (error) => {
|
|
1152
|
+
if (!didCancel) {
|
|
1153
|
+
dispatch({ type: "ERROR", data: error });
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
return () => {
|
|
1157
|
+
didCancel = true;
|
|
1158
|
+
if (subscription) {
|
|
1159
|
+
subscription.unsubscribe();
|
|
1160
|
+
}
|
|
1161
|
+
if (logPusher) {
|
|
1162
|
+
clearInterval(logPusher);
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
}, [scaffolderApi, dispatch, taskId]);
|
|
1166
|
+
return state;
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
const useStyles$1 = makeStyles({
|
|
1170
|
+
svgIcon: {
|
|
1171
|
+
display: "inline-block",
|
|
1172
|
+
"& svg": {
|
|
1173
|
+
display: "inline-block",
|
|
1174
|
+
fontSize: "inherit",
|
|
1175
|
+
verticalAlign: "baseline"
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
const IconLink = (props) => {
|
|
1180
|
+
const { href, text, Icon, ...linkProps } = props;
|
|
1181
|
+
const classes = useStyles$1();
|
|
1182
|
+
return /* @__PURE__ */ React.createElement(Grid, {
|
|
1183
|
+
container: true,
|
|
1184
|
+
direction: "row",
|
|
1185
|
+
spacing: 1
|
|
1186
|
+
}, /* @__PURE__ */ React.createElement(Grid, {
|
|
1187
|
+
item: true
|
|
1188
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
1189
|
+
component: "div",
|
|
1190
|
+
className: classes.svgIcon
|
|
1191
|
+
}, Icon ? /* @__PURE__ */ React.createElement(Icon, null) : /* @__PURE__ */ React.createElement(LanguageIcon, null))), /* @__PURE__ */ React.createElement(Grid, {
|
|
1192
|
+
item: true
|
|
1193
|
+
}, /* @__PURE__ */ React.createElement(Link$1, {
|
|
1194
|
+
to: href,
|
|
1195
|
+
...linkProps
|
|
1196
|
+
}, text || href)));
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
const TaskPageLinks = ({ output }) => {
|
|
1200
|
+
const { entityRef: entityRefOutput, remoteUrl } = output;
|
|
1201
|
+
let { links = [] } = output;
|
|
1202
|
+
const app = useApp();
|
|
1203
|
+
const entityRoute = useRouteRef(entityRouteRef);
|
|
1204
|
+
const iconResolver = (key) => {
|
|
1205
|
+
var _a;
|
|
1206
|
+
return key ? (_a = app.getSystemIcon(key)) != null ? _a : LanguageIcon : LanguageIcon;
|
|
1207
|
+
};
|
|
1208
|
+
if (remoteUrl) {
|
|
1209
|
+
links = [{ url: remoteUrl, title: "Repo" }, ...links];
|
|
1210
|
+
}
|
|
1211
|
+
if (entityRefOutput) {
|
|
1212
|
+
links = [
|
|
1213
|
+
{
|
|
1214
|
+
entityRef: entityRefOutput,
|
|
1215
|
+
title: "Open in catalog",
|
|
1216
|
+
icon: "catalog"
|
|
1217
|
+
},
|
|
1218
|
+
...links
|
|
1219
|
+
];
|
|
1220
|
+
}
|
|
1221
|
+
return /* @__PURE__ */ React.createElement(Box, {
|
|
1222
|
+
px: 3,
|
|
1223
|
+
pb: 3
|
|
1224
|
+
}, links.filter(({ url, entityRef }) => url || entityRef).map(({ url, entityRef, title, icon }) => {
|
|
1225
|
+
if (entityRef) {
|
|
1226
|
+
const entityName = parseEntityName(entityRef);
|
|
1227
|
+
const target = entityRoute(entityName);
|
|
1228
|
+
return { title, icon, url: target };
|
|
1229
|
+
}
|
|
1230
|
+
return { title, icon, url };
|
|
1231
|
+
}).map(({ url, title, icon }, i) => /* @__PURE__ */ React.createElement(IconLink, {
|
|
1232
|
+
key: `output-link-${i}`,
|
|
1233
|
+
href: url,
|
|
1234
|
+
text: title != null ? title : url,
|
|
1235
|
+
Icon: iconResolver(icon),
|
|
1236
|
+
target: "_blank"
|
|
1237
|
+
})));
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
const humanizeDuration = require("humanize-duration");
|
|
1241
|
+
const useStyles = makeStyles$1((theme) => createStyles({
|
|
1242
|
+
root: {
|
|
1243
|
+
width: "100%"
|
|
1244
|
+
},
|
|
1245
|
+
button: {
|
|
1246
|
+
marginBottom: theme.spacing(2),
|
|
1247
|
+
marginLeft: theme.spacing(2)
|
|
1248
|
+
},
|
|
1249
|
+
actionsContainer: {
|
|
1250
|
+
marginBottom: theme.spacing(2)
|
|
1251
|
+
},
|
|
1252
|
+
resetContainer: {
|
|
1253
|
+
padding: theme.spacing(3)
|
|
1254
|
+
},
|
|
1255
|
+
labelWrapper: {
|
|
1256
|
+
display: "flex",
|
|
1257
|
+
flex: 1,
|
|
1258
|
+
flexDirection: "row",
|
|
1259
|
+
justifyContent: "space-between"
|
|
1260
|
+
},
|
|
1261
|
+
stepWrapper: {
|
|
1262
|
+
width: "100%"
|
|
1263
|
+
}
|
|
1264
|
+
}));
|
|
1265
|
+
const StepTimeTicker = ({ step }) => {
|
|
1266
|
+
const [time, setTime] = useState("");
|
|
1267
|
+
useInterval(() => {
|
|
1268
|
+
if (!step.startedAt) {
|
|
1269
|
+
setTime("");
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
const end = step.endedAt ? DateTime.fromISO(step.endedAt) : DateTime.local();
|
|
1273
|
+
const startedAt = DateTime.fromISO(step.startedAt);
|
|
1274
|
+
const formatted = Interval.fromDateTimes(startedAt, end).toDuration().valueOf();
|
|
1275
|
+
setTime(humanizeDuration(formatted, { round: true }));
|
|
1276
|
+
}, 1e3);
|
|
1277
|
+
return /* @__PURE__ */ React.createElement(Typography$1, {
|
|
1278
|
+
variant: "caption"
|
|
1279
|
+
}, time);
|
|
1280
|
+
};
|
|
1281
|
+
const useStepIconStyles = makeStyles$1((theme) => createStyles({
|
|
1282
|
+
root: {
|
|
1283
|
+
color: theme.palette.text.disabled,
|
|
1284
|
+
display: "flex",
|
|
1285
|
+
height: 22,
|
|
1286
|
+
alignItems: "center"
|
|
1287
|
+
},
|
|
1288
|
+
completed: {
|
|
1289
|
+
color: theme.palette.status.ok
|
|
1290
|
+
},
|
|
1291
|
+
error: {
|
|
1292
|
+
color: theme.palette.status.error
|
|
1293
|
+
}
|
|
1294
|
+
}));
|
|
1295
|
+
function TaskStepIconComponent(props) {
|
|
1296
|
+
const classes = useStepIconStyles();
|
|
1297
|
+
const { active, completed, error } = props;
|
|
1298
|
+
const getMiddle = () => {
|
|
1299
|
+
if (active) {
|
|
1300
|
+
return /* @__PURE__ */ React.createElement(CircularProgress, {
|
|
1301
|
+
size: "24px"
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
if (completed) {
|
|
1305
|
+
return /* @__PURE__ */ React.createElement(Check, null);
|
|
1306
|
+
}
|
|
1307
|
+
if (error) {
|
|
1308
|
+
return /* @__PURE__ */ React.createElement(Cancel, null);
|
|
1309
|
+
}
|
|
1310
|
+
return /* @__PURE__ */ React.createElement(FiberManualRecordIcon, null);
|
|
1311
|
+
};
|
|
1312
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1313
|
+
className: classNames(classes.root, {
|
|
1314
|
+
[classes.completed]: completed,
|
|
1315
|
+
[classes.error]: error
|
|
1316
|
+
})
|
|
1317
|
+
}, getMiddle());
|
|
1318
|
+
}
|
|
1319
|
+
const TaskStatusStepper = memo(({
|
|
1320
|
+
steps,
|
|
1321
|
+
currentStepId,
|
|
1322
|
+
onUserStepChange
|
|
1323
|
+
}) => {
|
|
1324
|
+
const classes = useStyles();
|
|
1325
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1326
|
+
className: classes.root
|
|
1327
|
+
}, /* @__PURE__ */ React.createElement(Stepper, {
|
|
1328
|
+
activeStep: steps.findIndex((s) => s.id === currentStepId),
|
|
1329
|
+
orientation: "vertical",
|
|
1330
|
+
nonLinear: true
|
|
1331
|
+
}, steps.map((step, index) => {
|
|
1332
|
+
const isCompleted = step.status === "completed";
|
|
1333
|
+
const isFailed = step.status === "failed";
|
|
1334
|
+
const isActive = step.status === "processing";
|
|
1335
|
+
const isSkipped = step.status === "skipped";
|
|
1336
|
+
return /* @__PURE__ */ React.createElement(Step, {
|
|
1337
|
+
key: String(index),
|
|
1338
|
+
expanded: true
|
|
1339
|
+
}, /* @__PURE__ */ React.createElement(StepButton, {
|
|
1340
|
+
onClick: () => onUserStepChange(step.id)
|
|
1341
|
+
}, /* @__PURE__ */ React.createElement(StepLabel, {
|
|
1342
|
+
StepIconProps: {
|
|
1343
|
+
completed: isCompleted,
|
|
1344
|
+
error: isFailed,
|
|
1345
|
+
active: isActive
|
|
1346
|
+
},
|
|
1347
|
+
StepIconComponent: TaskStepIconComponent,
|
|
1348
|
+
className: classes.stepWrapper
|
|
1349
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
1350
|
+
className: classes.labelWrapper
|
|
1351
|
+
}, /* @__PURE__ */ React.createElement(Typography$1, {
|
|
1352
|
+
variant: "subtitle2"
|
|
1353
|
+
}, step.name), isSkipped ? /* @__PURE__ */ React.createElement(Typography$1, {
|
|
1354
|
+
variant: "caption"
|
|
1355
|
+
}, "Skipped") : /* @__PURE__ */ React.createElement(StepTimeTicker, {
|
|
1356
|
+
step
|
|
1357
|
+
})))));
|
|
1358
|
+
})));
|
|
1359
|
+
});
|
|
1360
|
+
const hasLinks = ({ entityRef, remoteUrl, links = [] }) => !!(entityRef || remoteUrl || links.length > 0);
|
|
1361
|
+
const TaskPage = ({ loadingText }) => {
|
|
1362
|
+
const classes = useStyles();
|
|
1363
|
+
const navigate = useNavigate();
|
|
1364
|
+
const rootLink = useRouteRef(rootRouteRef);
|
|
1365
|
+
const [userSelectedStepId, setUserSelectedStepId] = useState(void 0);
|
|
1366
|
+
const [lastActiveStepId, setLastActiveStepId] = useState(void 0);
|
|
1367
|
+
const { taskId } = useParams();
|
|
1368
|
+
const taskStream = useTaskEventStream(taskId);
|
|
1369
|
+
const completed = taskStream.completed;
|
|
1370
|
+
const steps = useMemo(() => {
|
|
1371
|
+
var _a, _b;
|
|
1372
|
+
return (_b = (_a = taskStream.task) == null ? void 0 : _a.spec.steps.map((step) => {
|
|
1373
|
+
var _a2;
|
|
1374
|
+
return {
|
|
1375
|
+
...step,
|
|
1376
|
+
...(_a2 = taskStream == null ? void 0 : taskStream.steps) == null ? void 0 : _a2[step.id]
|
|
1377
|
+
};
|
|
1378
|
+
})) != null ? _b : [];
|
|
1379
|
+
}, [taskStream]);
|
|
1380
|
+
useEffect(() => {
|
|
1381
|
+
var _a;
|
|
1382
|
+
const mostRecentFailedOrActiveStep = steps.find((step) => ["failed", "processing"].includes(step.status));
|
|
1383
|
+
if (completed && !mostRecentFailedOrActiveStep) {
|
|
1384
|
+
setLastActiveStepId((_a = steps[steps.length - 1]) == null ? void 0 : _a.id);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
setLastActiveStepId(mostRecentFailedOrActiveStep == null ? void 0 : mostRecentFailedOrActiveStep.id);
|
|
1388
|
+
}, [steps, completed]);
|
|
1389
|
+
const currentStepId = userSelectedStepId != null ? userSelectedStepId : lastActiveStepId;
|
|
1390
|
+
const logAsString = useMemo(() => {
|
|
1391
|
+
if (!currentStepId) {
|
|
1392
|
+
return loadingText ? loadingText : "Loading...";
|
|
1393
|
+
}
|
|
1394
|
+
const log = taskStream.stepLogs[currentStepId];
|
|
1395
|
+
if (!(log == null ? void 0 : log.length)) {
|
|
1396
|
+
return "Waiting for logs...";
|
|
1397
|
+
}
|
|
1398
|
+
return log.join("\n");
|
|
1399
|
+
}, [taskStream.stepLogs, currentStepId, loadingText]);
|
|
1400
|
+
const taskNotFound = taskStream.completed === true && taskStream.loading === false && !taskStream.task;
|
|
1401
|
+
const { output } = taskStream;
|
|
1402
|
+
const handleStartOver = () => {
|
|
1403
|
+
var _a, _b;
|
|
1404
|
+
if (!taskStream.task || !((_b = (_a = taskStream.task) == null ? void 0 : _a.spec.metadata) == null ? void 0 : _b.name)) {
|
|
1405
|
+
navigate(generatePath(rootLink()));
|
|
1406
|
+
}
|
|
1407
|
+
const formData = taskStream.task.spec.apiVersion === "backstage.io/v1beta2" ? taskStream.task.spec.values : taskStream.task.spec.parameters;
|
|
1408
|
+
navigate(generatePath(`${rootLink()}/templates/:templateName?${qs.stringify({
|
|
1409
|
+
formData: JSON.stringify(formData)
|
|
1410
|
+
})}`, {
|
|
1411
|
+
templateName: taskStream.task.spec.metadata.name
|
|
1412
|
+
}));
|
|
1413
|
+
};
|
|
1414
|
+
return /* @__PURE__ */ React.createElement(Page, {
|
|
1415
|
+
themeId: "home"
|
|
1416
|
+
}, /* @__PURE__ */ React.createElement(Header, {
|
|
1417
|
+
pageTitleOverride: `Task ${taskId}`,
|
|
1418
|
+
title: /* @__PURE__ */ React.createElement(React.Fragment, null, "Task Activity ", /* @__PURE__ */ React.createElement(Lifecycle, {
|
|
1419
|
+
alpha: true,
|
|
1420
|
+
shorthand: true
|
|
1421
|
+
})),
|
|
1422
|
+
subtitle: `Activity for task: ${taskId}`
|
|
1423
|
+
}), /* @__PURE__ */ React.createElement(Content, null, taskNotFound ? /* @__PURE__ */ React.createElement(ErrorPage, {
|
|
1424
|
+
status: "404",
|
|
1425
|
+
statusMessage: "Task not found",
|
|
1426
|
+
additionalInfo: "No task found with this ID"
|
|
1427
|
+
}) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Grid$1, {
|
|
1428
|
+
container: true
|
|
1429
|
+
}, /* @__PURE__ */ React.createElement(Grid$1, {
|
|
1430
|
+
item: true,
|
|
1431
|
+
xs: 3
|
|
1432
|
+
}, /* @__PURE__ */ React.createElement(Paper, null, /* @__PURE__ */ React.createElement(TaskStatusStepper, {
|
|
1433
|
+
steps,
|
|
1434
|
+
currentStepId,
|
|
1435
|
+
onUserStepChange: setUserSelectedStepId
|
|
1436
|
+
}), output && hasLinks(output) && /* @__PURE__ */ React.createElement(TaskPageLinks, {
|
|
1437
|
+
output
|
|
1438
|
+
}), /* @__PURE__ */ React.createElement(Button$1, {
|
|
1439
|
+
className: classes.button,
|
|
1440
|
+
onClick: handleStartOver,
|
|
1441
|
+
disabled: !completed,
|
|
1442
|
+
variant: "contained",
|
|
1443
|
+
color: "primary"
|
|
1444
|
+
}, "Start Over"))), /* @__PURE__ */ React.createElement(Grid$1, {
|
|
1445
|
+
item: true,
|
|
1446
|
+
xs: 9
|
|
1447
|
+
}, !currentStepId && /* @__PURE__ */ React.createElement(Progress, null), /* @__PURE__ */ React.createElement("div", {
|
|
1448
|
+
style: { height: "80vh" }
|
|
1449
|
+
}, /* @__PURE__ */ React.createElement(LogViewer, {
|
|
1450
|
+
text: logAsString
|
|
1451
|
+
})))))));
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
export { EntityPicker as E, FIELD_EXTENSION_WRAPPER_KEY as F, OwnerPicker as O, RepoUrlPicker as R, SecretsContext as S, TemplateTypePicker as T, EntityNamePicker as a, EntityTagsPicker as b, OwnedEntityPicker as c, registerComponentRouteRef as d, entityNamePickerValidation as e, TemplateList as f, rootRouteRef as g, FIELD_EXTENSION_KEY as h, SecretsContextProvider as i, TaskPage as j, ScaffolderClient as k, createScaffolderFieldExtension as l, ScaffolderFieldExtensions as m, EntityPickerFieldExtension as n, EntityNamePickerFieldExtension as o, EntityTagsPickerFieldExtension as p, OwnerPickerFieldExtension as q, repoPickerValidation as r, scaffolderApiRef as s, OwnedEntityPickerFieldExtension as t, RepoUrlPickerFieldExtension as u, ScaffolderPage as v, scaffolderPlugin as w, TextValuePicker as x, FavouriteTemplate as y, useTemplateSecrets as z };
|
|
1455
|
+
//# sourceMappingURL=index-71578268.esm.js.map
|