@backstage/plugin-scaffolder-react 1.8.4-next.0 → 1.8.4

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 CHANGED
@@ -1,5 +1,48 @@
1
1
  # @backstage/plugin-scaffolder-react
2
2
 
3
+ ## 1.8.4
4
+
5
+ ### Patch Changes
6
+
7
+ - abfbcfc: Updated dependency `@testing-library/react` to `^15.0.0`.
8
+ - 87d2eb8: Updated dependency `json-schema-library` to `^9.0.0`.
9
+ - cb1e3b0: Updated dependency `@testing-library/dom` to `^10.0.0`.
10
+ - 0e692cf: Added ESLint rule `no-top-level-material-ui-4-imports` to migrate the Material UI imports.
11
+ - df99f62: The `value` sent on the `create` analytics event (fired when a Scaffolder template is executed) is now set to the number of minutes saved by executing the template. This value is derived from the `backstage.io/time-saved` annotation on the template entity, if available.
12
+
13
+ Note: the `create` event is now captured in the `<Workflow>` component. If you are directly making use of the alpha-exported `<Stepper>` component, an analytics `create` event will no longer be captured on your behalf.
14
+
15
+ - Updated dependencies
16
+ - @backstage/plugin-catalog-react@1.11.3
17
+ - @backstage/core-components@0.14.4
18
+ - @backstage/core-plugin-api@1.9.2
19
+ - @backstage/theme@0.5.3
20
+ - @backstage/version-bridge@1.0.8
21
+ - @backstage/catalog-client@1.6.4
22
+ - @backstage/catalog-model@1.4.5
23
+ - @backstage/types@1.1.1
24
+ - @backstage/plugin-scaffolder-common@1.5.1
25
+
26
+ ## 1.8.4-next.1
27
+
28
+ ### Patch Changes
29
+
30
+ - 87d2eb8: Updated dependency `json-schema-library` to `^9.0.0`.
31
+ - df99f62: The `value` sent on the `create` analytics event (fired when a Scaffolder template is executed) is now set to the number of minutes saved by executing the template. This value is derived from the `backstage.io/time-saved` annotation on the template entity, if available.
32
+
33
+ Note: the `create` event is now captured in the `<Workflow>` component. If you are directly making use of the alpha-exported `<Stepper>` component, an analytics `create` event will no longer be captured on your behalf.
34
+
35
+ - Updated dependencies
36
+ - @backstage/catalog-client@1.6.4-next.0
37
+ - @backstage/catalog-model@1.4.5
38
+ - @backstage/core-components@0.14.4-next.0
39
+ - @backstage/core-plugin-api@1.9.1
40
+ - @backstage/theme@0.5.2
41
+ - @backstage/types@1.1.1
42
+ - @backstage/version-bridge@1.0.7
43
+ - @backstage/plugin-catalog-react@1.11.3-next.1
44
+ - @backstage/plugin-scaffolder-common@1.5.1
45
+
3
46
  ## 1.8.4-next.0
4
47
 
5
48
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-scaffolder-react",
3
- "version": "1.8.4-next.0",
3
+ "version": "1.8.4",
4
4
  "main": "../dist/alpha.esm.js",
5
5
  "module": "../dist/alpha.esm.js",
6
6
  "types": "../dist/alpha.d.ts"
package/dist/alpha.d.ts CHANGED
@@ -52,6 +52,11 @@ declare const ReviewState: (props: ReviewStateProps) => React__default.JSX.Eleme
52
52
  type StepperProps = {
53
53
  manifest: TemplateParameterSchema;
54
54
  extensions: FieldExtensionOptions<any, any>[];
55
+ /**
56
+ * @deprecated This was only ever used for analytics tracking purposes, which
57
+ * is now handled in the `<Workflow />` component. Passing it in will have no
58
+ * effect.
59
+ */
55
60
  templateName?: string;
56
61
  formProps?: FormProps;
57
62
  initialState?: Record<string, JsonValue>;
package/dist/alpha.esm.js CHANGED
@@ -1,5 +1,10 @@
1
1
  import { useApi, featureFlagsApiRef, useAnalytics, useApiHolder, useApp, errorApiRef, useRouteRef, alertApiRef } from '@backstage/core-plugin-api';
2
- import { makeStyles, FormControl, Typography, createStyles, Paper, List, ListItem, ListItemIcon, ListItemText, LinearProgress, Stepper as Stepper$1, Step, StepLabel, Button, useTheme, Card, CardContent, Grid, Box, Divider, Chip, CardActions, CircularProgress, StepButton, FormControlLabel, Checkbox, TextField } from '@material-ui/core';
2
+ import MuiStepper from '@material-ui/core/Stepper';
3
+ import MuiStep from '@material-ui/core/Step';
4
+ import MuiStepLabel from '@material-ui/core/StepLabel';
5
+ import Button from '@material-ui/core/Button';
6
+ import LinearProgress from '@material-ui/core/LinearProgress';
7
+ import { makeStyles, createStyles, useTheme } from '@material-ui/core/styles';
3
8
  import React, { useState, useMemo, useCallback, useEffect } from 'react';
4
9
  import { Draft07 } from 'json-schema-library';
5
10
  import { parse, stringify } from 'flatted';
@@ -7,36 +12,53 @@ import { StructuredMetadataTable, MarkdownContent, ItemCardHeader, Link, UserIco
7
12
  import validator from '@rjsf/validator-ajv8';
8
13
  import qs from 'qs';
9
14
  import useAsync from 'react-use/esm/useAsync';
10
- import { s as scaffolderApiRef, S as SecretsContextProvider } from './esm/ref-DURDhsGA.esm.js';
15
+ import { s as scaffolderApiRef, S as SecretsContextProvider } from './esm/ref-NRtFlQHB.esm.js';
11
16
  import cloneDeep from 'lodash/cloneDeep';
12
17
  import { withTheme } from '@rjsf/core';
13
18
  import { getUiOptions, getTemplate } from '@rjsf/utils';
19
+ import FormControl from '@material-ui/core/FormControl';
20
+ import Typography from '@material-ui/core/Typography';
14
21
  import { Theme } from '@rjsf/material-ui';
22
+ import List from '@material-ui/core/List';
23
+ import ListItem from '@material-ui/core/ListItem';
24
+ import ListItemIcon from '@material-ui/core/ListItemIcon';
25
+ import ListItemText from '@material-ui/core/ListItemText';
26
+ import Paper from '@material-ui/core/Paper';
15
27
  import ErrorIcon from '@material-ui/icons/Error';
16
28
  import { RELATION_OWNED_BY, stringifyEntityRef, parseEntityRef } from '@backstage/catalog-model';
17
- import { FavoriteEntity, getEntityRelations, EntityRefLinks, useEntityList, entityRouteRef, useEntityTypeFilter } from '@backstage/plugin-catalog-react';
29
+ import { FavoriteEntity, getEntityRelations, EntityRefLinks, useEntityList, catalogApiRef, entityRouteRef, useEntityTypeFilter } from '@backstage/plugin-catalog-react';
30
+ import Box from '@material-ui/core/Box';
31
+ import Card from '@material-ui/core/Card';
32
+ import CardActions from '@material-ui/core/CardActions';
33
+ import CardContent from '@material-ui/core/CardContent';
34
+ import Chip from '@material-ui/core/Chip';
35
+ import Divider from '@material-ui/core/Divider';
36
+ import Grid from '@material-ui/core/Grid';
18
37
  import LanguageIcon from '@material-ui/icons/Language';
19
38
  import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
39
+ import { Duration, DateTime, Interval } from 'luxon';
40
+ import useAsync$1 from 'react-use/lib/useAsync';
20
41
  import LinkIcon from '@material-ui/icons/Link';
21
42
  import DescriptionIcon from '@material-ui/icons/Description';
43
+ import MuiStepButton from '@material-ui/core/StepButton';
44
+ import CircularProgress from '@material-ui/core/CircularProgress';
22
45
  import RemoveCircleOutline from '@material-ui/icons/RemoveCircleOutline';
23
46
  import PanoramaFishEyeIcon from '@material-ui/icons/PanoramaFishEye';
24
47
  import classNames from 'classnames';
25
48
  import CheckCircleOutline from '@material-ui/icons/CheckCircleOutline';
26
49
  import ErrorOutline from '@material-ui/icons/ErrorOutline';
27
50
  import useInterval from 'react-use/esm/useInterval';
28
- import { DateTime, Interval } from 'luxon';
29
51
  import humanizeDuration from 'humanize-duration';
30
52
  import { useMountEffect } from '@react-hookz/web';
31
- import { makeStyles as makeStyles$1 } from '@material-ui/core/styles';
32
53
  import capitalize from 'lodash/capitalize';
54
+ import Checkbox from '@material-ui/core/Checkbox';
55
+ import FormControlLabel from '@material-ui/core/FormControlLabel';
56
+ import TextField from '@material-ui/core/TextField';
33
57
  import CheckBoxIcon from '@material-ui/icons/CheckBox';
34
58
  import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
35
59
  import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
36
- import { Autocomplete } from '@material-ui/lab';
60
+ import Autocomplete from '@material-ui/lab/Autocomplete';
37
61
  import IconButton from '@material-ui/core/IconButton';
38
- import ListItemIcon$1 from '@material-ui/core/ListItemIcon';
39
- import ListItemText$1 from '@material-ui/core/ListItemText';
40
62
  import MenuItem from '@material-ui/core/MenuItem';
41
63
  import MenuList from '@material-ui/core/MenuList';
42
64
  import Popover from '@material-ui/core/Popover';
@@ -175,6 +197,7 @@ function isObject(value) {
175
197
  return typeof value === "object" && value !== null && !Array.isArray(value);
176
198
  }
177
199
 
200
+ const isJsonError = (value) => "type" in value && value.type === "error";
178
201
  const createAsyncValidators = (rootSchema, validators, context) => {
179
202
  async function validate(formData, pathPrefix = "#", current = formData) {
180
203
  var _a, _b;
@@ -198,9 +221,20 @@ const createAsyncValidators = (rootSchema, validators, context) => {
198
221
  }
199
222
  };
200
223
  for (const [key, value] of Object.entries(current)) {
201
- const path = `${pathPrefix}/${key}`;
202
- const definitionInSchema = parsedSchema.getSchema(path, formData);
203
- const { schema, uiSchema } = extractSchemaFromStep(definitionInSchema);
224
+ const pointer = `${pathPrefix}/${key}`;
225
+ const definitionInSchema = parsedSchema.getSchema({
226
+ pointer,
227
+ data: formData
228
+ });
229
+ if (!definitionInSchema) {
230
+ continue;
231
+ }
232
+ if (isJsonError(definitionInSchema)) {
233
+ throw new Error(definitionInSchema.message);
234
+ }
235
+ const { schema, uiSchema } = extractSchemaFromStep(
236
+ definitionInSchema
237
+ );
204
238
  const hasItems = definitionInSchema && definitionInSchema.items;
205
239
  const doValidateItem = async (propValue, itemSchema, itemUiSchema) => {
206
240
  await validateForm(
@@ -217,7 +251,7 @@ const createAsyncValidators = (rootSchema, validators, context) => {
217
251
  await doValidateItem(propValue, itemsSchema, itemsUiSchema);
218
252
  }
219
253
  };
220
- if (definitionInSchema && "ui:field" in definitionInSchema) {
254
+ if ("ui:field" in definitionInSchema) {
221
255
  await doValidateItem(definitionInSchema, schema, uiSchema);
222
256
  } else if (hasItems && "ui:field" in definitionInSchema.items) {
223
257
  await doValidate(definitionInSchema.items);
@@ -227,7 +261,7 @@ const createAsyncValidators = (rootSchema, validators, context) => {
227
261
  await doValidate(propValue);
228
262
  }
229
263
  } else if (isObject(value)) {
230
- formValidation[key] = await validate(formData, path, value);
264
+ formValidation[key] = await validate(formData, pointer, value);
231
265
  }
232
266
  }
233
267
  return formValidation;
@@ -243,10 +277,10 @@ const ReviewState = (props) => {
243
277
  var _a;
244
278
  for (const step of props.schemas) {
245
279
  const parsedSchema = new Draft07(step.mergedSchema);
246
- const definitionInSchema = parsedSchema.getSchema(
247
- `#/${key}`,
248
- props.formState
249
- );
280
+ const definitionInSchema = parsedSchema.getSchema({
281
+ pointer: `#/${key}`,
282
+ data: props.formState
283
+ });
250
284
  if (definitionInSchema) {
251
285
  const backstageReviewOptions = (_a = definitionInSchema["ui:backstage"]) == null ? void 0 : _a.review;
252
286
  if (backstageReviewOptions) {
@@ -663,11 +697,8 @@ const Stepper = (stepperProps) => {
663
697
  [setFormState]
664
698
  );
665
699
  const handleCreate = useCallback(() => {
666
- var _a2;
667
700
  props.onCreate(formState);
668
- const name = typeof formState.name === "string" ? formState.name : void 0;
669
- analytics.captureEvent("create", (_a2 = name != null ? name : props.templateName) != null ? _a2 : "unknown");
670
- }, [props, formState, analytics]);
701
+ }, [props, formState]);
671
702
  const currentStep = useTransformSchemaToProps(steps[activeStep], { layouts });
672
703
  const handleNext = async ({
673
704
  formData = {}
@@ -692,7 +723,7 @@ const Stepper = (stepperProps) => {
692
723
  const createLabel = (_d = (_c = presentation == null ? void 0 : presentation.buttonLabels) == null ? void 0 : _c.createButtonText) != null ? _d : createButtonText;
693
724
  const reviewLabel = (_f = (_e = presentation == null ? void 0 : presentation.buttonLabels) == null ? void 0 : _e.reviewButtonText) != null ? _f : reviewButtonText;
694
725
  return /* @__PURE__ */ React.createElement(React.Fragment, null, isValidating && /* @__PURE__ */ React.createElement(LinearProgress, { variant: "indeterminate" }), /* @__PURE__ */ React.createElement(
695
- Stepper$1,
726
+ MuiStepper,
696
727
  {
697
728
  activeStep,
698
729
  alternativeLabel: true,
@@ -701,8 +732,8 @@ const Stepper = (stepperProps) => {
701
732
  },
702
733
  steps.map((step, index) => {
703
734
  const isAllowedLabelClick = activeStep > index;
704
- return /* @__PURE__ */ React.createElement(Step, { key: index }, /* @__PURE__ */ React.createElement(
705
- StepLabel,
735
+ return /* @__PURE__ */ React.createElement(MuiStep, { key: index }, /* @__PURE__ */ React.createElement(
736
+ MuiStepLabel,
706
737
  {
707
738
  "aria-label": `Step ${index + 1}`,
708
739
  style: { cursor: isAllowedLabelClick ? "pointer" : "default" },
@@ -714,7 +745,7 @@ const Stepper = (stepperProps) => {
714
745
  step.title
715
746
  ));
716
747
  }),
717
- /* @__PURE__ */ React.createElement(Step, null, /* @__PURE__ */ React.createElement(StepLabel, null, "Review"))
748
+ /* @__PURE__ */ React.createElement(MuiStep, null, /* @__PURE__ */ React.createElement(MuiStepLabel, null, "Review"))
718
749
  ), /* @__PURE__ */ React.createElement("div", { className: styles.formWrapper }, activeStep < steps.length ? /* @__PURE__ */ React.createElement(
719
750
  Form,
720
751
  {
@@ -989,6 +1020,24 @@ const TemplateGroups = (props) => {
989
1020
  }));
990
1021
  };
991
1022
 
1023
+ const useTemplateTimeSavedMinutes = (templateRef) => {
1024
+ const catalogApi = useApi(catalogApiRef);
1025
+ const { value: timeSavedMinutes } = useAsync$1(async () => {
1026
+ var _a;
1027
+ const entity = await catalogApi.getEntityByRef(templateRef);
1028
+ const timeSaved = (_a = entity == null ? void 0 : entity.metadata.annotations) == null ? void 0 : _a["backstage.io/time-saved"];
1029
+ if (!entity || !timeSaved) {
1030
+ return void 0;
1031
+ }
1032
+ const durationMs = Duration.fromISO(timeSaved).as("minutes");
1033
+ if (Number.isNaN(durationMs)) {
1034
+ return void 0;
1035
+ }
1036
+ return durationMs;
1037
+ }, [catalogApi, templateRef]);
1038
+ return timeSavedMinutes;
1039
+ };
1040
+
992
1041
  const useStyles$5 = makeStyles({
993
1042
  markdown: {
994
1043
  /** to make the styles for React Markdown not leak into the description */
@@ -1002,7 +1051,8 @@ const useStyles$5 = makeStyles({
1002
1051
  });
1003
1052
  const Workflow = (workflowProps) => {
1004
1053
  var _a;
1005
- const { title, description, namespace, templateName, ...props } = workflowProps;
1054
+ const { title, description, namespace, templateName, onCreate, ...props } = workflowProps;
1055
+ const analytics = useAnalytics();
1006
1056
  const styles = useStyles$5();
1007
1057
  const templateRef = stringifyEntityRef({
1008
1058
  kind: "Template",
@@ -1012,6 +1062,18 @@ const Workflow = (workflowProps) => {
1012
1062
  const errorApi = useApi(errorApiRef);
1013
1063
  const { loading, manifest, error } = useTemplateParameterSchema(templateRef);
1014
1064
  const sortedManifest = useFilteredSchemaProperties(manifest);
1065
+ const minutesSaved = useTemplateTimeSavedMinutes(templateRef);
1066
+ const workflowOnCreate = useCallback(
1067
+ async (formState) => {
1068
+ var _a2;
1069
+ onCreate(formState);
1070
+ const name = typeof formState.name === "string" ? formState.name : void 0;
1071
+ analytics.captureEvent("create", (_a2 = name != null ? name : templateName) != null ? _a2 : "unknown", {
1072
+ value: minutesSaved
1073
+ });
1074
+ },
1075
+ [onCreate, analytics, templateName, minutesSaved]
1076
+ );
1015
1077
  useEffect(() => {
1016
1078
  if (error) {
1017
1079
  errorApi.post(new Error(`Failed to load template, ${error}`));
@@ -1038,7 +1100,7 @@ const Workflow = (workflowProps) => {
1038
1100
  Stepper,
1039
1101
  {
1040
1102
  manifest: sortedManifest,
1041
- templateName,
1103
+ onCreate: workflowOnCreate,
1042
1104
  ...props
1043
1105
  }
1044
1106
  )
@@ -1249,7 +1311,7 @@ const TaskSteps = (props) => {
1249
1311
  isError: (_b = props.isError) != null ? _b : false
1250
1312
  }
1251
1313
  ), /* @__PURE__ */ React.createElement(Box, { padding: 2 }, /* @__PURE__ */ React.createElement(
1252
- Stepper$1,
1314
+ MuiStepper,
1253
1315
  {
1254
1316
  activeStep: props.activeStep,
1255
1317
  alternativeLabel: true,
@@ -1267,8 +1329,8 @@ const TaskSteps = (props) => {
1267
1329
  active: isActive,
1268
1330
  skipped: isSkipped
1269
1331
  };
1270
- return /* @__PURE__ */ React.createElement(Step, { key: step.id }, /* @__PURE__ */ React.createElement(StepButton, null, /* @__PURE__ */ React.createElement(
1271
- StepLabel,
1332
+ return /* @__PURE__ */ React.createElement(MuiStep, { key: step.id }, /* @__PURE__ */ React.createElement(MuiStepButton, null, /* @__PURE__ */ React.createElement(
1333
+ MuiStepLabel,
1272
1334
  {
1273
1335
  StepIconProps: stepIconProps,
1274
1336
  StepIconComponent: StepIcon,
@@ -1281,7 +1343,7 @@ const TaskSteps = (props) => {
1281
1343
  )));
1282
1344
  };
1283
1345
 
1284
- const useStyles$2 = makeStyles$1({
1346
+ const useStyles$2 = makeStyles({
1285
1347
  root: {
1286
1348
  width: "100%",
1287
1349
  height: "100%",
@@ -1360,7 +1422,7 @@ const TemplateCategoryPicker = () => {
1360
1422
  ));
1361
1423
  };
1362
1424
 
1363
- const useStyles = makeStyles$1((theme) => ({
1425
+ const useStyles = makeStyles((theme) => ({
1364
1426
  button: {
1365
1427
  color: theme.page.fontColor
1366
1428
  }
@@ -1403,7 +1465,7 @@ function ScaffolderPageContextMenu(props) {
1403
1465
  anchorOrigin: { vertical: "bottom", horizontal: "right" },
1404
1466
  transformOrigin: { vertical: "top", horizontal: "right" }
1405
1467
  },
1406
- /* @__PURE__ */ React.createElement(MenuList, null, onCreateClicked && /* @__PURE__ */ React.createElement(MenuItem, { onClick: onCreateClicked }, /* @__PURE__ */ React.createElement(ListItemIcon$1, null, /* @__PURE__ */ React.createElement(CreateComponentIcon, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText$1, { primary: "Create" })), onEditorClicked && /* @__PURE__ */ React.createElement(MenuItem, { onClick: onEditorClicked }, /* @__PURE__ */ React.createElement(ListItemIcon$1, null, /* @__PURE__ */ React.createElement(Edit, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText$1, { primary: "Template Editor" })), onActionsClicked && /* @__PURE__ */ React.createElement(MenuItem, { onClick: onActionsClicked }, /* @__PURE__ */ React.createElement(ListItemIcon$1, null, /* @__PURE__ */ React.createElement(DescriptionIcon, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText$1, { primary: "Installed Actions" })), onTasksClicked && /* @__PURE__ */ React.createElement(MenuItem, { onClick: onTasksClicked }, /* @__PURE__ */ React.createElement(ListItemIcon$1, null, /* @__PURE__ */ React.createElement(List$1, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText$1, { primary: "Task List" })))
1468
+ /* @__PURE__ */ React.createElement(MenuList, null, onCreateClicked && /* @__PURE__ */ React.createElement(MenuItem, { onClick: onCreateClicked }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(CreateComponentIcon, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Create" })), onEditorClicked && /* @__PURE__ */ React.createElement(MenuItem, { onClick: onEditorClicked }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Edit, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Template Editor" })), onActionsClicked && /* @__PURE__ */ React.createElement(MenuItem, { onClick: onActionsClicked }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(DescriptionIcon, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Installed Actions" })), onTasksClicked && /* @__PURE__ */ React.createElement(MenuItem, { onClick: onTasksClicked }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(List$1, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Task List" })))
1407
1469
  ));
1408
1470
  }
1409
1471