@digital-ai/plugin-dai-release
Version:
Frontend functionalities for the dai-release backstage plugin
311 lines (308 loc) • 11.4 kB
JavaScript
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