@backstage/plugin-scaffolder 1.3.0-next.1 → 1.3.0-next.2

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,22 @@
1
1
  # @backstage/plugin-scaffolder
2
2
 
3
+ ## 1.3.0-next.2
4
+
5
+ ### Minor Changes
6
+
7
+ - dc39366bdb: - Added a new page under `/create/tasks` to show tasks that have been run by the Scaffolder.
8
+ - Ability to filter these tasks by the signed in user, and all tasks.
9
+ - Added optional method to the `ScaffolderApi` interface called `listTasks` to get tasks with an required `filterByOwnership` parameter.
10
+
11
+ ### Patch Changes
12
+
13
+ - ac0c7e45ee: Fixes review mask in `MultistepJsonForm` to work as documented. `show: true` no longer needed when mask is set.
14
+ - fd505f40c0: Handle binary files and files that are too large during dry-run content upload.
15
+ - Updated dependencies
16
+ - @backstage/plugin-catalog-common@1.0.3-next.1
17
+ - @backstage/core-components@0.9.5-next.2
18
+ - @backstage/integration@1.2.1-next.2
19
+
3
20
  ## 1.3.0-next.1
4
21
 
5
22
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-scaffolder",
3
- "version": "1.3.0-next.1",
3
+ "version": "1.3.0-next.2",
4
4
  "main": "../dist/index.esm.js",
5
5
  "types": "../dist/index.alpha.d.ts"
6
6
  }
@@ -1,11 +1,11 @@
1
- import React, { useState, useContext, useCallback, createContext, useEffect, useRef, useMemo, Children, Component } from 'react';
1
+ import React, { useState, useContext, useCallback, createContext, useEffect, useRef, useMemo, Children, Component, Fragment } from 'react';
2
2
  import { useNavigate, Navigate, useOutlet, Routes, Route } from 'react-router';
3
- import { ItemCardHeader, MarkdownContent, Button, ContentHeader, Progress, WarningPanel, Link as Link$1, Content, ItemCardGrid, Page, Header, CreateButton, SupportButton, StructuredMetadataTable, InfoCard, ErrorPage, ErrorPanel, LogViewer } from '@backstage/core-components';
3
+ import { ItemCardHeader, MarkdownContent, Button, ContentHeader, Progress, WarningPanel, Link as Link$1, Content, ItemCardGrid, Page, Header, CreateButton, SupportButton, StructuredMetadataTable, InfoCard, ErrorPage, ErrorPanel, LogViewer, StatusError, StatusOK, StatusPending, Lifecycle, EmptyState } from '@backstage/core-components';
4
4
  import { useRouteRef, useApi, errorApiRef, featureFlagsApiRef, useApiHolder, alertApiRef, useElementFilter } from '@backstage/core-plugin-api';
5
- import { getEntityRelations, getEntitySourceLocation, FavoriteEntity, EntityRefLinks, useEntityList, EntityListProvider, CatalogFilterLayout, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, catalogApiRef, humanizeEntityRef } from '@backstage/plugin-catalog-react';
6
- import { s as selectedTemplateRouteRef, r as rootRouteRef, a as registerComponentRouteRef, T as TemplateTypePicker, S as SecretsContext, b as scaffolderApiRef, c as scaffolderTaskRouteRef, d as TaskStatusStepper, e as TaskPageLinks, F as FIELD_EXTENSION_WRAPPER_KEY, f as FIELD_EXTENSION_KEY, g as SecretsContextProvider, h as actionsRouteRef, i as editRouteRef, j as TaskPage } from './index-6a2f3be9.esm.js';
7
- import { RELATION_OWNED_BY, stringifyEntityRef } from '@backstage/catalog-model';
8
- import { makeStyles, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, IconButton, Tooltip, Link, Stepper, Step, StepLabel, StepContent, Button as Button$1, Paper, LinearProgress, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Divider as Divider$1, FormControl, InputLabel, Select, MenuItem as MenuItem$1 } from '@material-ui/core';
5
+ import { getEntityRelations, getEntitySourceLocation, FavoriteEntity, EntityRefLinks, useEntityList, EntityListProvider, CatalogFilterLayout, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, catalogApiRef, humanizeEntityRef, EntityRefLink } from '@backstage/plugin-catalog-react';
6
+ import { s as selectedTemplateRouteRef, e as editRouteRef, a as actionsRouteRef, b as scaffolderListTaskRouteRef, r as registerComponentRouteRef, T as TemplateTypePicker, S as SecretsContext, c as scaffolderApiRef, d as scaffolderTaskRouteRef, f as rootRouteRef, g as TaskStatusStepper, h as TaskPageLinks, F as FIELD_EXTENSION_WRAPPER_KEY, i as FIELD_EXTENSION_KEY, j as SecretsContextProvider, k as TaskPage } from './index-b64713a1.esm.js';
7
+ import { RELATION_OWNED_BY, stringifyEntityRef, parseEntityRef } from '@backstage/catalog-model';
8
+ import { makeStyles, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, IconButton, Tooltip, Link, Stepper, Step, StepLabel, StepContent, Button as Button$1, Paper, LinearProgress, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Divider as Divider$1, FormControl, InputLabel, Select, MenuItem as MenuItem$1, List as List$2, ListItemIcon as ListItemIcon$1, ListItemText as ListItemText$1 } from '@material-ui/core';
9
9
  import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
10
10
  import WarningIcon from '@material-ui/icons/Warning';
11
11
  import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common';
@@ -19,6 +19,7 @@ import Popover from '@material-ui/core/Popover';
19
19
  import { makeStyles as makeStyles$1 } from '@material-ui/core/styles';
20
20
  import Description from '@material-ui/icons/Description';
21
21
  import Edit from '@material-ui/icons/Edit';
22
+ import List from '@material-ui/icons/List';
22
23
  import MoreVert from '@material-ui/icons/MoreVert';
23
24
  import qs from 'qs';
24
25
  import { useParams } from 'react-router-dom';
@@ -40,7 +41,7 @@ import AccordionDetails from '@material-ui/core/AccordionDetails';
40
41
  import AccordionSummary from '@material-ui/core/AccordionSummary';
41
42
  import Divider from '@material-ui/core/Divider';
42
43
  import ExpandMoreIcon$1 from '@material-ui/icons/ExpandLess';
43
- import List from '@material-ui/core/List';
44
+ import List$1 from '@material-ui/core/List';
44
45
  import ListItem from '@material-ui/core/ListItem';
45
46
  import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
46
47
  import Cancel from '@material-ui/icons/Cancel';
@@ -61,7 +62,12 @@ import RefreshIcon from '@material-ui/icons/Refresh';
61
62
  import SaveIcon from '@material-ui/icons/Save';
62
63
  import useDebounce from 'react-use/lib/useDebounce';
63
64
  import { showPanel } from '@codemirror/view';
64
- import { D as DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from './default-bff55719.esm.js';
65
+ import MaterialTable from '@material-table/core';
66
+ import SettingsIcon from '@material-ui/icons/Settings';
67
+ import AllIcon from '@material-ui/icons/FontDownload';
68
+ import { DateTime, Interval } from 'luxon';
69
+ import humanizeDuration from 'humanize-duration';
70
+ import { D as DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from './default-554cb9ad.esm.js';
65
71
  import '@backstage/errors';
66
72
  import 'zen-observable';
67
73
  import '@material-ui/core/FormControl';
@@ -79,12 +85,11 @@ import '@material-ui/core/Step';
79
85
  import '@material-ui/core/StepLabel';
80
86
  import '@material-ui/core/Stepper';
81
87
  import '@material-ui/icons/FiberManualRecord';
82
- import 'luxon';
83
88
  import 'react-use/lib/useInterval';
84
89
  import 'use-immer';
85
90
  import '@material-ui/icons/Language';
86
91
 
87
- const useStyles$d = makeStyles((theme) => ({
92
+ const useStyles$e = makeStyles((theme) => ({
88
93
  cardHeader: {
89
94
  position: "relative"
90
95
  },
@@ -163,7 +168,7 @@ const TemplateCard = ({ template, deprecated }) => {
163
168
  const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
164
169
  const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) ? templateProps.type : "other";
165
170
  const theme = backstageTheme.getPageTheme({ themeId });
166
- const classes = useStyles$d({ backgroundImage: theme.backgroundImage });
171
+ const classes = useStyles$e({ backgroundImage: theme.backgroundImage });
167
172
  const href = templateRoute({ templateName: templateProps.name });
168
173
  const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
169
174
  const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi);
@@ -248,18 +253,21 @@ const TemplateList = ({
248
253
  })))));
249
254
  };
250
255
 
251
- const useStyles$c = makeStyles$1({
256
+ const useStyles$d = makeStyles$1({
252
257
  button: {
253
258
  color: "white"
254
259
  }
255
260
  });
256
261
  function ScaffolderPageContextMenu(props) {
257
- const classes = useStyles$c();
262
+ const classes = useStyles$d();
258
263
  const [anchorEl, setAnchorEl] = useState();
259
- const pageLink = useRouteRef(rootRouteRef);
264
+ const editLink = useRouteRef(editRouteRef);
265
+ const actionsLink = useRouteRef(actionsRouteRef);
266
+ const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
260
267
  const navigate = useNavigate();
261
268
  const showEditor = props.editor !== false;
262
269
  const showActions = props.actions !== false;
270
+ const showTasks = props.tasks !== false;
263
271
  if (!showEditor && !showActions) {
264
272
  return null;
265
273
  }
@@ -284,17 +292,23 @@ function ScaffolderPageContextMenu(props) {
284
292
  anchorOrigin: { vertical: "bottom", horizontal: "right" },
285
293
  transformOrigin: { vertical: "top", horizontal: "right" }
286
294
  }, /* @__PURE__ */ React.createElement(MenuList, null, showEditor && /* @__PURE__ */ React.createElement(MenuItem, {
287
- onClick: () => navigate(`${pageLink()}/edit`)
295
+ onClick: () => navigate(editLink())
288
296
  }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Edit, {
289
297
  fontSize: "small"
290
298
  })), /* @__PURE__ */ React.createElement(ListItemText, {
291
299
  primary: "Template Editor"
292
300
  })), showActions && /* @__PURE__ */ React.createElement(MenuItem, {
293
- onClick: () => navigate(`${pageLink()}/actions`)
301
+ onClick: () => navigate(actionsLink())
294
302
  }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Description, {
295
303
  fontSize: "small"
296
304
  })), /* @__PURE__ */ React.createElement(ListItemText, {
297
305
  primary: "Installed Actions"
306
+ })), showTasks && /* @__PURE__ */ React.createElement(MenuItem, {
307
+ onClick: () => navigate(tasksLink())
308
+ }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(List, {
309
+ fontSize: "small"
310
+ })), /* @__PURE__ */ React.createElement(ListItemText, {
311
+ primary: "Task List"
298
312
  })))));
299
313
  }
300
314
 
@@ -474,13 +488,13 @@ function getReviewData(formData, steps) {
474
488
  continue;
475
489
  }
476
490
  const review = uiSchema["ui:backstage"].review;
477
- if (!review.show) {
478
- continue;
479
- }
480
491
  if (review.mask) {
481
492
  reviewData[key] = review.mask;
482
493
  continue;
483
494
  }
495
+ if (!review.show) {
496
+ continue;
497
+ }
484
498
  reviewData[key] = formData[key];
485
499
  }
486
500
  }
@@ -732,7 +746,7 @@ const TemplatePage = ({
732
746
  }))));
733
747
  };
734
748
 
735
- const useStyles$b = makeStyles((theme) => ({
749
+ const useStyles$c = makeStyles((theme) => ({
736
750
  code: {
737
751
  fontFamily: "Menlo, monospace",
738
752
  padding: theme.spacing(1),
@@ -755,7 +769,7 @@ const useStyles$b = makeStyles((theme) => ({
755
769
  }));
756
770
  const ActionsPage = () => {
757
771
  const api = useApi(scaffolderApiRef);
758
- const classes = useStyles$b();
772
+ const classes = useStyles$c();
759
773
  const { loading, value, error } = useAsync(async () => {
760
774
  return api.listActions();
761
775
  });
@@ -891,7 +905,7 @@ class WebFileSystemAccess {
891
905
  }
892
906
  }
893
907
 
894
- const useStyles$a = makeStyles$1((theme) => ({
908
+ const useStyles$b = makeStyles$1((theme) => ({
895
909
  introText: {
896
910
  textAlign: "center",
897
911
  marginTop: theme.spacing(2)
@@ -909,7 +923,7 @@ const useStyles$a = makeStyles$1((theme) => ({
909
923
  }
910
924
  }));
911
925
  function TemplateEditorIntro(props) {
912
- const classes = useStyles$a();
926
+ const classes = useStyles$b();
913
927
  const supportsLoad = WebFileSystemAccess.isSupported();
914
928
  const cardLoadLocal = /* @__PURE__ */ React.createElement(Card$1, {
915
929
  className: classes.card,
@@ -1133,7 +1147,25 @@ function DirectoryEditorProvider(props) {
1133
1147
  }, props.children);
1134
1148
  }
1135
1149
 
1150
+ const MAX_CONTENT_SIZE = 256 * 1024;
1151
+ const CHUNK_SIZE = 32768;
1136
1152
  const DryRunContext = createContext(void 0);
1153
+ function base64EncodeContent(content) {
1154
+ if (content.length > MAX_CONTENT_SIZE) {
1155
+ return btoa("<file too large>");
1156
+ }
1157
+ try {
1158
+ return btoa(content);
1159
+ } catch {
1160
+ const decoder = new TextEncoder();
1161
+ const buffer = decoder.encode(content);
1162
+ const chunks = new Array();
1163
+ for (let offset = 0; offset < buffer.length; offset += CHUNK_SIZE) {
1164
+ chunks.push(String.fromCharCode(...buffer.slice(offset, offset + CHUNK_SIZE)));
1165
+ }
1166
+ return btoa(chunks.join(""));
1167
+ }
1168
+ }
1137
1169
  function DryRunProvider(props) {
1138
1170
  const scaffolderApi = useApi(scaffolderApiRef);
1139
1171
  const [state, setState] = useState({
@@ -1179,7 +1211,7 @@ function DryRunProvider(props) {
1179
1211
  secrets: {},
1180
1212
  directoryContents: options.files.map((file) => ({
1181
1213
  path: file.path,
1182
- base64Content: btoa(file.content)
1214
+ base64Content: base64EncodeContent(file.content)
1183
1215
  }))
1184
1216
  });
1185
1217
  const result = {
@@ -1212,7 +1244,7 @@ function useDryRun() {
1212
1244
  return value;
1213
1245
  }
1214
1246
 
1215
- const useStyles$9 = makeStyles$1((theme) => ({
1247
+ const useStyles$a = makeStyles$1((theme) => ({
1216
1248
  root: {
1217
1249
  overflowY: "auto",
1218
1250
  background: theme.palette.background.default
@@ -1229,9 +1261,9 @@ const useStyles$9 = makeStyles$1((theme) => ({
1229
1261
  }
1230
1262
  }));
1231
1263
  function DryRunResultsList() {
1232
- const classes = useStyles$9();
1264
+ const classes = useStyles$a();
1233
1265
  const dryRun = useDryRun();
1234
- return /* @__PURE__ */ React.createElement(List, {
1266
+ return /* @__PURE__ */ React.createElement(List$1, {
1235
1267
  className: classes.root,
1236
1268
  dense: true
1237
1269
  }, dryRun.results.map((result) => {
@@ -1254,7 +1286,7 @@ function DryRunResultsList() {
1254
1286
  }));
1255
1287
  }
1256
1288
 
1257
- const useStyles$8 = makeStyles$1({
1289
+ const useStyles$9 = makeStyles$1({
1258
1290
  root: {
1259
1291
  whiteSpace: "nowrap",
1260
1292
  overflowY: "auto"
@@ -1320,7 +1352,7 @@ function FileTreeItem({ entry }) {
1320
1352
  })));
1321
1353
  }
1322
1354
  function FileBrowser(props) {
1323
- const classes = useStyles$8();
1355
+ const classes = useStyles$9();
1324
1356
  const fileTree = useMemo(() => parseFileEntires(props.filePaths), [props.filePaths]);
1325
1357
  return /* @__PURE__ */ React.createElement(TreeView, {
1326
1358
  selected: props.selected,
@@ -1338,7 +1370,7 @@ function FileBrowser(props) {
1338
1370
  })));
1339
1371
  }
1340
1372
 
1341
- const useStyles$7 = makeStyles$1((theme) => ({
1373
+ const useStyles$8 = makeStyles$1((theme) => ({
1342
1374
  root: {
1343
1375
  display: "grid",
1344
1376
  gridTemplateColumns: "280px auto 3fr",
@@ -1354,7 +1386,7 @@ const useStyles$7 = makeStyles$1((theme) => ({
1354
1386
  }
1355
1387
  }));
1356
1388
  function DryRunResultsSplitView(props) {
1357
- const classes = useStyles$7();
1389
+ const classes = useStyles$8();
1358
1390
  const childArray = Children.toArray(props.children);
1359
1391
  if (childArray.length !== 2) {
1360
1392
  throw new Error("must have exactly 2 children");
@@ -1370,7 +1402,7 @@ function DryRunResultsSplitView(props) {
1370
1402
  }, childArray[1]));
1371
1403
  }
1372
1404
 
1373
- const useStyles$6 = makeStyles$1({
1405
+ const useStyles$7 = makeStyles$1({
1374
1406
  root: {
1375
1407
  display: "flex",
1376
1408
  flexFlow: "column nowrap"
@@ -1396,7 +1428,7 @@ const useStyles$6 = makeStyles$1({
1396
1428
  }
1397
1429
  });
1398
1430
  function FilesContent() {
1399
- const classes = useStyles$6();
1431
+ const classes = useStyles$7();
1400
1432
  const { selectedResult } = useDryRun();
1401
1433
  const [selectedPath, setSelectedPath] = useState("");
1402
1434
  const selectedFile = selectedResult == null ? void 0 : selectedResult.directoryContents.find((f) => f.path === selectedPath);
@@ -1461,7 +1493,7 @@ function LogContent() {
1461
1493
  }
1462
1494
  function OutputContent() {
1463
1495
  var _a, _b;
1464
- const classes = useStyles$6();
1496
+ const classes = useStyles$7();
1465
1497
  const { selectedResult } = useDryRun();
1466
1498
  if (!selectedResult) {
1467
1499
  return null;
@@ -1480,7 +1512,7 @@ function OutputContent() {
1480
1512
  }));
1481
1513
  }
1482
1514
  function DryRunResultsView() {
1483
- const classes = useStyles$6();
1515
+ const classes = useStyles$7();
1484
1516
  const [selectedTab, setSelectedTab] = useState("files");
1485
1517
  return /* @__PURE__ */ React.createElement("div", {
1486
1518
  className: classes.root
@@ -1503,7 +1535,7 @@ function DryRunResultsView() {
1503
1535
  }, selectedTab === "files" && /* @__PURE__ */ React.createElement(FilesContent, null), selectedTab === "log" && /* @__PURE__ */ React.createElement(LogContent, null), selectedTab === "output" && /* @__PURE__ */ React.createElement(OutputContent, null))));
1504
1536
  }
1505
1537
 
1506
- const useStyles$5 = makeStyles$1((theme) => ({
1538
+ const useStyles$6 = makeStyles$1((theme) => ({
1507
1539
  header: {
1508
1540
  height: 48,
1509
1541
  minHeight: 0,
@@ -1522,7 +1554,7 @@ const useStyles$5 = makeStyles$1((theme) => ({
1522
1554
  }
1523
1555
  }));
1524
1556
  function DryRunResults() {
1525
- const classes = useStyles$5();
1557
+ const classes = useStyles$6();
1526
1558
  const dryRun = useDryRun();
1527
1559
  const [expanded, setExpanded] = useState(false);
1528
1560
  const [hidden, setHidden] = useState(true);
@@ -1554,7 +1586,7 @@ function DryRunResults() {
1554
1586
  }), /* @__PURE__ */ React.createElement(DryRunResultsView, null))));
1555
1587
  }
1556
1588
 
1557
- const useStyles$4 = makeStyles((theme) => ({
1589
+ const useStyles$5 = makeStyles((theme) => ({
1558
1590
  button: {
1559
1591
  padding: theme.spacing(1)
1560
1592
  },
@@ -1573,7 +1605,7 @@ const useStyles$4 = makeStyles((theme) => ({
1573
1605
  }));
1574
1606
  function TemplateEditorBrowser(props) {
1575
1607
  var _a, _b;
1576
- const classes = useStyles$4();
1608
+ const classes = useStyles$5();
1577
1609
  const directoryEditor = useDirectoryEditor();
1578
1610
  const changedFiles = directoryEditor.files.filter((file) => file.dirty);
1579
1611
  const handleClose = () => {
@@ -1617,7 +1649,7 @@ function TemplateEditorBrowser(props) {
1617
1649
  }));
1618
1650
  }
1619
1651
 
1620
- const useStyles$3 = makeStyles$1({
1652
+ const useStyles$4 = makeStyles$1({
1621
1653
  containerWrapper: {
1622
1654
  position: "relative",
1623
1655
  width: "100%",
@@ -1665,7 +1697,7 @@ function TemplateEditorForm(props) {
1665
1697
  setErrorText,
1666
1698
  fieldExtensions = []
1667
1699
  } = props;
1668
- const classes = useStyles$3();
1700
+ const classes = useStyles$4();
1669
1701
  const apiHolder = useApiHolder();
1670
1702
  const [steps, setSteps] = useState();
1671
1703
  const fields = useMemo(() => {
@@ -1760,7 +1792,7 @@ function TemplateEditorFormDirectoryEditorDryRun(props) {
1760
1792
  }
1761
1793
  TemplateEditorForm.DirectoryEditorDryRun = TemplateEditorFormDirectoryEditorDryRun;
1762
1794
 
1763
- const useStyles$2 = makeStyles((theme) => ({
1795
+ const useStyles$3 = makeStyles((theme) => ({
1764
1796
  container: {
1765
1797
  position: "relative",
1766
1798
  width: "100%",
@@ -1789,7 +1821,7 @@ const useStyles$2 = makeStyles((theme) => ({
1789
1821
  }));
1790
1822
  function TemplateEditorTextArea(props) {
1791
1823
  const { errorText } = props;
1792
- const classes = useStyles$2();
1824
+ const classes = useStyles$3();
1793
1825
  const panelExtension = useMemo(() => {
1794
1826
  if (!errorText) {
1795
1827
  return showPanel.of(null);
@@ -1855,7 +1887,7 @@ function TemplateEditorDirectoryEditorTextArea(props) {
1855
1887
  }
1856
1888
  TemplateEditorTextArea.DirectoryEditor = TemplateEditorDirectoryEditorTextArea;
1857
1889
 
1858
- const useStyles$1 = makeStyles({
1890
+ const useStyles$2 = makeStyles({
1859
1891
  root: {
1860
1892
  gridArea: "pageContent",
1861
1893
  display: "grid",
@@ -1883,7 +1915,7 @@ const useStyles$1 = makeStyles({
1883
1915
  }
1884
1916
  });
1885
1917
  const TemplateEditor = (props) => {
1886
- const classes = useStyles$1();
1918
+ const classes = useStyles$2();
1887
1919
  const [errorText, setErrorText] = useState();
1888
1920
  return /* @__PURE__ */ React.createElement(DirectoryEditorProvider, {
1889
1921
  directory: props.directory
@@ -1945,7 +1977,7 @@ steps:
1945
1977
  values:
1946
1978
  name: \${{parameters.name}}
1947
1979
  `;
1948
- const useStyles = makeStyles((theme) => ({
1980
+ const useStyles$1 = makeStyles((theme) => ({
1949
1981
  root: {
1950
1982
  gridArea: "pageContent",
1951
1983
  display: "grid",
@@ -1975,7 +2007,7 @@ const TemplateFormPreviewer = ({
1975
2007
  customFieldExtensions = [],
1976
2008
  onClose
1977
2009
  }) => {
1978
- const classes = useStyles();
2010
+ const classes = useStyles$1();
1979
2011
  const alertApi = useApi(alertApiRef);
1980
2012
  const catalogApi = useApi(catalogApiRef);
1981
2013
  const [selectedTemplate, setSelectedTemplate] = useState("");
@@ -2082,6 +2114,220 @@ function TemplateEditorPage(props) {
2082
2114
  }), content);
2083
2115
  }
2084
2116
 
2117
+ const useStyles = makeStyles((theme) => ({
2118
+ root: {
2119
+ backgroundColor: "rgba(0, 0, 0, .11)",
2120
+ boxShadow: "none",
2121
+ margin: theme.spacing(1, 0, 1, 0)
2122
+ },
2123
+ title: {
2124
+ margin: theme.spacing(1, 0, 0, 1),
2125
+ textTransform: "uppercase",
2126
+ fontSize: 12,
2127
+ fontWeight: "bold"
2128
+ },
2129
+ listIcon: {
2130
+ minWidth: 30,
2131
+ color: theme.palette.text.primary
2132
+ },
2133
+ menuItem: {
2134
+ minHeight: theme.spacing(6)
2135
+ },
2136
+ groupWrapper: {
2137
+ margin: theme.spacing(1, 1, 2, 1)
2138
+ }
2139
+ }), {
2140
+ name: "ScaffolderReactOwnerListPicker"
2141
+ });
2142
+ function getFilterGroups() {
2143
+ return [
2144
+ {
2145
+ name: "Task Owner",
2146
+ items: [
2147
+ {
2148
+ id: "owned",
2149
+ label: "Owned",
2150
+ icon: SettingsIcon
2151
+ },
2152
+ {
2153
+ id: "all",
2154
+ label: "All",
2155
+ icon: AllIcon
2156
+ }
2157
+ ]
2158
+ }
2159
+ ];
2160
+ }
2161
+ const OwnerListPicker = (props) => {
2162
+ const { filter, onSelectOwner } = props;
2163
+ const classes = useStyles();
2164
+ const filterGroups = getFilterGroups();
2165
+ return /* @__PURE__ */ React.createElement(Card, {
2166
+ className: classes.root
2167
+ }, filterGroups.map((group) => /* @__PURE__ */ React.createElement(Fragment, {
2168
+ key: group.name
2169
+ }, /* @__PURE__ */ React.createElement(Typography, {
2170
+ variant: "subtitle2",
2171
+ className: classes.title
2172
+ }, group.name), /* @__PURE__ */ React.createElement(Card, {
2173
+ className: classes.groupWrapper
2174
+ }, /* @__PURE__ */ React.createElement(List$2, {
2175
+ disablePadding: true,
2176
+ dense: true
2177
+ }, group.items.map((item) => /* @__PURE__ */ React.createElement(MenuItem$1, {
2178
+ key: item.id,
2179
+ button: true,
2180
+ divider: true,
2181
+ onClick: () => onSelectOwner(item.id),
2182
+ selected: item.id === filter,
2183
+ className: classes.menuItem,
2184
+ "data-testid": `owner-picker-${item.id}`
2185
+ }, item.icon && /* @__PURE__ */ React.createElement(ListItemIcon$1, {
2186
+ className: classes.listIcon
2187
+ }, /* @__PURE__ */ React.createElement(item.icon, {
2188
+ fontSize: "small"
2189
+ })), /* @__PURE__ */ React.createElement(ListItemText$1, null, /* @__PURE__ */ React.createElement(Typography, {
2190
+ variant: "body1"
2191
+ }, item.label)))))))));
2192
+ };
2193
+
2194
+ const CreatedAtColumn = ({ createdAt }) => {
2195
+ const createdAtTime = DateTime.fromISO(createdAt);
2196
+ const formatted = Interval.fromDateTimes(createdAtTime, DateTime.local()).toDuration().valueOf();
2197
+ return /* @__PURE__ */ React.createElement("p", null, humanizeDuration(formatted, { round: true }), " ago");
2198
+ };
2199
+
2200
+ const OwnerEntityColumn = ({ entityRef }) => {
2201
+ var _a, _b, _c;
2202
+ const catalogApi = useApi(catalogApiRef);
2203
+ const { value, loading, error } = useAsync(() => catalogApi.getEntityByRef(entityRef || ""), [catalogApi, entityRef]);
2204
+ if (!entityRef) {
2205
+ return /* @__PURE__ */ React.createElement("p", null, "Unknown");
2206
+ }
2207
+ if (loading || error) {
2208
+ return null;
2209
+ }
2210
+ return /* @__PURE__ */ React.createElement(EntityRefLink, {
2211
+ entityRef: parseEntityRef(entityRef),
2212
+ title: (_c = (_b = (_a = value == null ? void 0 : value.spec) == null ? void 0 : _a.profile) == null ? void 0 : _b.displayName) != null ? _c : value == null ? void 0 : value.metadata.name
2213
+ });
2214
+ };
2215
+
2216
+ const TaskStatusColumn = ({ status }) => {
2217
+ switch (status) {
2218
+ case "processing":
2219
+ return /* @__PURE__ */ React.createElement(StatusPending, null, status);
2220
+ case "completed":
2221
+ return /* @__PURE__ */ React.createElement(StatusOK, null, status);
2222
+ case "error":
2223
+ default:
2224
+ return /* @__PURE__ */ React.createElement(StatusError, null, status);
2225
+ }
2226
+ };
2227
+
2228
+ const TemplateTitleColumn = ({ entityRef }) => {
2229
+ const scaffolder = useApi(scaffolderApiRef);
2230
+ const { value, loading, error } = useAsync(() => scaffolder.getTemplateParameterSchema(entityRef || ""), [scaffolder, entityRef]);
2231
+ if (loading || error || !entityRef) {
2232
+ return null;
2233
+ }
2234
+ return /* @__PURE__ */ React.createElement(EntityRefLink, {
2235
+ entityRef: parseEntityRef(entityRef),
2236
+ title: value == null ? void 0 : value.title
2237
+ });
2238
+ };
2239
+
2240
+ const ListTaskPageContent = (props) => {
2241
+ var _a;
2242
+ const { initiallySelectedFilter = "owned" } = props;
2243
+ const scaffolderApi = useApi(scaffolderApiRef);
2244
+ const rootLink = useRouteRef(rootRouteRef);
2245
+ const [ownerFilter, setOwnerFilter] = useState(initiallySelectedFilter);
2246
+ const { value, loading, error } = useAsync(() => {
2247
+ var _a2;
2248
+ if (scaffolderApi.listTasks) {
2249
+ return (_a2 = scaffolderApi.listTasks) == null ? void 0 : _a2.call(scaffolderApi, { filterByOwnership: ownerFilter });
2250
+ }
2251
+ console.warn("listTasks is not implemented in the scaffolderApi, please make sure to implement this method.");
2252
+ return Promise.resolve({ tasks: [] });
2253
+ }, [scaffolderApi, ownerFilter]);
2254
+ if (loading) {
2255
+ return /* @__PURE__ */ React.createElement(Progress, null);
2256
+ }
2257
+ if (error) {
2258
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ErrorPanel, {
2259
+ error
2260
+ }), /* @__PURE__ */ React.createElement(EmptyState, {
2261
+ missing: "info",
2262
+ title: "No information to display",
2263
+ description: "There is no Tasks or there was an issue communicating with backend."
2264
+ }));
2265
+ }
2266
+ return /* @__PURE__ */ React.createElement(CatalogFilterLayout, null, /* @__PURE__ */ React.createElement(CatalogFilterLayout.Filters, null, /* @__PURE__ */ React.createElement(OwnerListPicker, {
2267
+ filter: ownerFilter,
2268
+ onSelectOwner: (id) => setOwnerFilter(id)
2269
+ })), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, /* @__PURE__ */ React.createElement(MaterialTable, {
2270
+ data: (_a = value == null ? void 0 : value.tasks) != null ? _a : [],
2271
+ title: "Tasks",
2272
+ columns: [
2273
+ {
2274
+ title: "Task ID",
2275
+ field: "id",
2276
+ render: (row) => /* @__PURE__ */ React.createElement(Link$1, {
2277
+ to: `${rootLink()}/tasks/${row.id}`
2278
+ }, row.id)
2279
+ },
2280
+ {
2281
+ title: "Template",
2282
+ render: (row) => {
2283
+ var _a2;
2284
+ return /* @__PURE__ */ React.createElement(TemplateTitleColumn, {
2285
+ entityRef: (_a2 = row.spec.templateInfo) == null ? void 0 : _a2.entityRef
2286
+ });
2287
+ }
2288
+ },
2289
+ {
2290
+ title: "Created",
2291
+ field: "createdAt",
2292
+ render: (row) => /* @__PURE__ */ React.createElement(CreatedAtColumn, {
2293
+ createdAt: row.createdAt
2294
+ })
2295
+ },
2296
+ {
2297
+ title: "Owner",
2298
+ field: "createdBy",
2299
+ render: (row) => {
2300
+ var _a2, _b;
2301
+ return /* @__PURE__ */ React.createElement(OwnerEntityColumn, {
2302
+ entityRef: (_b = (_a2 = row.spec) == null ? void 0 : _a2.user) == null ? void 0 : _b.ref
2303
+ });
2304
+ }
2305
+ },
2306
+ {
2307
+ title: "Status",
2308
+ field: "status",
2309
+ render: (row) => /* @__PURE__ */ React.createElement(TaskStatusColumn, {
2310
+ status: row.status
2311
+ })
2312
+ }
2313
+ ]
2314
+ })));
2315
+ };
2316
+ const ListTasksPage = (props) => {
2317
+ return /* @__PURE__ */ React.createElement(Page, {
2318
+ themeId: "home"
2319
+ }, /* @__PURE__ */ React.createElement(Header, {
2320
+ pageTitleOverride: "Templates Tasks",
2321
+ title: /* @__PURE__ */ React.createElement(React.Fragment, null, "List template tasks ", /* @__PURE__ */ React.createElement(Lifecycle, {
2322
+ shorthand: true,
2323
+ alpha: true
2324
+ })),
2325
+ subtitle: "All tasks that have been started"
2326
+ }), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ListTaskPageContent, {
2327
+ ...props
2328
+ })));
2329
+ };
2330
+
2085
2331
  const Router = (props) => {
2086
2332
  const { groups, components = {}, defaultPreviewTemplate } = props;
2087
2333
  const { TemplateCardComponent, TaskPageComponent } = components;
@@ -2107,6 +2353,9 @@ const Router = (props) => {
2107
2353
  element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(TemplatePage, {
2108
2354
  customFieldExtensions: fieldExtensions
2109
2355
  }))
2356
+ }), /* @__PURE__ */ React.createElement(Route, {
2357
+ path: scaffolderListTaskRouteRef.path,
2358
+ element: /* @__PURE__ */ React.createElement(ListTasksPage, null)
2110
2359
  }), /* @__PURE__ */ React.createElement(Route, {
2111
2360
  path: scaffolderTaskRouteRef.path,
2112
2361
  element: /* @__PURE__ */ React.createElement(TaskPageElement, null)
@@ -2128,4 +2377,4 @@ const Router = (props) => {
2128
2377
  };
2129
2378
 
2130
2379
  export { Router };
2131
- //# sourceMappingURL=Router-0396362f.esm.js.map
2380
+ //# sourceMappingURL=Router-09b6a7c3.esm.js.map