UNPKG

@digital-ai/plugin-dai-release

Version:

Frontend functionalities for the dai-release backstage plugin

311 lines (308 loc) 11.4 kB
import { DotTypography, DotInputText, CssGrid, CssCell, DotDialog, DotAlertBanner } from '@digital-ai/dot-components'; import { TreeView, TreeItem } from '@mui/x-tree-view'; import React, { useState, useRef, useEffect } from 'react'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import ArrowRightIcon from '@mui/icons-material/ArrowRight'; import FolderOpenIcon from '@mui/icons-material/FolderOpen'; import IconButton from '@mui/material/IconButton'; import { WorkflowCard } from './WorkflowCardComponent.esm.js'; import { WorkflowCardSkeleton } from './Skeleton/WorkflowCardSkeletonComponent.esm.js'; import { calculateCellProps } from '../../utils/helpers.esm.js'; import isNil from 'lodash/isNil'; import { makeStyles } from '@material-ui/core'; import { useFolders } from '../../hooks/useFolders.esm.js'; import { useWorkflowRedirect } from '../../hooks/useWorkflowRedirect.esm.js'; const useStyles = makeStyles(() => ({ searchHeader: { marginBottom: "5px" }, dotIconSize: { fontSize: "20px" }, workflowCatalog: { height: "100%", width: "100%" }, workflowDrawerHeaderSearch: { paddingBottom: "24px" // .spacing(padding-bottom, 3); }, workflowCards: { flex: 1, overflowY: "auto" }, catalogGrid: { marginTop: "24px" }, customDialogWidth: { "& .MuiDialogContent-root": { overflowY: "hidden" // Ensure the dialog does not have a scroll bar }, "& .dot-dialog-content": { width: "600px", maxHeight: "196px", overflowY: "hidden" } }, dotDialogTitle: { "& h2": { flexGrow: 1, fontSize: "20px", fontFamily: "Lato, sans-serif", // Set your desired font size marginBottom: "0px" } }, dotTypography: { "& .MuiTypography-body1": { fontSize: "14px", fontFamily: "Lato, sans-serif" }, "& .MuiTypography-subtitle2": { fontSize: "14px", fontWeight: "700", fontFamily: "Lato, sans-serif" } }, dotButton: { "& .MuiButtonBase-root": { fontSize: "14px", fontFamily: "Lato, sans-serif" }, "& .MuiButton-root": { textTransform: "none" } }, noWorkflow: { display: "flex !important", justifyContent: "center", alignItems: "center" }, folderContainer: { maxHeight: "200px" // Adjust the height as needed } })); const WorkflowCatalogComponent = ({ loading, loadMoreData, data, searchInput, onSearchInput, resetState, instance, backstageTheme }) => { const classes = useStyles(); const [workflowDialogOpen, setWorkflowDialogOpen] = useState( null ); const [selectedFolderId, setSelectedFolderId] = useState( void 0 ); const containerRef = useRef(null); const observerTarget = useRef(null); const [shouldRedirect, setShouldRedirect] = useState(false); const [redirectUrl, setRedirectUrl] = useState(""); const [errorMessage, setErrorMessage] = useState(null); const [workflowParams, setWorkflowParams] = useState(null); useWorkflowRedirect( instance, workflowParams?.templateId || "", workflowParams?.title || "", workflowParams?.folderId || "", setRedirectUrl, setErrorMessage ); const { folders, triggerFetch } = useFolders(); const handleOnRunClick = (workflowFromCategory) => { triggerFetch(instance, workflowFromCategory.id); setWorkflowDialogOpen(workflowFromCategory.id); }; const handleRunWorkflow = () => { if (!workflowDialogOpen) return; const selectedWorkflow = data.find((w) => w.id === workflowDialogOpen); if (!selectedWorkflow) return; setWorkflowParams({ templateId: workflowDialogOpen, title: selectedWorkflow.title, folderId: selectedFolderId || "" }); }; useEffect(() => { if (workflowParams) { if (errorMessage) { setShouldRedirect(false); } else { setShouldRedirect(true); } } }, [errorMessage, workflowParams]); useEffect(() => { if (shouldRedirect && redirectUrl) { window.open(redirectUrl, "_blank"); setShouldRedirect(false); setWorkflowDialogOpen(null); setSelectedFolderId(void 0); setErrorMessage(null); setWorkflowParams(null); } }, [shouldRedirect, redirectUrl]); const handleScroll = () => { if (containerRef.current) { const { scrollTop, scrollHeight, clientHeight } = containerRef.current; if (scrollTop + clientHeight >= scrollHeight - 5) { loadMoreData(); } } }; function handleSearchInput(value) { resetState(); onSearchInput(value); } const handleOnCancel = () => { setWorkflowDialogOpen(null); setSelectedFolderId(void 0); setErrorMessage(null); setWorkflowParams(null); setShouldRedirect(false); }; const renderDialog = () => { const workflow = data.find((w) => w.id === workflowDialogOpen); if (!workflow) return null; const renderError = (message) => /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DotAlertBanner, { severity: "error", className: "dot-alert-banner" }, /* @__PURE__ */ React.createElement(DotTypography, null, message)), /* @__PURE__ */ React.createElement("br", null)); const renderTree = (nodes) => /* @__PURE__ */ React.createElement( TreeItem, { key: nodes.key, nodeId: nodes.key, label: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(FolderOpenIcon, { style: { marginRight: "8px" } }), nodes.title) }, Array.isArray(nodes.children) ? nodes.children.map((node) => renderTree(node)) : null ); const renderFolderTree = () => { const folderList = folders.folders; const convertToTreeNodes = (folderArray) => { return folderArray.map((folder) => ({ key: folder.id, title: folder.title, children: folder.children ? convertToTreeNodes(folder.children) : [] })); }; const limitedFolders = folderList; return convertToTreeNodes(limitedFolders); }; const options = renderFolderTree(); return /* @__PURE__ */ React.createElement( DotDialog, { cancelButtonProps: { label: "Cancel" }, className: `card-folder-dialog ${classes.customDialogWidth} ${classes.dotDialogTitle} ${classes.dotTypography} ${classes.dotButton}`, closeIconVisible: true, closeOnClickAway: true, closeOnSubmit: shouldRedirect, onSubmit: handleRunWorkflow, open: true, onCancel: handleOnCancel, submitButtonProps: { label: "Run workflow", disabled: isNil(selectedFolderId) || !!errorMessage }, title: "Choose folder" }, errorMessage && renderError(errorMessage), /* @__PURE__ */ React.createElement(DotTypography, null, "Select the folder where workflow ", /* @__PURE__ */ React.createElement("strong", null, workflow.title), " ", "will be run."), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement(DotTypography, { className: "persistent-label", variant: "subtitle2" }, "Folder name"), /* @__PURE__ */ React.createElement("div", { className: classes.folderContainer }, /* @__PURE__ */ React.createElement( TreeView, { sx: { "& .MuiTreeItem-root": { marginBottom: "8px" // Space between TreeItems }, "& .MuiTreeItem-content": { padding: "4px 8px" // Space around the TreeItem content }, "& .MuiTreeItem-label": { fontSize: "12px" // Customize label size } }, defaultCollapseIcon: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(ArrowDropDownIcon, null)), defaultExpandIcon: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(ArrowRightIcon, null)), selected: selectedFolderId || `Applications/${workflow.defaultTargetFolder}`, onNodeSelect: (_, nodeId) => setSelectedFolderId(nodeId) }, options.map((option) => renderTree(option)) )) ); }; const renderWorkflows = () => { return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement( CssGrid, { columnGap: { xxl: 12, xl: 12, lg: 12, md: 8, sm: 8, xs: 8 }, rowGap: { xxl: 12, xl: 12, lg: 12, md: 8, sm: 8, xs: 8 }, "row-gap": "12px" }, data.map((currentWorkflow, index) => { const props = calculateCellProps(index); return /* @__PURE__ */ React.createElement(CssCell, { ...props, key: currentWorkflow.id }, /* @__PURE__ */ React.createElement( WorkflowCard, { onClick: () => handleOnRunClick(currentWorkflow), workflow: currentWorkflow, backstageTheme } )); }), loading && [...Array(3)].map((_value, skeletonIndex) => { const props = calculateCellProps(skeletonIndex); return /* @__PURE__ */ React.createElement(CssCell, { ...props, key: skeletonIndex }, /* @__PURE__ */ React.createElement(WorkflowCardSkeleton, null)); }) ), data.length === 0 && !loading && /* @__PURE__ */ React.createElement( CssGrid, { columnGap: { xxl: 12, xl: 12, lg: 12, md: 8, sm: 8, xs: 8 }, rowGap: { xxl: 12, xl: 12, lg: 12, md: 8, sm: 8, xs: 8 }, "row-gap": "12px", className: classes.noWorkflow }, /* @__PURE__ */ React.createElement(CssCell, null, /* @__PURE__ */ React.createElement(DotTypography, { variant: "body1" }, "No workflows found")) )); }; return /* @__PURE__ */ React.createElement("div", { className: classes.workflowDrawerHeaderSearch }, /* @__PURE__ */ React.createElement(DotTypography, { className: classes.searchHeader, variant: "subtitle2" }, "Search Workflows"), /* @__PURE__ */ React.createElement( DotInputText, { id: "search-input", name: "search-input", onChange: (e) => handleSearchInput(e.target.value), persistentLabel: true, placeholder: "Start typing to filter workflows...", value: searchInput, endIcon: /* @__PURE__ */ React.createElement(IconButton, { type: "button", sx: { p: "10px" }, "aria-label": "search" }, /* @__PURE__ */ React.createElement( "span", { className: `${classes.dotIconSize} dot-icon`, style: { height: "20px", fontSize: "20px" } }, /* @__PURE__ */ React.createElement("i", { className: "icon-search" }) )) } ), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement( "div", { className: "search-row", style: { height: "calc(80vh - 100px)", overflowY: "scroll" }, ref: containerRef, onScroll: handleScroll }, renderWorkflows() ), renderDialog(), /* @__PURE__ */ React.createElement("div", { ref: observerTarget })); }; export { WorkflowCatalogComponent }; //# sourceMappingURL=WorkflowCatalogComponent.esm.js.map