From 93d5e9b9521c125ee312efbc8f2273e7128f91e6 Mon Sep 17 00:00:00 2001 From: PintoGideon Date: Tue, 20 Feb 2024 11:33:36 -0500 Subject: [PATCH 1/4] Cleanup --- src/components/CreateFeed/Pipelines.tsx | 3 + src/components/CreateFeed/utils.ts | 12 ++- src/components/Dashboard/index.tsx | 17 ++--- src/components/Feeds/FeedListView.tsx | 11 ++- src/components/IconContainer/index.tsx | 12 +-- src/components/NodeDetails/PluginTitle.tsx | 5 +- src/components/NodeDetails/Status.tsx | 7 +- .../Pipelines/ClipboardCopyCommand.tsx | 12 ++- src/components/Pipelines/Tree.tsx | 73 ++++++++++--------- src/store/user/types.ts | 2 +- src/types/index.d.ts | 4 - vite.config.ts | 22 +++--- 12 files changed, 96 insertions(+), 84 deletions(-) diff --git a/src/components/CreateFeed/Pipelines.tsx b/src/components/CreateFeed/Pipelines.tsx index d86407130..9bcb4f287 100644 --- a/src/components/CreateFeed/Pipelines.tsx +++ b/src/components/CreateFeed/Pipelines.tsx @@ -482,10 +482,13 @@ const Pipelines = ({ } handleSetCurrentNodeTitle={handleSetCurrentNodeTitle} /> + {/* + + */} , ): Promise => { - const arr: any[] = []; + const arr: { + breadcrumb: string; + title: string; + key: string; + isLeaf: boolean; + checkable: boolean; + }[] = []; //@ts-ignore const { files, folders } = await fetchFilesFromAPath(treeNode.breadcrumb); const items = [...files, ...folders]; @@ -74,20 +80,20 @@ export const generateTreeNodes = async ( const filePath = items[i].data.fname.split("/"); const fileName = filePath[filePath.length - 1]; arr.push({ - //@ts-ignore breadcrumb: `${treeNode.breadcrumb}/${fileName}`, title: fileName, key: `${treeNode.key}-${i}`, isLeaf: true, + checkable: false, }); } else { const checkList = ["uploads", "SERVICES", "PACS"]; const isCheckable = !checkList.includes(items[i]); arr.push({ - //@ts-ignore breadcrumb: `${treeNode.breadcrumb}/${items[i]}`, title: items[i], key: `${treeNode.key}-${i}`, + isLeaf: false, checkable: isCheckable, }); } diff --git a/src/components/Dashboard/index.tsx b/src/components/Dashboard/index.tsx index c05fd2734..88fa36f5d 100644 --- a/src/components/Dashboard/index.tsx +++ b/src/components/Dashboard/index.tsx @@ -1,12 +1,13 @@ import React from "react"; import { useDispatch } from "react-redux"; +import preval from "preval.macro"; import WrapperConnect from "../Wrapper"; import { PageSection, Grid, GridItem } from "@patternfly/react-core"; import { Typography } from "antd"; import { setSidebarActive } from "../../store/ui/actions"; -import "./dashboard.css"; import { InfoIcon } from "../Common"; import { CatalogTile } from "@patternfly/react-catalog-view-extension"; +import "./dashboard.css"; const { Paragraph } = Typography; @@ -28,15 +29,11 @@ const DashboardPage = (props: DashboardProps) => { ); }, [dispatch]); - /* - const buildVersion = preval` const { execSync } = require('child_process') module.exports = execSync('npm run -s print-version', {encoding: 'utf-8'}) `; - */ - return ( @@ -50,7 +47,7 @@ const DashboardPage = (props: DashboardProps) => { Let's get started.

- Build: + Build: {buildVersion}

} @@ -69,7 +66,7 @@ const DashboardPage = (props: DashboardProps) => { description={ 'Visit the "Library" in the main navigation to review your data collection' } - > + />{" "} @@ -80,7 +77,7 @@ const DashboardPage = (props: DashboardProps) => { description={ 'Visit "New and Existing Analyses" in the main navigation to review your data analyses' } - > + /> @@ -91,7 +88,7 @@ const DashboardPage = (props: DashboardProps) => { description={ 'Visit "PACS Query/Retrieve" in the main navigation to pull medical data and save it your library' } - > + /> @@ -102,7 +99,7 @@ const DashboardPage = (props: DashboardProps) => { description={ 'Visit "Run a Quick Workflow" to choose from existing analysis templates that allow for detailed analysis' } - > + />
diff --git a/src/components/Feeds/FeedListView.tsx b/src/components/Feeds/FeedListView.tsx index 0c7f90b62..e13258eae 100644 --- a/src/components/Feeds/FeedListView.tsx +++ b/src/components/Feeds/FeedListView.tsx @@ -96,13 +96,20 @@ const TableSelectable: React.FunctionComponent = () => { (state) => state.feed, ); - const onSetPage = (_e: any, newPage: number) => { + const onSetPage = ( + _e: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPage: number, + ) => { navigate( `/feeds?search=${search}&searchType=${searchType}&page=${newPage}&perPage=${perPage}&type=${type}`, ); }; - const onPerPageSelect = (_e: any, newPerPage: number, newPage: number) => { + const onPerPageSelect = ( + _e: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPerPage: number, + newPage: number, + ) => { navigate( `/feeds?search=${search}&searchType=${searchType}&page=${newPage}&perPage=${newPerPage}&type=${type}`, ); diff --git a/src/components/IconContainer/index.tsx b/src/components/IconContainer/index.tsx index 158f1d8b0..7c22f480c 100644 --- a/src/components/IconContainer/index.tsx +++ b/src/components/IconContainer/index.tsx @@ -131,9 +131,9 @@ const IconContainer = ({ allFeeds }: { allFeeds: Feed[] }) => { }); }; - const handleDelete = (bulkSelect: Feed[]) => { + const handleDelete = async (bulkSelect: Feed[]) => { if (bulkSelect && allFeeds) { - bulkSelect.forEach(async (feed: Feed) => { + for (const feed of bulkSelect) { try { await feed.delete(); } catch (error: any) { @@ -142,13 +142,13 @@ const IconContainer = ({ allFeeds }: { allFeeds: Feed[] }) => { : error.message; handleError(errorMessage); } - }); + } dispatch(setBulkSelect([], false)); - handleModalToggle(false); queryClient.invalidateQueries({ queryKey: ["feeds"], }); + handleModalToggle(false); } }; @@ -240,8 +240,8 @@ const IconContainer = ({ allFeeds }: { allFeeds: Feed[] }) => { const feedIdList = []; const data = feedList[i].data; const newFeedName = feedName - ? feedName + "-" + data.name - : "duplicate-" + data.name; + ? `${feedName}-${data.name}` + : `duplicate-${data.name}`; feedIdList.push(data.id); try { const createdFeed: Feed = await cujs.mergeMultipleFeeds( diff --git a/src/components/NodeDetails/PluginTitle.tsx b/src/components/NodeDetails/PluginTitle.tsx index 5c7b37ab4..14713b0b2 100644 --- a/src/components/NodeDetails/PluginTitle.tsx +++ b/src/components/NodeDetails/PluginTitle.tsx @@ -25,7 +25,7 @@ const PluginTitle = () => { setValue(title); }, [selectedPlugin]); - if (selectedPlugin && selectedPlugin.data) { + if (selectedPlugin?.data) { const { title, plugin_version, plugin_name } = selectedPlugin.data; const pluginName = `${ title ? title : `${plugin_name} v.${plugin_version}` @@ -97,9 +97,8 @@ const PluginTitle = () => { )} ); - } else { - return No Plugin was selected; } + return No Plugin was selected; }; export default PluginTitle; diff --git a/src/components/NodeDetails/Status.tsx b/src/components/NodeDetails/Status.tsx index 6a7440223..a39b0c3f9 100644 --- a/src/components/NodeDetails/Status.tsx +++ b/src/components/NodeDetails/Status.tsx @@ -4,8 +4,7 @@ import usePluginInstanceResource from "./usePluginInstanceResource"; const Status = () => { const pluginInstanceResource = usePluginInstanceResource(); - const pluginStatus = - pluginInstanceResource && pluginInstanceResource.pluginStatus; + const pluginStatus = pluginInstanceResource?.pluginStatus; if (pluginStatus && pluginStatus.length > 0) { const items = pluginStatus.map((label: any, index: number) => { @@ -31,7 +30,9 @@ const Status = () => { items={items} /> ); - } else return null; + } + + return null; }; export default Status; diff --git a/src/components/Pipelines/ClipboardCopyCommand.tsx b/src/components/Pipelines/ClipboardCopyCommand.tsx index ab202a8f0..24e7b90f0 100644 --- a/src/components/Pipelines/ClipboardCopyCommand.tsx +++ b/src/components/Pipelines/ClipboardCopyCommand.tsx @@ -14,13 +14,11 @@ const ClipboardCopyCommand = ({ state }: { state: SinglePipeline }) => { const { currentNode, parameterList } = state; const params = parameterList && currentNode && parameterList[currentNode]; - const pluginPiping = - state.pluginPipings && - state.pluginPipings.filter((piping) => { - if (currentNode && piping.data.id === currentNode) { - return piping; - } - }); + const pluginPiping = state?.pluginPipings?.filter((piping) => { + if (currentNode && piping.data.id === currentNode) { + return piping; + } + }); let generatedCommand = ""; diff --git a/src/components/Pipelines/Tree.tsx b/src/components/Pipelines/Tree.tsx index da21a1596..c64dd8691 100644 --- a/src/components/Pipelines/Tree.tsx +++ b/src/components/Pipelines/Tree.tsx @@ -132,11 +132,12 @@ const Tree = (props: TreeProps) => { pluginParameters, currentPipelineId, handleSetCurrentNodeCallback, + pipelinePlugins?.[0], ]); React.useEffect(() => { //@ts-ignore - if (size && size.width) { + if (size?.width) { setTranslate({ //@ts-ignore x: size.width / 2.5, @@ -158,7 +159,7 @@ const Tree = (props: TreeProps) => { const newLinksToAdd: any[] = []; if (tsIds) { - links.forEach((link) => { + for (const link of links) { const targetId = link.target.data.id; const sourceId = link.target.data.id; @@ -174,24 +175,22 @@ const Tree = (props: TreeProps) => { const parents = tsIds[topologicalLink.data.id]; const dict: any = {}; - links && - links.forEach((link) => { - for (let i = 0; i < parents.length; i++) { - if ( - link.source.data.id === parents[i] && - !dict[link.source.data.id] - ) { - dict[link.source.data.id] = link.source; - } else if ( - link.target.data.id === parents[i] && - !dict[link.target.data.id] - ) { - dict[link.target.data.id] = link.target; - } - } - return dict; - }); + for (const link of links) { + for (let i = 0; i < parents.length; i++) { + if ( + link.source.data.id === parents[i] && + !dict[link.source.data.id] + ) { + dict[link.source.data.id] = link.source; + } else if ( + link.target.data.id === parents[i] && + !dict[link.target.data.id] + ) { + dict[link.target.data.id] = link.target; + } + } + } for (const i in dict) { newLinksToAdd.push({ @@ -200,7 +199,7 @@ const Tree = (props: TreeProps) => { }); } } - }); + } } newLinks = [...links, ...newLinksToAdd]; } @@ -211,11 +210,16 @@ const Tree = (props: TreeProps) => { return ( <> -
+
{loading ? ( Fetching Pipeline..... ) : translate.x > 0 && translate.y > 0 ? ( + Feed Tree { return ( ); @@ -291,26 +295,25 @@ const LinkData: React.FC = ({ linkData }) => { const { source, target } = linkData; const drawPath = (ts: boolean) => { - const deltaX = target.x - source.x, - deltaY = target.y - source.y, - dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY), - normX = deltaX / dist, - normY = deltaY / dist, - sourcePadding = nodeRadius, - targetPadding = nodeRadius + 4, - sourceX = source.x + sourcePadding * normX, - sourceY = source.y + sourcePadding * normY, - targetX = target.x - targetPadding * normX, - targetY = target.y - targetPadding * normY; + const deltaX = target.x - source.x; + const deltaY = target.y - source.y; + const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + const normX = deltaX / dist; + const normY = deltaY / dist; + const sourcePadding = nodeRadius; + const targetPadding = nodeRadius + 4; + const sourceX = source.x + sourcePadding * normX; + const sourceY = source.y + sourcePadding * normY; + const targetX = target.x - targetPadding * normX; + const targetY = target.y - targetPadding * normY; if (ts) { return linkVertical()({ source: [sourceX, sourceY], target: [targetX, targetY], }); - } else { - return `M${sourceX} ${sourceY} L${targetX} ${targetY}`; } + return `M${sourceX} ${sourceY} L${targetX} ${targetY}`; }; const ts = target.data.plugin_name === "pl-topologicalcopy"; diff --git a/src/store/user/types.ts b/src/store/user/types.ts index 42d100693..0e57ef249 100644 --- a/src/store/user/types.ts +++ b/src/store/user/types.ts @@ -14,7 +14,7 @@ export interface IUserState { token?: string | null; isRememberMe?: boolean; isLoggedIn?: boolean; - isStaff: boolean; + isStaff?: boolean; } export const UserActionTypes = keyMirror({ diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 7a31302c2..c329a683b 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -5,10 +5,6 @@ declare module "@patternfly/react-log-viewer"; declare module "cornerstone-core"; declare module "cornerstone-tools"; declare module "xtk"; -declare module "cornerstone-math"; -declare module "cornerstone-file-image-loader"; -declare module "cornerstone-wado-image-loader"; -declare module "cornerstone-nifti-image-loader"; declare module "dicom-parser"; declare module "rusha"; declare module "hammerjs"; diff --git a/vite.config.ts b/vite.config.ts index 79d4070ed..3dc74396d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,23 +7,25 @@ import IstanbulPlugin from "vite-plugin-istanbul"; export default defineConfig({ plugins: [ react(), - macrosPlugin(), // used for getting version string - ...(process.env.USE_BABEL_PLUGIN_ISTANBUL ? [IstanbulPlugin({ - include: "src/*", - exclude: [ "node_modules", "test/" ], - extension: [ ".js", ".ts", ".tsx" ], - })] : []) + macrosPlugin(), // used for getting version string + ...(process.env.USE_BABEL_PLUGIN_ISTANBUL + ? [ + IstanbulPlugin({ + include: "src/*", + exclude: ["node_modules", "test/"], + extension: [".js", ".ts", ".tsx"], + }), + ] + : []), ], build: { - sourcemap: !!process.env.USE_BABEL_PLUGIN_ISTANBUL + sourcemap: !!process.env.USE_BABEL_PLUGIN_ISTANBUL, }, resolve: { alias: { - - // workaround for "Cornerstone3D tools does not build with vite" // https://github.com/cornerstonejs/cornerstone3D/issues/1071 - "@cornerstonejs/tools": "@cornerstonejs/tools/dist/umd/index.js" + "@cornerstonejs/tools": "@cornerstonejs/tools/dist/umd/index.js", }, }, }); From 231b3577e3a83bcedb71d5199f9408dba8f70951 Mon Sep 17 00:00:00 2001 From: PintoGideon Date: Mon, 26 Feb 2024 17:32:02 -0500 Subject: [PATCH 2/4] Update to the Pipelines Component --- package-lock.json | 108 +++--- package.json | 2 +- src/api/common.ts | 80 +++-- src/components/AddPipeline/AddPipeline.tsx | 203 +++++------ src/components/CreateFeed/ChooseConfig.tsx | 34 +- src/components/CreateFeed/CreateFeed.tsx | 14 +- src/components/CreateFeed/Pipelines.tsx | 218 ++++++++---- src/components/CreateFeed/createFeedHelper.ts | 10 +- src/components/CreateFeed/types/pipeline.ts | 6 +- src/components/FeedTree/FeedTree.tsx | 69 ++-- src/components/FeedTree/Marker.tsx | 4 +- src/components/FeedTree/Node.tsx | 24 +- src/components/FeedTree/ParentComponent.tsx | 2 +- src/components/FeedTree/data.ts | 43 ++- src/components/Feeds/FeedListView.tsx | 48 +-- src/components/Feeds/FeedView.tsx | 34 +- src/components/NodeDetails/NodeDetails.tsx | 38 +-- .../Pipelines/ConfigurationPage.tsx | 98 +++--- src/components/Pipelines/GeneralCompute.tsx | 12 +- src/components/Pipelines/ListCompute.tsx | 75 +++-- src/components/Pipelines/NodeData.tsx | 30 +- src/components/Pipelines/TitleChange.tsx | 5 +- src/components/Pipelines/Tree.tsx | 68 ++-- src/components/Pipelines/UploadJson.tsx | 2 +- .../PipelinesCopy/CodeBlockComponent.tsx | 158 +++++++++ .../ComputeListForSingleCompute.tsx | 49 +++ .../PipelinesCopy/GeneralCompute.tsx | 65 ++++ src/components/PipelinesCopy/ListCompute.tsx | 66 ++++ src/components/PipelinesCopy/NodeData.tsx | 173 ++++++++++ .../PipelinesCopy/Pipelines.module.css | 4 + .../PipelinesCopy/PipelinesComponent.tsx | 42 +++ src/components/PipelinesCopy/TitleChange.tsx | 78 +++++ src/components/PipelinesCopy/Tree.tsx | 317 ++++++++++++++++++ src/components/PipelinesCopy/context.tsx | 232 +++++++++++++ src/components/PipelinesCopy/index.tsx | 170 ++++++++++ 35 files changed, 2023 insertions(+), 558 deletions(-) create mode 100644 src/components/PipelinesCopy/CodeBlockComponent.tsx create mode 100644 src/components/PipelinesCopy/ComputeListForSingleCompute.tsx create mode 100644 src/components/PipelinesCopy/GeneralCompute.tsx create mode 100644 src/components/PipelinesCopy/ListCompute.tsx create mode 100644 src/components/PipelinesCopy/NodeData.tsx create mode 100644 src/components/PipelinesCopy/Pipelines.module.css create mode 100644 src/components/PipelinesCopy/PipelinesComponent.tsx create mode 100644 src/components/PipelinesCopy/TitleChange.tsx create mode 100644 src/components/PipelinesCopy/Tree.tsx create mode 100644 src/components/PipelinesCopy/context.tsx create mode 100644 src/components/PipelinesCopy/index.tsx diff --git a/package-lock.json b/package-lock.json index d2ad030f1..402126156 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@patternfly/react-table": "^5.2.0", "@react-hook/resize-observer": "^1.2.6", "@tanstack/react-query": "^5.17.15", - "antd": "^5.13.2", + "antd": "^5.14.1", "axios": "^1.6.5", "chris-utility": "^1.1.6", "d3-hierarchy": "^1.1.9", @@ -116,9 +116,9 @@ } }, "node_modules/@ant-design/cssinjs": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.18.2.tgz", - "integrity": "sha512-514V9rjLaFYb3v4s55/8bg2E6fb81b99s3crDZf4nSwtiDLLXs8axnIph+q2TVkY2hbJPZOn/cVsVcnLkzFy7w==", + "version": "1.18.4", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.18.4.tgz", + "integrity": "sha512-IrUAOj5TYuMG556C9gdbFuOrigyhzhU5ZYpWb3gYTxAwymVqRbvLzFCZg6OsjLBR6GhzcxYF3AhxKmjB+rA2xA==", "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", @@ -134,12 +134,12 @@ } }, "node_modules/@ant-design/icons": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.2.6.tgz", - "integrity": "sha512-4wn0WShF43TrggskBJPRqCD0fcHbzTYjnaoskdiJrVHg86yxoZ8ZUqsXvyn4WUqehRiFKnaclOhqk9w4Ui2KVw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.3.0.tgz", + "integrity": "sha512-69FgBsIkeCjw72ZU3fJpqjhmLCPrzKGEllbrAZK7MUdt1BrKsyG6A8YDCBPKea27UQ0tRXi33PcjR4tp/tEXMg==", "dependencies": { "@ant-design/colors": "^7.0.0", - "@ant-design/icons-svg": "^4.3.0", + "@ant-design/icons-svg": "^4.4.0", "@babel/runtime": "^7.11.2", "classnames": "^2.2.6", "rc-util": "^5.31.1" @@ -153,9 +153,9 @@ } }, "node_modules/@ant-design/icons-svg": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.3.1.tgz", - "integrity": "sha512-4QBZg8ccyC6LPIRii7A0bZUk3+lEDCLnhB+FVsflGdcWPPmV+j3fire4AwwoqHV/BibgvBmR9ZIo4s867smv+g==" + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==" }, "node_modules/@ant-design/react-slick": { "version": "1.0.2", @@ -1734,9 +1734,9 @@ } }, "node_modules/@rc-component/color-picker": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-1.5.1.tgz", - "integrity": "sha512-onyAFhWKXuG4P162xE+7IgaJkPkwM94XlOYnQuu69XdXWMfxpeFi6tpJBsieIMV7EnyLV5J3lDzdLiFeK0iEBA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-1.5.2.tgz", + "integrity": "sha512-YJXujYzYFAEtlXJXy0yJUhwzUWPTcniBZto+wZ/vnACmFnUTNR7dH+NOeqSwMMsssh74e9H5Jfpr5LAH2PYqUw==", "dependencies": { "@babel/runtime": "^7.23.6", "@ctrl/tinycolor": "^3.6.1", @@ -1807,9 +1807,9 @@ } }, "node_modules/@rc-component/tour": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.12.2.tgz", - "integrity": "sha512-2he76Iwf0cVchI70dHCowR5DCWpPRY9+foNoO1h+TD2cZbsGSoEk+m3jEaFPh4ChXYhdzsxp+0siz8/br91JhA==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.12.3.tgz", + "integrity": "sha512-U4mf1FiUxGCwrX4ed8op77Y8VKur+8Y/61ylxtqGbcSoh1EBC7bWd/DkLu0ClTUrKZInqEi1FL7YgFtnT90vHA==", "dependencies": { "@babel/runtime": "^7.18.0", "@rc-component/portal": "^1.0.0-9", @@ -1826,9 +1826,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-1.18.2.tgz", - "integrity": "sha512-jRLYgFgjLEPq3MvS87fIhcfuywFSRDaDrYw1FLku7Cm4esszvzTbA0JBsyacAyLrK9rF3TiHFcvoEDMzoD3CTA==", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-1.18.3.tgz", + "integrity": "sha512-Ksr25pXreYe1gX6ayZ1jLrOrl9OAUHUqnuhEx6MeHnNa1zVM5Y2Aj3Q35UrER0ns8D2cJYtmJtVli+i+4eKrvA==", "dependencies": { "@babel/runtime": "^7.23.2", "@rc-component/portal": "^1.1.0", @@ -2867,24 +2867,24 @@ } }, "node_modules/antd": { - "version": "5.13.2", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.13.2.tgz", - "integrity": "sha512-P+N8gc0NOPy2WqJj/57Ey3dZUmb7nEUwAM+CIJaR5SOEjZnhEtMGRJSt+3lnhJ3MNRR39aR6NYkRVp2mYfphiA==", + "version": "5.14.1", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.14.1.tgz", + "integrity": "sha512-P0Bwt9NKSZqnEJ0QAyAb13ay34FjOKsz+KEp/ts+feYsynhUxF7/Ay6d1jS6ZcNpcs+JWTlLKO59YFZ3tX07wQ==", "dependencies": { "@ant-design/colors": "^7.0.2", - "@ant-design/cssinjs": "^1.18.2", - "@ant-design/icons": "^5.2.6", + "@ant-design/cssinjs": "^1.18.4", + "@ant-design/icons": "^5.3.0", "@ant-design/react-slick": "~1.0.2", "@ctrl/tinycolor": "^3.6.1", "@rc-component/color-picker": "~1.5.1", "@rc-component/mutate-observer": "^1.1.0", - "@rc-component/tour": "~1.12.2", - "@rc-component/trigger": "^1.18.2", + "@rc-component/tour": "~1.12.3", + "@rc-component/trigger": "^1.18.3", "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.10", "qrcode.react": "^3.1.0", - "rc-cascader": "~3.21.0", + "rc-cascader": "~3.21.2", "rc-checkbox": "~3.1.0", "rc-collapse": "~3.7.2", "rc-dialog": "~9.3.4", @@ -2893,26 +2893,26 @@ "rc-field-form": "~1.41.0", "rc-image": "~7.5.1", "rc-input": "~1.4.3", - "rc-input-number": "~8.6.1", + "rc-input-number": "~9.0.0", "rc-mentions": "~2.10.1", "rc-menu": "~9.12.4", "rc-motion": "^2.9.0", "rc-notification": "~5.3.0", "rc-pagination": "~4.0.4", - "rc-picker": "~3.14.6", + "rc-picker": "~4.1.1", "rc-progress": "~3.5.1", "rc-rate": "~2.12.0", "rc-resize-observer": "^1.4.0", - "rc-segmented": "~2.2.2", + "rc-segmented": "~2.3.0", "rc-select": "~14.11.0", "rc-slider": "~10.5.0", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", - "rc-table": "~7.37.0", + "rc-table": "~7.39.0", "rc-tabs": "~14.0.0", "rc-textarea": "~1.6.3", "rc-tooltip": "~6.1.3", - "rc-tree": "~5.8.2", + "rc-tree": "~5.8.5", "rc-tree-select": "~5.17.0", "rc-upload": "~4.5.2", "rc-util": "^5.38.1", @@ -9050,14 +9050,14 @@ } }, "node_modules/rc-cascader": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.21.0.tgz", - "integrity": "sha512-7aADjbfqiR4HrTHG9S019p2jeKM/AxISPA5+sBJR7Mlhm/i+lR7VjBju3KQulJNJLKNEnQYg4TFhcPf2SLua9g==", + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.21.2.tgz", + "integrity": "sha512-J7GozpgsLaOtzfIHFJFuh4oFY0ePb1w10twqK6is3pAkqHkca/PsokbDr822KIRZ8/CK8CqevxohuPDVZ1RO/A==", "dependencies": { "@babel/runtime": "^7.12.5", "array-tree-filter": "^2.1.0", "classnames": "^2.3.1", - "rc-select": "~14.11.0-0", + "rc-select": "~14.11.0", "rc-tree": "~5.8.1", "rc-util": "^5.37.0" }, @@ -9191,9 +9191,9 @@ } }, "node_modules/rc-input-number": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-8.6.1.tgz", - "integrity": "sha512-gaAMUKtUKLktJ3Yx93tjgYY1M0HunnoqzPEqkb9//Ydup4DcG0TFL9yHBA3pgVdNIt5f0UWyHCgFBj//JxeD6A==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.0.0.tgz", + "integrity": "sha512-RfcDBDdWFFetouWFXBA+WPEC8LzBXyngr9b+yTLVIygfFu7HiLRGn/s/v9wwno94X7KFvnb28FNynMGj9XJlDQ==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/mini-decimal": "^1.0.1", @@ -9303,14 +9303,16 @@ } }, "node_modules/rc-picker": { - "version": "3.14.6", - "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-3.14.6.tgz", - "integrity": "sha512-AdKKW0AqMwZsKvIpwUWDUnpuGKZVrbxVTZTNjcO+pViGkjC1EBcjMgxVe8tomOEaIHJL5Gd13vS8Rr3zzxWmag==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.1.4.tgz", + "integrity": "sha512-sAdgj1kW9wvuoS5p2Zw3pT52iUYxidYaqXVLooaKxTqgYbhe8cG8Ld3b8cgwYfKrIkm/j+qp9nDQlrFPSl16lQ==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/trigger": "^1.5.0", "classnames": "^2.2.1", - "rc-util": "^5.30.0" + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.38.1" }, "engines": { "node": ">=8.x" @@ -9385,9 +9387,9 @@ } }, "node_modules/rc-segmented": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.2.2.tgz", - "integrity": "sha512-Mq52M96QdHMsNdE/042ibT5vkcGcD5jxKp7HgPC2SRofpia99P5fkfHy1pEaajLMF/kj0+2Lkq1UZRvqzo9mSA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.3.0.tgz", + "integrity": "sha512-I3FtM5Smua/ESXutFfb8gJ8ZPcvFR+qUgeeGFQHBOvRiRKyAk4aBE5nfqrxXx+h8/vn60DQjOt6i4RNtrbOobg==", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", @@ -9469,9 +9471,9 @@ } }, "node_modules/rc-table": { - "version": "7.37.0", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.37.0.tgz", - "integrity": "sha512-hEB17ktLRVfVmdo+U8MjGr+PuIgdQ8Cxj/N5lwMvP/Az7TOrQxwTMLVEDoj207tyPYLTWifHIF9EJREWwyk67g==", + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.39.0.tgz", + "integrity": "sha512-7fHLMNsm/2DlGwyIMkdH2xIeRzb5I69bLsFaEVtX+gqmGhByy0wtOAgHkiOew3PtXozSJyh+iXifjLgQzWdczw==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/context": "^1.4.0", @@ -9540,9 +9542,9 @@ } }, "node_modules/rc-tree": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.8.2.tgz", - "integrity": "sha512-xH/fcgLHWTLmrSuNphU8XAqV7CdaOQgm4KywlLGNoTMhDAcNR3GVNP6cZzb0GrKmIZ9yae+QLot/cAgUdPRMzg==", + "version": "5.8.5", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.8.5.tgz", + "integrity": "sha512-PRfcZtVDNkR7oh26RuNe1hpw11c1wfgzwmPFL0lnxGnYefe9lDAO6cg5wJKIAwyXFVt5zHgpjYmaz0CPy1ZtKg==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", diff --git a/package.json b/package.json index d3ecc613c..646c0ae62 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@patternfly/react-table": "^5.2.0", "@react-hook/resize-observer": "^1.2.6", "@tanstack/react-query": "^5.17.15", - "antd": "^5.13.2", + "antd": "^5.14.1", "axios": "^1.6.5", "chris-utility": "^1.1.6", "d3-hierarchy": "^1.1.9", diff --git a/src/api/common.ts b/src/api/common.ts index 0c7d4e17f..78cebe2f6 100644 --- a/src/api/common.ts +++ b/src/api/common.ts @@ -6,7 +6,9 @@ import { type PipelineList, PluginPiping, Feed, - PipelineInstance, + Plugin, + PipelinePipingDefaultParameterList, + ComputeResource, } from "@fnndsc/chrisapi"; export function useSafeDispatch(dispatch: any) { @@ -119,7 +121,7 @@ async function fetchResource( } return { resource, - totalCount: resourceList.totalCount, + totalCount: resourceList.totalCount as number, }; } @@ -185,13 +187,6 @@ export const fetchPipelines = async ( search: string, searchType: string, ) => { - let errorPayload: { - error_message: string; - } = { - error_message: "", - }; - let registeredPipelinesList: PipelineList; - let registeredPipelines: PipelineInstance[]; const offset = perPage * (page - 1); const client = ChrisAPIClient.getClient(); const params = { @@ -200,17 +195,17 @@ export const fetchPipelines = async ( [`${searchType}`]: search, }; try { - registeredPipelinesList = await client.getPipelines(params); - registeredPipelines = - (registeredPipelinesList.getItems() as PipelineInstance[]) || []; + const registeredPipelinesList: PipelineList = + await client.getPipelines(params); + const registeredPipelines = + (registeredPipelinesList.getItems() as Pipeline[]) || []; return { registeredPipelines, - registeredPipelinesList, - error: errorPayload, + totalCount: registeredPipelinesList.totalCount, }; } catch (error) { const errorObj = catchError(error); - errorPayload = errorObj; + throw new Error(errorObj.error_message); } }; @@ -228,13 +223,12 @@ export async function fetchResources(pipelineInstance: Pipeline) { params, boundPipelineFn, ); - const { resource: pipelinePlugins } = await fetchResource( - params, - boundPipelinePluginFn, - ); - const parameters = await pipelineInstance.getDefaultParameters({ - limit: 1000, - }); + const { resource: pipelinePlugins }: { resource: Plugin[] } = + await fetchResource(params, boundPipelinePluginFn); + const parameters: PipelinePipingDefaultParameterList = + await pipelineInstance.getDefaultParameters({ + limit: 1000, + }); return { parameters, @@ -271,33 +265,37 @@ export const generatePipelineWithData = async (data: any) => { export async function fetchComputeInfo( plugin_id: number, - dictionary_id: number, + dictionary_id: string, ) { - const client = ChrisAPIClient.getClient(); - const computeEnvs = await client.getComputeResources({ - plugin_id: `${plugin_id}`, - }); - - if (computeEnvs.getItems()) { - const computeEnvData = { - [dictionary_id]: { - computeEnvs: computeEnvs.data, - currentlySelected: computeEnvs.data[0].name, - }, - }; - return computeEnvData; + try { + const client = ChrisAPIClient.getClient(); + const computeEnvs = await client.getComputeResources({ + plugin_id: `${plugin_id}`, + }); + + if (computeEnvs.getItems()) { + const computeEnvData = { + [dictionary_id]: { + computeEnvs: computeEnvs.getItems() as ComputeResource[], + currentlySelected: computeEnvs.data[0].name as string, + }, + }; + return computeEnvData; + } + } catch (e) { + throw new Error("Error fetching the compoute Environment"); } - return undefined; } export function catchError(errorRequest: any) { if (errorRequest.response) { return { error_message: errorRequest.response.data as string }; - } else if (errorRequest.message) { + } + + if (errorRequest.message) { return { error_message: errorRequest.message as string }; - } else { - return { error_message: errorRequest as string }; } + return { error_message: errorRequest as string }; } // A function to limit concurrency using Promise.allSettled. @@ -386,7 +384,7 @@ export const uploadWrapper = ( const url = `${import.meta.env.VITE_CHRIS_UI_URL}uploadedfiles/`; return localFiles.map((file) => { const onUploadProgressWrap = (progressEvent: AxiosProgressEvent) => { - onUploadProgress && onUploadProgress(file, progressEvent); + onUploadProgress?.(file, progressEvent); }; const promise = uploadFile( diff --git a/src/components/AddPipeline/AddPipeline.tsx b/src/components/AddPipeline/AddPipeline.tsx index 5a94416b8..4545f07b1 100644 --- a/src/components/AddPipeline/AddPipeline.tsx +++ b/src/components/AddPipeline/AddPipeline.tsx @@ -1,120 +1,118 @@ -import React from "react"; -import { useDispatch } from "react-redux"; import { Button, Modal, ModalVariant } from "@patternfly/react-core"; -import { PlusButtonIcon } from "../../icons"; -import PipelineContainer from "../CreateFeed/PipelineContainter"; -import { PipelineContext } from "../CreateFeed/context"; -import { ErrorAlert } from "../Common"; +import { useMutation } from "@tanstack/react-query"; +import { Alert } from "antd"; +import React, { useContext } from "react"; +import { useDispatch } from "react-redux"; import ChrisAPIClient from "../../api/chrisapiclient"; +import { PlusButtonIcon } from "../../icons"; import { useTypedSelector } from "../../store/hooks"; +import { getNodeOperations } from "../../store/plugin/actions"; import { - getSelectedPlugin, getPluginInstancesSuccess, + getSelectedPlugin, } from "../../store/pluginInstance/actions"; import { getPluginInstanceStatusRequest } from "../../store/resources/actions"; -import { PipelineTypes } from "../CreateFeed/types/pipeline"; -import { getNodeOperations } from "../../store/plugin/actions"; +import { SpinContainer } from "../Common"; +import Pipelines from "../PipelinesCopy"; +import { PipelineContext, Types } from "../PipelinesCopy/context"; const AddPipeline = () => { + const { state, dispatch } = useContext(PipelineContext); + const { pipelineToAdd, selectedPipeline, computeInfo, titleInfo } = state; const reactDispatch = useDispatch(); - - const feed = useTypedSelector((state) => state.feed.currentFeed.data); - const { selectedPlugin } = useTypedSelector((state) => state.instance); const { childPipeline } = useTypedSelector( (state) => state.plugin.nodeOperations, ); - const { state, dispatch: pipelineDispatch } = - React.useContext(PipelineContext); - const { pipelineData, selectedPipeline } = state; - const [error, setError] = React.useState({}); + + const alreadyAvailableInstances = useTypedSelector( + (state) => state.instance.pluginInstances.data, + ); const handleToggle = () => { - setError({}); + if (childPipeline) { + dispatch({ + type: Types.ResetState, + }); + mutation.reset(); + } reactDispatch(getNodeOperations("childPipeline")); }; - React.useEffect(() => { - const el = document.querySelector(".react-json-view"); - - if (el) { - //@ts-ignore - el!.scrollIntoView({ block: "center", behavior: "smooth" }); - } - }); + const feed = useTypedSelector((state) => state.feed.currentFeed.data); + const { selectedPlugin } = useTypedSelector((state) => state.instance); const addPipeline = async () => { - setError({}); - if (selectedPlugin && selectedPipeline && feed) { - setError({}); - const { - pluginPipings, - pipelinePlugins, - pluginParameters, - computeEnvs, - parameterList, - title, - } = pipelineData[selectedPipeline]; - - if (pluginPipings && pluginParameters && pipelinePlugins) { - const client = ChrisAPIClient.getClient(); - try { - const nodes_info = client.computeWorkflowNodesInfo( - //@ts-ignore - pluginParameters.data, - ); - nodes_info.forEach((node) => { - if (computeEnvs && computeEnvs[node["piping_id"]]) { - const compute_node = - computeEnvs[node["piping_id"]]["currentlySelected"]; - - const titleChange = title && title[node["piping_id"]]; - if (titleChange) { - node.title = titleChange; - } - if (compute_node) { - node.compute_resource_name = compute_node; - } - } - - if (parameterList && parameterList[node["piping_id"]]) { - const params = parameterList[node["piping_id"]]; - node["plugin_parameter_defaults"] = params; - } - }); - await client.createWorkflow(selectedPipeline, { - previous_plugin_inst_id: selectedPlugin.data.id, - nodes_info: JSON.stringify(nodes_info), - }); - - pipelineDispatch({ - type: PipelineTypes.ResetState, - }); - - const data = await feed.getPluginInstances({ - limit: 1000, - }); - if (data.getItems()) { - const instanceList = data.getItems(); - const firstInstance = instanceList && instanceList[0]; - reactDispatch(getSelectedPlugin(firstInstance)); - if (instanceList) { - const pluginInstanceObj = { - selected: firstInstance, - pluginInstances: instanceList, - }; - reactDispatch(getPluginInstancesSuccess(pluginInstanceObj)); - reactDispatch(getPluginInstanceStatusRequest(pluginInstanceObj)); - } + const id = pipelineToAdd?.data.id; + const resources = selectedPipeline?.[id]; + + if (selectedPlugin && feed && resources) { + const { parameters } = resources; + const client = ChrisAPIClient.getClient(); + + try { + const nodes_info = client.computeWorkflowNodesInfo(parameters.data); + + for (const node of nodes_info) { + // Set compute info + const activeNode = computeInfo?.[id][node.piping_id]; + // Set Title + const titleSet = titleInfo?.[id][node.piping_id]; + + if (activeNode) { + const compute_node = activeNode.currentlySelected; + node.compute_resource_name = compute_node; + } + + if (titleSet) { + node.title = titleSet; } - handleToggle(); - } catch (error: any) { - const errorMessage = error.response.data || error.message; - setError(errorMessage); } + + const workflow = await client.createWorkflow(id, { + previous_plugin_inst_id: selectedPlugin.data.id, + nodes_info: JSON.stringify(nodes_info), + }); + + const pluginInstances = await workflow.getPluginInstances({ + limit: 1000, + }); + const instanceItems = pluginInstances.getItems(); + if (instanceItems && alreadyAvailableInstances) { + const firstInstance = instanceItems[instanceItems.length - 1]; + const completeList = [...alreadyAvailableInstances, ...instanceItems]; + reactDispatch(getSelectedPlugin(firstInstance)); + const pluginInstanceObj = { + selected: firstInstance, + pluginInstances: completeList, + }; + reactDispatch(getPluginInstancesSuccess(pluginInstanceObj)); + reactDispatch(getPluginInstanceStatusRequest(pluginInstanceObj)); + } + + // biome-ignore lint/suspicious/noExplicitAny: + } catch (e: any) { + throw new Error(e.message ? e.message : e); } } }; + const mutation = useMutation({ + mutationFn: () => addPipeline(), + }); + + React.useEffect(() => { + const el = document.querySelector("#indicators"); + + if (el) { + el.scrollIntoView({ block: "center", behavior: "smooth" }); + } + }); + + const isButtonDisabled = + pipelineToAdd && computeInfo?.[pipelineToAdd.data.id] && !mutation.isPending + ? false + : true; + return ( } onClick={handleToggle} type="button"> @@ -128,10 +126,10 @@ const AddPipeline = () => { description="Add a Pipeline to the plugin instance" actions={[ , @@ -140,12 +138,19 @@ const AddPipeline = () => { , ]} > - -
- {Object.keys(error).length > 0 && ( - setError({})} errors={error} /> - )} -
+ + + {mutation.isError || mutation.isSuccess || mutation.isPending ? ( +
+ {mutation.isError && ( + + )} + {mutation.isSuccess && ( + + )} + {mutation.isPending && } +
+ ) : null} ); diff --git a/src/components/CreateFeed/ChooseConfig.tsx b/src/components/CreateFeed/ChooseConfig.tsx index 96df75af4..831a96945 100644 --- a/src/components/CreateFeed/ChooseConfig.tsx +++ b/src/components/CreateFeed/ChooseConfig.tsx @@ -1,41 +1,39 @@ -import React, { useCallback, useContext, useEffect, useState } from "react"; import { + Button, Card, CardBody, CardHeader, - Drawer, CardTitle, Chip, + Drawer, DrawerActions, DrawerContent, DrawerHead, DrawerPanelBody, DrawerPanelContent, + Flex, + FlexItem, Grid, GridItem, Tooltip, - Button, useWizardContext, - Flex, - FlexItem, } from "@patternfly/react-core"; -import { Steps, notification, Alert } from "antd"; +import SettingsIcon from "@patternfly/react-icons/dist/esm/icons/cogs-icon"; import TrashIcon from "@patternfly/react-icons/dist/esm/icons/trash-icon"; import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon"; -import SettingsIcon from "@patternfly/react-icons/dist/esm/icons/cogs-icon"; - -import { CreateFeedContext } from "./context"; -import LocalFileUpload from "./LocalFileUpload"; +import { Alert, Steps, notification } from "antd"; +import React, { useCallback, useContext, useEffect, useState } from "react"; +import { useTypedSelector } from "../../store/hooks"; +import GuidedConfig from "../AddNode/GuidedConfig"; +import { AddNodeContext } from "../AddNode/context"; +import { Types as AddNodeTypes, chooseConfigProps } from "../AddNode/types"; import DragAndUpload from "../DragFileUpload"; import ChrisFileSelect from "./ChrisFileSelect"; import DataPacks from "./DataPacks"; -import GuidedConfig from "../AddNode/GuidedConfig"; -import { chooseConfigProps } from "../AddNode/types"; import { FileList } from "./HelperComponent"; +import LocalFileUpload from "./LocalFileUpload"; +import { CreateFeedContext } from "./context"; import { Types } from "./types/feed"; -import { AddNodeContext } from "../AddNode/context"; -import { Types as AddNodeTypes } from "../AddNode/types"; -import { useTypedSelector } from "../../store/hooks"; const ChooseConfig = ({ handleFileUpload, @@ -86,11 +84,11 @@ const ChooseConfig = ({ if ( selectedConfig.includes("fs_plugin") && params?.required.length != Object.keys(requiredInput).length - ) + ) { return; - else { - onNext(); } + + onNext(); break; case "ArrowLeft": onBack(); diff --git a/src/components/CreateFeed/CreateFeed.tsx b/src/components/CreateFeed/CreateFeed.tsx index e891e6b9e..137410357 100644 --- a/src/components/CreateFeed/CreateFeed.tsx +++ b/src/components/CreateFeed/CreateFeed.tsx @@ -17,7 +17,7 @@ import { Types } from "./types/feed"; import { PipelineTypes } from "./types/pipeline"; import BasicInformation from "./BasicInformation"; import ChooseConfig from "./ChooseConfig"; -import PipelineContainer from "./PipelineContainter"; +import PipelinesCopy from "../PipelinesCopy"; import Review from "./Review"; import withSelectionAlert from "./SelectionAlert"; import { useTypedSelector } from "../../store/hooks"; @@ -167,11 +167,11 @@ export default function CreateFeed() { [handleDispatch], ); - const allRequiredFieldsNotEmpty: boolean = - selectedConfig.includes("fs_plugin") && - Object.keys(requiredInput).length > 0 - ? true - : false; + const allRequiredFieldsNotEmpty: boolean = selectedConfig.includes( + "fs_plugin", + ) + ? true + : false; const filesChoosen = data.chrisFiles.length > 0 || data.localFiles.length > 0; @@ -251,7 +251,7 @@ export default function CreateFeed() { )} - + state.user.isStaff); const { pipelineData, selectedPipeline, pipelines } = state; const [errors, setErrors] = React.useState({}); - const { goToNextStep: onNext, goToPrevStep: onBack } = - useContext(WizardContext); const [pageState, setPageState] = React.useState({ page: 1, @@ -135,9 +121,6 @@ const Pipelines = ({ }); } - if (data?.error.error_message) { - throw new Error(data?.error.error_message); - } return data; }, enabled: true, @@ -227,11 +210,13 @@ const Pipelines = ({ handlePipelineSecondaryResource, pipelineData, selectedPipeline, + pipelineData[pipeline?.data.id], ], ); const handleOnExpand = useCallback( - async (pipeline: Pipeline) => { + (pipeline: Pipeline) => { + console.log("Pipeline", pipeline, selectedPipeline); if (!(selectedPipeline === pipeline.data.id)) { handlePipelineSecondaryResource(pipeline); } else { @@ -260,41 +245,10 @@ const Pipelines = ({ handleCleanResources, handlePipelineSecondaryResource, selectedPipeline, - state.pipelineData, + state.pipelineData[pipeline?.data.id], ], ); - const handleKeyDown = useCallback( - (e: any, pipeline: Pipeline) => { - if ( - e.code === "Enter" && - e.target.closest("DIV.pf-c-data-list__toggle") - ) { - e.preventDefaut(); - handleOnExpand(pipeline); - } - }, - [handleOnExpand], - ); - - const handleBrowserKeyDown = useCallback( - (e: any) => { - if (e.code === "ArrowLeft") { - onBack(); - } else if (e.code === "ArrowRight") { - onNext(); - } - }, - [onBack, onNext], - ); - - useEffect(() => { - window.addEventListener("keydown", handleBrowserKeyDown); - return () => { - window.removeEventListener("keydown", handleBrowserKeyDown); - }; - }, [handleBrowserKeyDown]); - const onToggle = () => { setIsDropdownOpen(!isDropdownOpen); }; @@ -372,6 +326,135 @@ const Pipelines = ({ onPerPageSelect={onPerPageSelect} />
+ + {isError && ( + {error.message}
} /> + )} + + {isLoading ? ( + + ) : pipelines.length > 0 ? ( + pipelines.map((pipeline: Pipeline) => { + return ( + { + handleOnExpand(pipeline); + }} + key={pipeline.data.id} + items={[ + { + key: pipeline.data.id, + label: ( +
+
+ {pipeline.data.name} +
+ +
+ + {showDelete && ( + + )} +
+
+ ), + children: + (expanded?.[pipeline.data.id] || + state.pipelineData[pipeline.data.id]) && + !isResourcesLoading && + !isResourceError ? ( + <> +
+ + + +
+ + + + ) : ( + + ), + }, + ]} + /> + ); + }) + ) : ( + + )} + + {/* + {isError && ( {error.message}} /> @@ -428,7 +511,7 @@ const Pipelines = ({ : "Select"} )} - {/*Hardcode to only allow the user 'chris' to delete the pipelines*/} + {/*Hardcode to only allow the user 'chris' to delete the pipelines} {showDelete && ( + + + {error && } + + ); +} + +export default TitleChange; diff --git a/src/components/PipelinesCopy/Tree.tsx b/src/components/PipelinesCopy/Tree.tsx new file mode 100644 index 000000000..c56951cf8 --- /dev/null +++ b/src/components/PipelinesCopy/Tree.tsx @@ -0,0 +1,317 @@ +import { Pipeline } from "@fnndsc/chrisapi"; +import { hierarchy, tree } from "d3-hierarchy"; +import { event, select } from "d3-selection"; +import { linkVertical } from "d3-shape"; +import { zoom as d3Zoom, zoomIdentity } from "d3-zoom"; +import React, { + Fragment, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { TreeNode, getFeedTree } from "../../api/common"; +import { EmptyStateComponent, SpinContainer } from "../Common"; +import { ThemeContext } from "../DarkTheme/useTheme"; +import TransitionGroupWrapper from "../FeedTree/TransitionGroupWrapper"; +import { + getTsNodesWithPipings, + type Point, + type Separation, +} from "../FeedTree/data"; +import useSize from "../FeedTree/useSize"; +import NodeData from "./NodeData"; +import { PipelineContext } from "./context"; + +const nodeSize = { x: 200, y: 80 }; +const svgClassName = "feed-tree__svg"; +const graphClassName = "feed-tree__graph"; +const scale = 1; + +export interface TreeProps { + translate?: Point; + scaleExtent: { + min: number; + max: number; + }; + zoom?: number; + nodeSize?: { + x: number; + y: number; + }; + separation?: Separation; + orientation?: "horizontal" | "vertical"; + currentPipeline: Pipeline; +} + +const Tree = (props: TreeProps) => { + const { currentPipeline } = props; + const { state } = React.useContext(PipelineContext); + const { selectedPipeline } = state; + const divRef = useRef(null); + const [translate, setTranslate] = React.useState({ + x: 0, + y: 0, + }); + const size = useSize(divRef); + const [loading, setLoading] = React.useState(false); + const [data, setData] = React.useState(); + const [tsIds, setTsIds] = React.useState(); + const { zoom, scaleExtent } = props; + const bindZoomListener = React.useCallback(() => { + const svg = select(`.${svgClassName}`); + const g = select(`.${graphClassName}`); + + svg.call( + ///@ts-ignore + d3Zoom().transform, + zoomIdentity.translate(translate.x, translate.y).scale(zoom), + ); + + svg.call( + //@ts-ignore + d3Zoom() + .scaleExtent([scaleExtent.min, scaleExtent.max]) + .on("zoom", () => { + g.attr("transform", event.transform); + }), + ); + }, [zoom, scaleExtent, translate.x, translate.y]); + + React.useEffect(() => { + bindZoomListener(); + }, [bindZoomListener]); + + React.useEffect(() => { + if (selectedPipeline) { + const { pluginPipings, parameters } = + selectedPipeline[currentPipeline.data.id]; + setLoading(true); + const tree = getFeedTree(pluginPipings); + getTsNodesWithPipings(pluginPipings, parameters).then((tsIds) => { + setTsIds(tsIds); + }); + setData(tree); + setLoading(false); + } + }, [selectedPipeline?.[currentPipeline.data.id]]); + + React.useEffect(() => { + //@ts-ignore + if (size?.width) { + setTranslate({ + //@ts-ignore + x: size.width / 2.5, + //@ts-ignore + y: size.height / 3, + }); + } + }, [size]); + + React.useEffect(() => { + const svgElement = document.querySelector(".feed-tree__svg"); + + if (svgElement && divRef.current) { + const svgHeight = svgElement.getBoundingClientRect().height; + divRef.current.style.height = `${svgHeight}px`; + } + }, []); + + const generateTree = () => { + const d3Tree = tree().nodeSize([nodeSize.x, nodeSize.y]); + let nodes; + let links: any[] = []; + let newLinks: any[] = []; + if (data) { + const rootNode = d3Tree(hierarchy(data[0])); + nodes = rootNode.descendants(); + links = rootNode.links(); + const newLinksToAdd: any[] = []; + + if (tsIds) { + for (const link of links) { + const targetId = link.target.data.id; + const sourceId = link.target.data.id; + + if (targetId && sourceId && (tsIds[targetId] || tsIds[sourceId])) { + // tsPlugin found + let topologicalLink: any; + + if (tsIds[targetId]) { + topologicalLink = link.target; + } else { + topologicalLink = link.source; + } + + const parents = tsIds[topologicalLink.data.id]; + const dict: any = {}; + + for (const link of links) { + for (let i = 0; i < parents.length; i++) { + if ( + link.source.data.id === parents[i] && + !dict[link.source.data.id] + ) { + dict[link.source.data.id] = link.source; + } else if ( + link.target.data.id === parents[i] && + !dict[link.target.data.id] + ) { + dict[link.target.data.id] = link.target; + } + } + } + + for (const i in dict) { + newLinksToAdd.push({ + source: dict[i], + target: topologicalLink, + }); + } + } + } + } + newLinks = [...links, ...newLinksToAdd]; + } + return { nodes, newLinks: newLinks }; + }; + + const { nodes, newLinks: links } = generateTree(); + + return ( + <> +
+ {loading ? ( + + ) : translate.x > 0 && translate.y > 0 ? ( + + Pipeline Tree + + {links?.map((linkData, i) => { + return ( + + ); + })} + {nodes?.map(({ data, x, y, parent }, i) => { + return ( + { + throw new Error("Function not implemented."); + }} + currentPipelineId={currentPipeline.data.id} + /> + ); + })} + + + ) : ( + + )} +
+ + ); +}; + +Tree.defaultProps = { + orientation: "vertical", + scaleExtent: { min: 0.1, max: 1 }, + zoom: 1, + nodeSize: { x: 120, y: 80 }, +}; + +interface LinkProps { + linkData: any; + key: string; + orientation: "vertical"; +} + +type LinkState = { + initialStyle: { + opacity: number; + }; +}; + +const LinkData: React.FC = ({ linkData }) => { + const { isDarkTheme } = useContext(ThemeContext); + const linkRef = useRef(null); + const [initialStyle] = useState({ opacity: 1 }); + const nodeRadius = 12; + + useEffect(() => { + applyOpacity(1); + }, []); + + const applyOpacity = ( + opacity: number, + done = () => { + return null; + }, + ) => { + select(linkRef.current).style("opacity", opacity).on("end", done); + }; + + const { source, target } = linkData; + + const drawPath = (ts: boolean) => { + const deltaX = target.x - source.x; + const deltaY = target.y - source.y; + const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + const normX = deltaX / dist; + const normY = deltaY / dist; + const sourcePadding = nodeRadius; + const targetPadding = nodeRadius + 4; + const sourceX = source.x + sourcePadding * normX; + const sourceY = source.y + sourcePadding * normY; + const targetX = target.x - targetPadding * normX; + const targetY = target.y - targetPadding * normY; + + if (ts) { + return linkVertical()({ + source: [sourceX, sourceY], + target: [targetX, targetY], + }); + } + return `M${sourceX} ${sourceY} L${targetX} ${targetY}`; + }; + + const ts = target.data.plugin_name === "pl-topologicalcopy"; + + const strokeWidthColor = isDarkTheme ? "#F2F9F9" : "#6A6E73"; + + return ( + + + + ); +}; + +export default Tree; diff --git a/src/components/PipelinesCopy/context.tsx b/src/components/PipelinesCopy/context.tsx new file mode 100644 index 000000000..fd10ed72f --- /dev/null +++ b/src/components/PipelinesCopy/context.tsx @@ -0,0 +1,232 @@ +import { + ComputeResource, + Pipeline, + PipelinePipingDefaultParameterList, + Plugin, + PluginPiping, +} from "@fnndsc/chrisapi"; +import { createContext, useReducer } from "react"; + +export type PerPipelinePayload = { + parameters: PipelinePipingDefaultParameterList; + pluginPipings: PluginPiping[]; + pipelinePlugins: Plugin[]; +}; + +export enum Types { + SetPipelines = "SET_PIPELINES", + SetComputeInfo = "SET_COMPUTE_INFO", + SetCurrentlyActiveNode = "SET_CURRENTLY_ACTIVE_NODE", + SetChangeCompute = "SET_CHANGE_COMPUTE", + SetChangeTitle = "SET_CHANGE_TITLE", + PipelineToAdd = "PIPELINES_TO_ADD", + ResetState = "RESET_STATE", +} + +export type ComputeInfoPayload = { + [key: string]: { + computeEnvs: ComputeResource[]; + currentlySelected: string; + }; +}; + +export type ComputeInfoState = { + [key: string]: ComputeInfoPayload; +}; + +export type TitleInfoState = { + [key: string]: { + [key: string]: string; + }; +}; + +type PipelinePayload = { + [Types.SetPipelines]: { + pipelineId: string; + } & PerPipelinePayload; + [Types.SetComputeInfo]: { + pipelineId: string; + computeEnvPayload: ComputeInfoPayload; + }; + [Types.SetCurrentlyActiveNode]: { + pipelineId: string; + nodeId: string; + }; + [Types.SetChangeCompute]: { + pipelineId: string; + nodeId: string; + compute: string; + }; + + [Types.SetChangeTitle]: { + pipelineId: string; + nodeId: string; + title: string; + }; + + [Types.PipelineToAdd]: { + pipeline: Pipeline; + }; + + [Types.ResetState]: null; +}; + +type CurrentlyActiveNode = { + [key: string]: string; +}; + +export interface PipelineState { + selectedPipeline?: { + [key: string]: PerPipelinePayload; + }; + computeInfo?: ComputeInfoState; + titleInfo?: TitleInfoState; + currentlyActiveNode?: CurrentlyActiveNode; + pipelineToAdd?: Pipeline; +} + +export function getInitialPipelineState(): PipelineState { + return { + selectedPipeline: undefined, + computeInfo: undefined, + currentlyActiveNode: undefined, + pipelineToAdd: undefined, + titleInfo: undefined, + }; +} + +type ActionMap = { + [Key in keyof M]: M[Key] extends undefined + ? { + type: Key; + } + : { + type: Key; + payload: M[Key]; + }; +}; + +export type PipelineActions = + ActionMap[keyof ActionMap]; + +export const pipelineReducer = ( + state: PipelineState, + action: PipelineActions, +) => { + switch (action.type) { + case Types.SetPipelines: { + const { pipelineId, pluginPipings, parameters, pipelinePlugins } = + action.payload; + + return { + ...state, + selectedPipeline: { + ...state.selectedPipeline, + [pipelineId]: { + pluginPipings, + parameters, + pipelinePlugins, + }, + }, + }; + } + + case Types.SetComputeInfo: { + const { pipelineId, computeEnvPayload } = action.payload; + + return { + ...state, + computeInfo: { + ...state.computeInfo, + [pipelineId]: { + ...state.computeInfo?.[pipelineId], + ...computeEnvPayload, + }, + }, + }; + } + + case Types.SetChangeTitle: { + const { pipelineId, nodeId, title } = action.payload; + + return { + ...state, + titleInfo: { + ...state.titleInfo, + [pipelineId]: { + ...state.titleInfo?.[pipelineId], + [nodeId]: title, + }, + }, + }; + } + + case Types.SetCurrentlyActiveNode: { + const { pipelineId, nodeId } = action.payload; + + return { + ...state, + currentlyActiveNode: { + ...state.currentlyActiveNode, + [pipelineId]: nodeId, + }, + }; + } + + case Types.SetChangeCompute: { + const { pipelineId, nodeId, compute } = action.payload; + + const computeEnvs = state.computeInfo?.[pipelineId]?.[nodeId] + ?.computeEnvs as ComputeResource[]; + + return { + ...state, + computeInfo: { + ...state.computeInfo, + [pipelineId]: { + ...state.computeInfo?.[pipelineId], + [nodeId]: { + computeEnvs: computeEnvs, + currentlySelected: compute, + }, + }, + }, + }; + } + + case Types.PipelineToAdd: { + return { + ...state, + pipelineToAdd: action.payload.pipeline, + }; + } + + case Types.ResetState: { + const state = getInitialPipelineState(); + return state; + } + + default: + return state; + } +}; + +const PipelineProvider = ({ children }: { children: React.ReactNode }) => { + const initialPipelineState = getInitialPipelineState(); + const [state, dispatch] = useReducer(pipelineReducer, initialPipelineState); + return ( + + {children} + + ); +}; + +const PipelineContext = createContext<{ + state: PipelineState; + dispatch: any; +}>({ + state: getInitialPipelineState(), + dispatch: () => null, +}); + +export { PipelineContext, PipelineProvider }; diff --git a/src/components/PipelinesCopy/index.tsx b/src/components/PipelinesCopy/index.tsx new file mode 100644 index 000000000..2a186ee9f --- /dev/null +++ b/src/components/PipelinesCopy/index.tsx @@ -0,0 +1,170 @@ +import { Button, Pagination } from "@patternfly/react-core"; +import { useQuery } from "@tanstack/react-query"; +import { Alert, Collapse } from "antd"; +import { useContext, useState } from "react"; +import { fetchPipelines, fetchResources } from "../../api/common"; +import { EmptyStateComponent, SpinContainer } from "../Common"; +import PipelinesComponent from "./PipelinesComponent"; +import { PerPipelinePayload, PipelineContext, Types } from "./context"; + +type PaginationEvent = React.MouseEvent | React.KeyboardEvent | MouseEvent; +type LoadingResources = { + [key: string]: boolean; +}; +type LoadingResourceError = { + [key: string]: string; +}; + +const PipelinesCopy = () => { + const { state, dispatch } = useContext(PipelineContext); + const [loadingResources, setLoadingResources] = useState(); + const [resourceError, setResourceError] = useState(); + const [pageState, setPageState] = useState({ + page: 1, + perPage: 10, + search: "", + itemCount: 0, + }); + const { perPage, page, search } = pageState; + + const { data, isLoading, isError, error } = useQuery({ + queryKey: ["pipelines", pageState], + queryFn: async () => { + const fetchedData = await fetchPipelines(perPage, page, search, ""); + setPageState({ + ...pageState, + itemCount: fetchedData.totalCount, + }); + return fetchedData; + }, + refetchOnMount: true, + }); + + const onSetPage = (_event: PaginationEvent, page: number) => { + setPageState({ + ...pageState, + page, + }); + }; + + const onPerPageSelect = (_event: PaginationEvent, perPage: number) => { + setPageState({ + ...pageState, + perPage, + }); + }; + + const handleChange = async (key: string | string[]) => { + const filteredPipelines = data?.registeredPipelines.filter((pipeline) => + (key as string[]).includes(`${pipeline.data.id}`), + ); + + if (filteredPipelines) { + for (const pipeline of filteredPipelines) { + const { id } = pipeline.data; + if (!state.selectedPipeline?.[id]) { + try { + setLoadingResources({ + ...loadingResources, + [pipeline.data.id]: true, + }); + const data: PerPipelinePayload = await fetchResources(pipeline); + dispatch({ + type: Types.SetPipelines, + payload: { + pipelineId: id, + ...data, + }, + }); + setLoadingResources({ + ...loadingResources, + [id]: false, + }); + } catch { + setResourceError({ + ...resourceError, + [id]: "Error in Fetching the Resources for this Pipeline", + }); + setLoadingResources({ + ...loadingResources, + [id]: false, + }); + } + } + } + } + }; + + return ( +
+ + + {isError && ( + {error.message}} /> + )} + + {isLoading ? ( + + ) : data?.registeredPipelines && data.registeredPipelines.length > 0 ? ( + { + const { name, id } = pipeline.data; + return { + key: id, + label: ( +
+
+ {name} +
+
+ {" "} +
+
+ ), + children: ( +
+ {resourceError?.[id] ? ( + + ) : loadingResources?.[id] ? ( + + ) : ( + + )} +
+ ), + }; + })} + /> + ) : ( + + )} +
+ ); +}; + +export default PipelinesCopy; From c5a8d5bfa13083077449e82b2b45cf5126e030c1 Mon Sep 17 00:00:00 2001 From: PintoGideon Date: Tue, 27 Feb 2024 07:47:10 -0500 Subject: [PATCH 3/4] Pipelines MVP --- src/components/CreateFeed/CreateFeed.tsx | 27 +- .../CreateFeed/PipelineContainter.tsx | 205 ------ src/components/CreateFeed/Pipelines.tsx | 614 ------------------ src/components/CreateFeed/Review.tsx | 11 +- src/components/CreateFeed/context/index.tsx | 29 +- src/components/CreateFeed/createFeedHelper.ts | 96 ++- .../CreateFeed/reducer/pipelineReducer.ts | 228 ------- src/components/CreateFeed/types/pipeline.ts | 239 ------- src/components/Feeds/FeedListView.tsx | 3 +- .../Pipelines/ClipboardCopyCommand.tsx | 74 --- .../Pipelines/ConfigurationPage.tsx | 224 ------- src/components/Pipelines/CreatePipeline.tsx | 140 ---- src/components/Pipelines/GeneralCompute.tsx | 52 -- src/components/Pipelines/ListCompute.tsx | 55 -- src/components/Pipelines/NodeData.tsx | 154 ----- src/components/Pipelines/TitleChange.tsx | 102 --- src/components/Pipelines/Tree.tsx | 362 ----------- src/components/Pipelines/UploadJson.tsx | 141 ---- src/components/Pipelines/index.ts | 7 - .../PipelinesCopy/GeneralCompute.tsx | 4 +- src/components/PipelinesCopy/NodeData.tsx | 3 +- src/components/PipelinesCopy/Pipelines.css | 4 + .../PipelinesCopy/Pipelines.module.css | 4 - .../PipelinesCopy/PipelinesComponent.tsx | 11 +- src/components/PipelinesCopy/TitleChange.tsx | 8 +- src/components/PipelinesCopy/Tree.tsx | 11 +- src/components/PipelinesCopy/context.tsx | 23 + src/components/PipelinesCopy/index.tsx | 148 ++++- src/components/PipelinesPage/index.tsx | 6 +- 29 files changed, 232 insertions(+), 2753 deletions(-) delete mode 100644 src/components/CreateFeed/PipelineContainter.tsx delete mode 100644 src/components/CreateFeed/Pipelines.tsx delete mode 100644 src/components/CreateFeed/reducer/pipelineReducer.ts delete mode 100644 src/components/CreateFeed/types/pipeline.ts delete mode 100644 src/components/Pipelines/ClipboardCopyCommand.tsx delete mode 100644 src/components/Pipelines/ConfigurationPage.tsx delete mode 100644 src/components/Pipelines/CreatePipeline.tsx delete mode 100644 src/components/Pipelines/GeneralCompute.tsx delete mode 100644 src/components/Pipelines/ListCompute.tsx delete mode 100644 src/components/Pipelines/NodeData.tsx delete mode 100644 src/components/Pipelines/TitleChange.tsx delete mode 100644 src/components/Pipelines/Tree.tsx delete mode 100644 src/components/Pipelines/UploadJson.tsx delete mode 100644 src/components/Pipelines/index.ts create mode 100644 src/components/PipelinesCopy/Pipelines.css delete mode 100644 src/components/PipelinesCopy/Pipelines.module.css diff --git a/src/components/CreateFeed/CreateFeed.tsx b/src/components/CreateFeed/CreateFeed.tsx index 137410357..ec8e5b911 100644 --- a/src/components/CreateFeed/CreateFeed.tsx +++ b/src/components/CreateFeed/CreateFeed.tsx @@ -1,28 +1,28 @@ -import * as React from "react"; -import { useContext } from "react"; import { Button, Modal, ModalVariant, Wizard, - WizardStep, WizardHeader, + WizardStep, } from "@patternfly/react-core"; import { useQueryClient } from "@tanstack/react-query"; import { notification } from "antd"; -import { CreateFeedContext, PipelineContext } from "./context"; -import { AddNodeContext } from "../AddNode/context"; +import * as React from "react"; +import { useContext } from "react"; import { MainRouterContext } from "../../routes"; -import { Types } from "./types/feed"; -import { PipelineTypes } from "./types/pipeline"; +import { useTypedSelector } from "../../store/hooks"; +import { AddNodeContext } from "../AddNode/context"; +import PipelinesCopy from "../PipelinesCopy"; +import { PipelineContext } from "../PipelinesCopy/context"; import BasicInformation from "./BasicInformation"; import ChooseConfig from "./ChooseConfig"; -import PipelinesCopy from "../PipelinesCopy"; import Review from "./Review"; import withSelectionAlert from "./SelectionAlert"; -import { useTypedSelector } from "../../store/hooks"; -import { createFeed } from "./createFeedHelper"; +import { CreateFeedContext } from "./context"; import "./createFeed.css"; +import { createFeed } from "./createFeedHelper"; +import { Types } from "./types/feed"; export default function CreateFeed() { const queryClient = useQueryClient(); @@ -38,7 +38,6 @@ export default function CreateFeed() { const { pluginMeta, selectedPluginFromMeta, dropdownInput, requiredInput } = addNodeState; const { wizardOpen, data, selectedConfig } = state; - const { pipelineData, selectedPipeline } = pipelineState; const getUploadFileCount = (value: number) => { dispatch({ @@ -81,11 +80,10 @@ export default function CreateFeed() { requiredInput, selectedPluginFromMeta, username, - pipelineData, getUploadFileCount, getFeedError, selectedConfig, - selectedPipeline, + pipelineState, ); if (feed) { @@ -209,9 +207,12 @@ export default function CreateFeed() { router.actions.clearFeedData(); + /* + // Pipelines to Dispatch pipelineDispatch({ type: PipelineTypes.ResetState, }); + */ } }} title="Create a New Analysis" diff --git a/src/components/CreateFeed/PipelineContainter.tsx b/src/components/CreateFeed/PipelineContainter.tsx deleted file mode 100644 index 00c33bb88..000000000 --- a/src/components/CreateFeed/PipelineContainter.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import React, { useContext } from "react"; -import { Pipeline } from "@fnndsc/chrisapi"; -import Pipelines from "./Pipelines"; -import { PipelineContext } from "./context"; -import { PipelineTypes, Resources, UploadJsonProps } from "./types/pipeline"; - -const PipelineContainer = ({ justDisplay }: { justDisplay?: boolean }) => { - const { state, dispatch } = useContext(PipelineContext); - - const handleDispatchPipelines = React.useCallback( - (registeredPipelines: any) => { - dispatch({ - type: PipelineTypes.SetPipelines, - payload: { - pipelines: registeredPipelines, - }, - }); - }, - [dispatch], - ); - - const handleSetPipelineResources = React.useCallback( - (result: Resources) => { - const { parameters, pluginPipings, pipelinePlugins, pipelineId } = result; - dispatch({ - type: PipelineTypes.SetPipelineResources, - payload: { - pipelineId, - parameters, - pluginPipings, - pipelinePlugins, - }, - }); - }, - [dispatch], - ); - - const handleSetCurrentComputeEnv = React.useCallback( - ( - item: { - name: string; - description: string; - }, - currentNode: number, - currentPipelineId: number, - computeEnvList: any[], - ) => { - dispatch({ - type: PipelineTypes.SetCurrentComputeEnvironment, - payload: { - computeEnv: { - item, - currentNode, - currentPipelineId, - computeEnvList, - }, - }, - }); - }, - [dispatch], - ); - - const handleSetCurrentNode = React.useCallback( - (pipelineId: number, currentNode: number) => { - dispatch({ - type: PipelineTypes.SetCurrentNode, - payload: { - pipelineId, - currentNode, - }, - }); - }, - [dispatch], - ); - - const handleUploadDispatch = React.useCallback( - (result: UploadJsonProps) => { - const { pipelineInstance } = result; - dispatch({ - type: PipelineTypes.AddPipeline, - payload: { - pipeline: pipelineInstance, - }, - }); - handleSetPipelineResources(result); - }, - [dispatch, handleSetPipelineResources], - ); - - const handleCleanResources = React.useCallback(() => { - dispatch({ - type: PipelineTypes.DeselectPipeline, - payload: {}, - }); - }, [dispatch]); - - const handlePipelineSecondaryResource = React.useCallback( - (pipeline: Pipeline) => { - dispatch({ - type: PipelineTypes.SetCurrentPipeline, - payload: { - pipelineId: pipeline.data.id, - }, - }); - - dispatch({ - type: PipelineTypes.SetPipelineName, - payload: { - pipelineName: pipeline.data.name, - }, - }); - }, - [dispatch], - ); - - const handleSetPipelineEnvironments = React.useCallback( - ( - pipelineId: number, - computeEnvData: { - [x: number]: { - computeEnvs: any[]; - currentlySelected: any; - }; - }, - ) => { - dispatch({ - type: PipelineTypes.SetPipelineEnvironments, - payload: { - pipelineId, - computeEnvData, - }, - }); - }, - [dispatch], - ); - - const handleSetCurrentNodeTitle = React.useCallback( - (currentPipelineId: number, currentNode: number, title: string) => { - dispatch({ - type: PipelineTypes.SetCurrentNodeTitle, - payload: { - currentPipelineId, - currentNode, - title, - }, - }); - }, - [dispatch], - ); - - const handleSetGenerateCompute = React.useCallback( - (currentPipelineId: number, computeEnv: string) => { - dispatch({ - type: PipelineTypes.SetGeneralCompute, - payload: { - currentPipelineId, - computeEnv, - }, - }); - }, - [dispatch], - ); - - const handleFormParameters = React.useCallback( - (currentNode: number, currentPipelineId: number, params: any[]) => { - dispatch({ - type: PipelineTypes.SetFormParameters, - payload: { - currentNode, - currentPipelineId, - params, - }, - }); - }, - [dispatch], - ); - - return ( -
-
- -
-
- ); -}; - -export default PipelineContainer; diff --git a/src/components/CreateFeed/Pipelines.tsx b/src/components/CreateFeed/Pipelines.tsx deleted file mode 100644 index 2f7a9209c..000000000 --- a/src/components/CreateFeed/Pipelines.tsx +++ /dev/null @@ -1,614 +0,0 @@ -import type { Pipeline } from "@fnndsc/chrisapi"; -import { - Button, - Dropdown, - DropdownItem, - DropdownList, - MenuToggle, - Pagination, - TextInput, -} from "@patternfly/react-core"; -import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon"; -import { useQuery } from "@tanstack/react-query"; -import { Alert, Collapse } from "antd"; -import React, { useCallback } from "react"; -import { - catchError, - fetchComputeInfo, - fetchPipelines, - generatePipelineWithName, -} from "../../api/common"; -import { useTypedSelector } from "../../store/hooks"; -import { EmptyStateComponent, ErrorAlert, SpinContainer } from "../Common"; -import { - ConfigurationPage, - GeneralCompute, - Tree, - UploadJson, -} from "../Pipelines/"; -import type { PipelinesProps } from "./types/pipeline"; - -export const PIPELINEQueryTypes = { - NAME: ["Name", "Match plugin name containing this string"], - ID: ["Id", "Match plugin id exactly with this number"], - OWNER_USERNAME: [ - "Owner_Username", - "Match pipeline's owner username exactly with this string", - ], - CATEGORY: ["Category", "Match plugin category containing this string"], - DESCRIPTION: [ - "Description", - "Match plugin description containing this string", - ], - AUTHORS: ["Authors", "Match plugin authors containing this string"], - MIN_CREATION_DATE: [ - "Min_creation_date", - "Match plugin creation date greater than this date", - ], - MAX_CREATION_DATE: [ - "Max_creation_date", - "Match plugin creation date lte this date", - ], -}; -const Pipelines = ({ - justDisplay, - state, - handleDispatchPipelines, - handleSetPipelineResources, - handleUploadDispatch, - handleSetCurrentNode, - handleCleanResources, - handlePipelineSecondaryResource, - handleSetPipelineEnvironments, - handleSetCurrentNodeTitle, - handleSetGeneralCompute, - handleSetCurrentComputeEnv, - handleFormParameters, -}: PipelinesProps) => { - const showDelete = useTypedSelector((state) => state.user.isStaff); - const { pipelineData, selectedPipeline, pipelines } = state; - const [errors, setErrors] = React.useState({}); - - const [pageState, setPageState] = React.useState({ - page: 1, - perPage: 10, - search: "", - itemCount: 0, - }); - - const [expanded, setExpanded] = React.useState<{ [key: string]: boolean }>(); - const [pipeline, setPipeline] = React.useState(); - const { page, perPage, search } = pageState; - const [isDropdownOpen, setIsDropdownOpen] = React.useState(false); - const [dropdownValue, setDropdownValue] = React.useState( - PIPELINEQueryTypes.NAME[0], - ); - - const handleDispatchWrap = React.useCallback( - (registeredPipelines: any) => { - handleDispatchPipelines(registeredPipelines); - }, - [handleDispatchPipelines], - ); - - const handlePipelineSearch = (search: string) => { - setPageState({ - ...pageState, - search, - }); - }; - - const { isLoading, isError, error } = useQuery({ - queryKey: ["pipelines", perPage, page, search, dropdownValue], - queryFn: async () => { - const data = await fetchPipelines( - perPage, - page, - search, - dropdownValue.toLowerCase(), - ); - - if (data?.registeredPipelines) { - handleDispatchWrap(data?.registeredPipelines); - } - - if (data?.registeredPipelinesList) { - setPageState((pageState) => { - return { - ...pageState, - itemCount: data?.registeredPipelinesList.totalCount, - }; - }); - } - - return data; - }, - enabled: true, - refetchOnMount: true, - }); - - const { - isLoading: isResourcesLoading, - isError: isResourceError, - error: resourceError, - } = useQuery({ - queryKey: ["pipelineresources", pipeline], - queryFn: async () => { - if (pipeline) { - try { - const { resources } = await generatePipelineWithName( - pipeline.data.name, - ); - - handleSetPipelineResources({ - ...resources, - pipelineId: pipeline.data.id, - }); - const { pluginPipings } = resources; - - for (let i = 0; i < pluginPipings.length; i++) { - const piping = pluginPipings[i]; - const computeEnvData = await fetchComputeInfo( - piping.data.plugin_id, - piping.data.id, - ); - - if (computeEnvData) { - handleSetPipelineEnvironments(pipeline.data.id, computeEnvData); - } - } - return resources; - } catch (error: any) { - const errObj = catchError(error); - throw new Error(errObj.error_message); - } - } - }, - enabled: !!pipeline, - }); - - React.useEffect(() => { - const el = document.querySelector(".react-json-view"); - - if (el) { - //@ts-ignore - el?.scrollIntoView({ block: "center", behavior: "smooth" }); - } - }); - - const handleNodeClick = async (nodeName: number, pipelineId: number) => { - handleSetCurrentNode(pipelineId, nodeName); - }; - - const onSetPage = (_event: any, page: number) => { - setPageState({ - ...pageState, - page, - }); - }; - const onPerPageSelect = (_event: any, perPage: number) => { - setPageState({ - ...pageState, - perPage, - }); - }; - - const handleOnButtonClick = useCallback( - async (pipeline: Pipeline) => { - if (!(selectedPipeline === pipeline.data.id)) { - handlePipelineSecondaryResource(pipeline); - if (!pipelineData[pipeline.data.id]) { - setPipeline(pipeline); - } - } else { - setPipeline(undefined); - handleCleanResources(); - } - }, - [ - handleCleanResources, - handlePipelineSecondaryResource, - pipelineData, - selectedPipeline, - pipelineData[pipeline?.data.id], - ], - ); - - const handleOnExpand = useCallback( - (pipeline: Pipeline) => { - console.log("Pipeline", pipeline, selectedPipeline); - if (!(selectedPipeline === pipeline.data.id)) { - handlePipelineSecondaryResource(pipeline); - } else { - handleCleanResources(); - } - //Not already expanded or not previous fetched and cached in state - if ( - !expanded?.[pipeline.data.id] || - !state.pipelineData[pipeline.data.id] - ) { - setPipeline(pipeline); - setExpanded({ - ...expanded, - [pipeline.data.id]: true, - }); - } else { - setPipeline(undefined); - setExpanded({ - ...expanded, - [pipeline.data.id]: false, - }); - } - }, - [ - expanded, - handleCleanResources, - handlePipelineSecondaryResource, - selectedPipeline, - state.pipelineData[pipeline?.data.id], - ], - ); - - const onToggle = () => { - setIsDropdownOpen(!isDropdownOpen); - }; - - const onFocus = () => { - const element = document.getElementById("toggle-basic"); - element?.focus(); - }; - - const onSelect = () => { - setIsDropdownOpen(false); - onFocus(); - }; - - const updateDropdownValue = (type: string) => { - setDropdownValue(type); - handlePipelineSearch(""); - }; - - const dropdownItems = [ - Object.values(PIPELINEQueryTypes).map((pipeline) => { - return ( - updateDropdownValue(pipeline[0])} - > - {pipeline[0]} - - ); - }), - ]; - - return ( - <> - -
-
- { - return ( - - {dropdownValue} - - ); - }} - isOpen={isDropdownOpen} - > - {dropdownItems} - - } - aria-label="search" - onChange={(_event, value: string) => { - handlePipelineSearch?.(value.toLowerCase()); - }} - /> -
- -
- - {isError && ( - {error.message}} /> - )} - - {isLoading ? ( - - ) : pipelines.length > 0 ? ( - pipelines.map((pipeline: Pipeline) => { - return ( - { - handleOnExpand(pipeline); - }} - key={pipeline.data.id} - items={[ - { - key: pipeline.data.id, - label: ( -
-
- {pipeline.data.name} -
- -
- - {showDelete && ( - - )} -
-
- ), - children: - (expanded?.[pipeline.data.id] || - state.pipelineData[pipeline.data.id]) && - !isResourcesLoading && - !isResourceError ? ( - <> -
- - - -
- - - - ) : ( - - ), - }, - ]} - /> - ); - }) - ) : ( - - )} - - {/* - - - {isError && ( - {error.message}} /> - )} - {isLoading ? ( - - ) : pipelines.length > 0 ? ( - pipelines.map((pipeline: any) => ( - - - handleKeyDown(e, pipeline)} - onClick={() => handleOnExpand(pipeline)} - /> - -
- - {pipeline.data.name} - - - {pipeline.data.description} - -
- , - ]} - /> - - {!justDisplay && ( - - )} - {/*Hardcode to only allow the user 'chris' to delete the pipelines} - {showDelete && ( - - )} - -
- - - {(expanded?.[pipeline.data.id] || - state.pipelineData[pipeline.data.id]) && - !isResourcesLoading && - !isResourceError ? ( - <> -
- - {/* - - - } -
- - - - ) : ( - - )} -
-
- )) - ) : ( - - )} -
- */} - -
- {Object.keys(errors).length > 0 && ( - setErrors({})} /> - )} - - {isResourceError && ( - - )} -
- - ); -}; - -export default Pipelines; diff --git a/src/components/CreateFeed/Review.tsx b/src/components/CreateFeed/Review.tsx index d51169ba6..c36a699f0 100644 --- a/src/components/CreateFeed/Review.tsx +++ b/src/components/CreateFeed/Review.tsx @@ -1,7 +1,8 @@ import { useCallback, useContext, useEffect } from "react"; import { Grid, WizardContext, Split, SplitItem } from "@patternfly/react-core"; import { ChartDonutUtilization } from "@patternfly/react-charts"; -import { CreateFeedContext, PipelineContext } from "./context"; +import { CreateFeedContext } from "./context"; +import { PipelineContext } from "../PipelinesCopy/context"; import { unpackParametersIntoString } from "../AddNode/utils"; import { PluginDetails } from "../AddNode/ReviewGrid"; import { ChrisFileDetails, LocalFileDetails } from "./HelperComponent"; @@ -24,6 +25,8 @@ const Review = ({ handleSave }: { handleSave: () => void }) => { selectedComputeEnv, } = addNodeState; + const { pipelineToAdd } = pipelineState; + // the installed version of @patternfly/react-core doesn't support read-only chips const tagList = tags.map((tag: any) => (
@@ -125,7 +128,7 @@ const Review = ({ handleSave }: { handleSave: () => void }) => { ); }; - const feedErrorMessage = (feedError && feedError["error_message"]) || ""; + const feedErrorMessage = feedError?.error_message || ""; return (
@@ -161,9 +164,7 @@ const Review = ({ handleSave }: { handleSave: () => void }) => { title={Selected Pipeline:} subTitle={ - {pipelineState.pipelineName - ? pipelineState.pipelineName - : "None Selected"} + {pipelineToAdd ? pipelineToAdd.data.name : "None Selected"} } /> diff --git a/src/components/CreateFeed/context/index.tsx b/src/components/CreateFeed/context/index.tsx index 8598fbad6..be85a149a 100644 --- a/src/components/CreateFeed/context/index.tsx +++ b/src/components/CreateFeed/context/index.tsx @@ -1,10 +1,6 @@ import React, { createContext, useContext, useReducer } from "react"; import { MainRouterContext } from "../../../routes"; import { createFeedReducer, getInitialState } from "../reducer/feedReducer"; -import { - pipelineReducer, - getInitialPipelineState, -} from "../reducer/pipelineReducer"; import { CreateFeedState } from "../types/feed"; const CreateFeedContext = createContext<{ @@ -15,14 +11,6 @@ const CreateFeedContext = createContext<{ dispatch: () => null, }); -const PipelineContext = createContext<{ - state: any; - dispatch: any; -}>({ - state: getInitialPipelineState(), - dispatch: () => null, -}); - interface CreateFeedProviderProps { children: React.ReactNode; } @@ -40,19 +28,4 @@ const CreateFeedProvider: React.FC = ({ ); }; -const PipelineProvider = ({ children }: CreateFeedProviderProps) => { - const initialpipelineState = getInitialPipelineState(); - const [state, dispatch] = useReducer(pipelineReducer, initialpipelineState); - return ( - - {children} - - ); -}; - -export { - CreateFeedContext, - CreateFeedProvider, - PipelineProvider, - PipelineContext, -}; +export { CreateFeedContext, CreateFeedProvider }; diff --git a/src/components/CreateFeed/createFeedHelper.ts b/src/components/CreateFeed/createFeedHelper.ts index 1eeb2d1fa..2633db222 100644 --- a/src/components/CreateFeed/createFeedHelper.ts +++ b/src/components/CreateFeed/createFeedHelper.ts @@ -1,9 +1,13 @@ -import { Plugin, PluginInstance, PluginParameter } from "@fnndsc/chrisapi"; +import { + Plugin, + PluginInstance, + PluginParameter, + Feed, +} from "@fnndsc/chrisapi"; import ChrisAPIClient from "../../api/chrisapiclient"; import { InputType } from "../AddNode/types"; import { unpackParametersIntoObject } from "../AddNode/utils"; import { CreateFeedData } from "./types/feed"; -import { PipelineData } from "./types/pipeline"; import { catchError, @@ -11,6 +15,7 @@ import { limitConcurrency, uploadWrapper, } from "../../api/common"; +import { PipelineState } from "../PipelinesCopy/context"; export function getName(selectedConfig: string) { if (selectedConfig === "fs_plugin") { @@ -28,17 +33,16 @@ export const createFeed = async ( requiredInput: InputType, selectedPlugin: Plugin | undefined, username: string | null | undefined, - pipelineData: PipelineData, setUploadFileCallback: (status: number) => void, setErrorCallback: (error: any) => void, selectedConfig: string[], - selectedPipeline?: number, + state: PipelineState, ) => { /** * Dircopy requires a path from the ChRIS object storage * as in input */ - let feed; + let feed: Feed | undefined | null; if ( selectedConfig.includes("local_select") || @@ -47,11 +51,10 @@ export const createFeed = async ( feed = await createFeedInstanceWithDircopy( data, username, - pipelineData, setUploadFileCallback, setErrorCallback, selectedConfig, - selectedPipeline, + state, ); } else if (selectedConfig.includes("fs_plugin")) { feed = await createFeedInstanceWithFS( @@ -67,16 +70,15 @@ export const createFeed = async ( export const createFeedInstanceWithDircopy = async ( data: CreateFeedData, username: string | null | undefined, - pipelineData: PipelineData, setUploadFileCallback: (value: number) => void, errorCallback: (error: any) => void, selectedConfig: string[], - selectedPipeline?: number, + state: PipelineState, ) => { const { chrisFiles, localFiles } = data; let dirpath: string[] = []; - let feed; + let feed: Feed; if (selectedConfig.includes("swift_storage")) { dirpath = chrisFiles.map((path: string) => path); @@ -108,56 +110,46 @@ export const createFeedInstanceWithDircopy = async ( dir: dirpath.join(","), }, ); + const { pipelineToAdd, computeInfo, titleInfo, selectedPipeline } = + state; + const id = pipelineToAdd?.data.id; + const resources = selectedPipeline?.[id]; if (createdInstance) { - if (selectedPipeline) { - const pipeline = pipelineData[selectedPipeline]; - if ( - pipeline.pluginPipings && - pipeline.pluginParameters && - pipeline.pipelinePlugins && - pipeline.pluginPipings.length > 0 - ) { - const { pluginParameters, computeEnvs, parameterList } = pipeline; - - const nodes_info = client.computeWorkflowNodesInfo( - //@ts-ignore - pluginParameters.data, - ); - - nodes_info.forEach((node) => { - if (computeEnvs && computeEnvs[node["piping_id"]]) { - const compute_node = - computeEnvs[node["piping_id"]]["currentlySelected"]; - - const title = - pipeline.title && pipeline.title[node["piping_id"]]; - if (title) { - node.title = title; - } - if (compute_node) { - node.compute_resource_name = compute_node; - } - } - - if (parameterList && parameterList[node["piping_id"]]) { - const params = parameterList[node["piping_id"]]; - node["plugin_parameter_defaults"] = params; - } - }); - - await client.createWorkflow(selectedPipeline, { - previous_plugin_inst_id: createdInstance.data.id, - nodes_info: JSON.stringify(nodes_info), - }); + if (resources) { + const { parameters } = resources; + + const nodes_info = client.computeWorkflowNodesInfo(parameters.data); + + for (const node of nodes_info) { + // Set compute info + const activeNode = computeInfo?.[id][node.piping_id]; + // Set Title + const titleSet = titleInfo?.[id][node.piping_id]; + + if (activeNode) { + const compute_node = activeNode.currentlySelected; + node.compute_resource_name = compute_node; + } + + if (titleSet) { + node.title = titleSet; + } } + + await client.createWorkflow(id, { + previous_plugin_inst_id: createdInstance.data.id, + nodes_info: JSON.stringify(nodes_info), + }); } - feed = await createdInstance.getFeed(); + feed = (await createdInstance.getFeed()) as Feed; + return feed; } } catch (error) { console.log("Error", error); } + return null; //when the `post` finishes, the dircopyInstances's internal collection is updated } @@ -165,8 +157,6 @@ export const createFeedInstanceWithDircopy = async ( const errorObj = catchError(error); errorCallback(errorObj); } - - return feed; }; export const createFeedInstanceWithFS = async ( diff --git a/src/components/CreateFeed/reducer/pipelineReducer.ts b/src/components/CreateFeed/reducer/pipelineReducer.ts deleted file mode 100644 index 61530ac94..000000000 --- a/src/components/CreateFeed/reducer/pipelineReducer.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { - PipelineActions, - PipelineState, - PipelineTypes, -} from "../types/pipeline"; -import { merge } from "lodash"; - -export function getInitialPipelineState() { - return { - pipelineData: {}, - pipelineName: "", - pipelines: [], - selectedPipeline: undefined, - }; -} - -export const pipelineReducer = ( - state: PipelineState, - action: PipelineActions, -) => { - switch (action.type) { - case PipelineTypes.DeselectPipeline: { - return { - ...state, - selectedPipeline: undefined, - }; - } - case PipelineTypes.SetPipelines: { - return { - ...state, - pipelines: action.payload.pipelines, - }; - } - - case PipelineTypes.AddPipeline: { - return { - ...state, - pipelines: [...state.pipelines, action.payload.pipeline], - }; - } - case PipelineTypes.SetPipelineResources: { - const { pipelineId, pluginPipings, parameters, pipelinePlugins } = - action.payload; - return { - ...state, - pipelineData: { - ...state.pipelineData, - [pipelineId]: { - pluginPipings, - pluginParameters: parameters, - pipelinePlugins, - currentNode: undefined, - computeEnvs: undefined, - }, - }, - }; - } - - case PipelineTypes.SetCurrentComputeEnvironment: { - const { item, currentNode, currentPipelineId, computeEnvList } = - action.payload.computeEnv; - - return { - ...state, - pipelineData: { - ...state.pipelineData, - [currentPipelineId]: { - ...state.pipelineData[currentPipelineId], - computeEnvs: { - ...state.pipelineData[currentPipelineId].computeEnvs, - [currentNode]: { - computeEnvs: computeEnvList, - currentlySelected: item.name, - }, - }, - }, - }, - }; - } - - case PipelineTypes.SetGeneralCompute: { - const { currentPipelineId, computeEnv } = action.payload; - - if (state.pipelineData[currentPipelineId].computeEnvs) { - const computeEnvs = state.pipelineData[currentPipelineId].computeEnvs; - - if (computeEnvs) { - for (const id in computeEnvs) { - const currentComputeEnvs = computeEnvs[id].computeEnvs; - const names = currentComputeEnvs.map((env) => env.name); - if (names.includes(computeEnv)) { - computeEnvs[id].currentlySelected = computeEnv; - } - } - } - - return { - ...state, - pipelineData: { - ...state.pipelineData, - [currentPipelineId]: { - ...state.pipelineData[currentPipelineId], - computeEnvs, - generalCompute: computeEnv, - }, - }, - }; - } else { - return { - ...state, - pipelineData: { - ...state.pipelineData, - [currentPipelineId]: { - ...state.pipelineData[currentPipelineId], - generalCompute: computeEnv, - }, - }, - }; - } - } - - case PipelineTypes.SetPipelineEnvironments: { - const { computeEnvData, pipelineId } = action.payload; - if (state.pipelineData[pipelineId].computeEnvs) { - return { - ...state, - pipelineData: { - ...state.pipelineData, - [pipelineId]: { - ...state.pipelineData[pipelineId], - computeEnvs: merge( - state.pipelineData[pipelineId].computeEnvs, - computeEnvData, - ), - }, - }, - }; - } else { - return { - ...state, - pipelineData: { - ...state.pipelineData, - [pipelineId]: { - ...state.pipelineData[pipelineId], - computeEnvs: computeEnvData, - }, - }, - }; - } - } - - case PipelineTypes.SetCurrentNode: { - const { pipelineId, currentNode } = action.payload; - return { - ...state, - pipelineData: { - ...state.pipelineData, - [pipelineId]: { - ...state.pipelineData[pipelineId], - currentNode, - }, - }, - }; - } - - case PipelineTypes.SetCurrentPipeline: { - const { pipelineId } = action.payload; - return { - ...state, - selectedPipeline: pipelineId, - }; - } - - case PipelineTypes.SetPipelineName: { - const { pipelineName } = action.payload; - - return { - ...state, - pipelineName, - }; - } - - case PipelineTypes.SetCurrentNodeTitle: { - const { currentPipelineId, currentNode, title } = action.payload; - return { - ...state, - pipelineData: { - ...state.pipelineData, - [currentPipelineId]: { - ...state.pipelineData[currentPipelineId], - title: { - ...state.pipelineData[currentPipelineId].title, - [currentNode]: title, - }, - }, - }, - }; - } - - case PipelineTypes.SetFormParameters: { - const { currentPipelineId, currentNode, params } = action.payload; - return { - ...state, - pipelineData: { - ...state.pipelineData, - [currentPipelineId]: { - ...state.pipelineData[currentPipelineId], - parameterList: { - ...state.pipelineData[currentPipelineId].parameterList, - [currentNode]: params, - }, - }, - }, - }; - } - - case PipelineTypes.ResetState: { - return { - pipelineData: {}, - pipelineName: "", - pipelines: [], - selectedPipeline: undefined, - }; - } - default: - return state; - } -}; diff --git a/src/components/CreateFeed/types/pipeline.ts b/src/components/CreateFeed/types/pipeline.ts deleted file mode 100644 index 339946f96..000000000 --- a/src/components/CreateFeed/types/pipeline.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { - Pipeline, - PipelinePipingDefaultParameterList, - PluginPiping, -} from "@fnndsc/chrisapi"; - -type ActionMap = { - [Key in keyof M]: M[Key] extends undefined - ? { - type: Key; - } - : { - type: Key; - payload: M[Key]; - }; -}; - -export enum PipelineTypes { - SetPipelines = "SET_PIPELINES", - SetPipelineResources = "SET_PIPELINE_RESOURCES", - SetCurrentComputeEnvironment = "SET_CURRENT_COMPUTE_ENVIRONMENT", - SetCurrentNode = "SET_CURRENT_NODE", - AddPipeline = "ADD_PIPELINE", - SetCurrentPipeline = "SET_CURRENT_PIPELINE", - SetPipelineName = "SET_PIPELINE_NAME", - SetPipelineEnvironments = "SET_PIPELINE_ENVIRONMENTS", - SetCurrentNodeTitle = "SET_CURRENT_NODE_TITLE", - SetDefaultParameters = "SET_DEFAULT_PARAMETERS", - SetGeneralCompute = "SET_GENERAL_COMPUTE", - SetFormParameters = "SET_FORM_PARAMETERS", - DeselectPipeline = "DESELECT_PIPELINE", - ResetState = "RESET_STATE", -} - -export interface ComputeEnvData { - [key: string]: { computeEnvs: any[]; currentlySelected: any }; -} - -type PipelinePayload = { - [PipelineTypes.SetPipelines]: { - pipelines: any[]; - }; - - [PipelineTypes.SetFormParameters]: { - currentNode: number; - currentPipelineId: string; - params: any[]; - }; - - [PipelineTypes.SetPipelineResources]: { - pipelineId: number; - parameters: any[]; - pluginPipings: PluginPiping[]; - pipelinePlugins: any[]; - }; - - [PipelineTypes.SetCurrentComputeEnvironment]: { - computeEnv: { - item: any; - currentNode: number; - currentPipelineId: string; - computeEnvList: any[]; - }; - }; - - [PipelineTypes.SetCurrentNode]: { - pipelineId: number; - currentNode: number; - }; - - [PipelineTypes.AddPipeline]: { - pipeline: any; - }; - - [PipelineTypes.SetCurrentPipeline]: { - pipelineId: number; - }; - - [PipelineTypes.SetPipelineName]: { - pipelineName: string; - }; - - [PipelineTypes.SetPipelineEnvironments]: { - pipelineId: number; - computeEnvData: { - [key: string]: { - computeEnvs: any[]; - currentlySelected: any; - }; - }; - }; - - [PipelineTypes.SetCurrentNodeTitle]: { - currentPipelineId: string; - currentNode: number; - title: string; - }; - - [PipelineTypes.SetDefaultParameters]: { - pipelineId: string; - defaultParameters: []; - }; - - [PipelineTypes.DeselectPipeline]: Record; - - [PipelineTypes.SetGeneralCompute]: { - currentPipelineId: number; - computeEnv: string; - }; - - [PipelineTypes.ResetState]: Record; -}; - -export type PipelineActions = - ActionMap[keyof ActionMap]; - -export interface PipelineState { - pipelineData: PipelineData; - pipelineName: string; - selectedPipeline?: number; - pipelines: any[]; -} - -export interface SinglePipeline { - pluginParameters?: any[]; - defaultParameters?: any[]; - pluginPipings?: PluginPiping[]; - pipelinePlugins?: any[]; - computeEnvs?: ComputeEnvData; - currentNode?: number; - generalCompute?: string; - title: { - [id: number]: string; - }; - parameterList: { - [id: number]: any[]; - }; -} - -export type UploadJsonProps = Resources & PipelineInstanceResource; - -export interface Resources { - parameters: PipelinePipingDefaultParameterList; - pluginPipings: any[]; - pipelinePlugins: any[]; - pipelineId?: number; -} - -export interface PipelineInstanceResource { - pipelineInstance: Pipeline; -} - -export interface PipelineData { - [key: string]: SinglePipeline; -} - -export interface PipelinesProps { - justDisplay?: boolean; - handleDispatchPipelines: (registeredPipelines: any) => void; - handleSetPipelineResources: (result: Resources) => void; - handleUploadDispatch: (result: UploadJsonProps) => void; - handleSetCurrentNode: (pipelineId: number, currentNode: number) => void; - handleCleanResources: () => void; - handlePipelineSecondaryResource: (pipeline: Pipeline) => void; - handleSetCurrentNodeTitle: ( - currentPipelineId: number, - currentNode: number, - title: string, - ) => void; - handleSetPipelineEnvironments: ( - pipelineId: number, - computeEnvData: { - [x: number]: { - computeEnvs: any[]; - currentlySelected: any; - }; - }, - ) => void; - handleSetGeneralCompute: ( - currentPipelineId: number, - computeEnv: string, - ) => void; - - handleSetCurrentComputeEnv: ( - item: { - name: string; - description: string; - }, - currentNode: number, - currentPipelineId: number, - computeEnvList: any[], - ) => void; - - handleFormParameters: ( - currentNode: number, - currentPipelineId: number, - newParamDict: any[], - ) => void; - state: any; -} - -export interface ConfiguartionPageProps { - pipelines: any; - currentPipelineId: number; - pipeline: Pipeline; - state: SinglePipeline; - handleSetCurrentNodeTitle: ( - currentPipelineId: number, - currentNode: number, - title: string, - ) => void; - handleDispatchPipelines: (registeredPipelines: any) => void; - handleSetCurrentComputeEnv: ( - item: { - name: string; - description: string; - }, - currentNode: number, - currentPipelineId: number, - computeEnvList: any[], - ) => void; - handleFormParameters: ( - currentNode: number, - currentPipelineId: number, - newParamDict: any[], - ) => void; - handleSetGeneralCompute: ( - currentPipelineId: number, - computeEnv: string, - ) => void; - justDisplay?: boolean; -} - -export interface CreatePipelineProps { - pipelines: any; - pipeline: Pipeline; - state: SinglePipeline; - handleDispatchPipelines: (registeredPipelines: any) => void; -} diff --git a/src/components/Feeds/FeedListView.tsx b/src/components/Feeds/FeedListView.tsx index 5adb42d83..7a07b5a7f 100644 --- a/src/components/Feeds/FeedListView.tsx +++ b/src/components/Feeds/FeedListView.tsx @@ -37,7 +37,8 @@ import { setSidebarActive } from "../../store/ui/actions"; import { AddNodeProvider } from "../AddNode/context"; import { DataTableToolbar, InfoIcon } from "../Common"; import CreateFeed from "../CreateFeed/CreateFeed"; -import { CreateFeedProvider, PipelineProvider } from "../CreateFeed/context"; +import { CreateFeedProvider } from "../CreateFeed/context"; +import { PipelineProvider } from "../PipelinesCopy/context"; import { ThemeContext } from "../DarkTheme/useTheme"; import IconContainer from "../IconContainer"; import WrapperConnect from "../Wrapper"; diff --git a/src/components/Pipelines/ClipboardCopyCommand.tsx b/src/components/Pipelines/ClipboardCopyCommand.tsx deleted file mode 100644 index 24e7b90f0..000000000 --- a/src/components/Pipelines/ClipboardCopyCommand.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from "react"; -import { - CodeBlock, - CodeBlockCode, - ClipboardCopyButton, - CodeBlockAction, - clipboardCopyFunc, -} from "@patternfly/react-core"; -import { SinglePipeline } from "../CreateFeed/types/pipeline"; - -const ClipboardCopyCommand = ({ state }: { state: SinglePipeline }) => { - const [copied, setCopied] = React.useState(false); - - const { currentNode, parameterList } = state; - - const params = parameterList && currentNode && parameterList[currentNode]; - const pluginPiping = state?.pluginPipings?.filter((piping) => { - if (currentNode && piping.data.id === currentNode) { - return piping; - } - }); - - let generatedCommand = ""; - - if (params && params.length > 0) { - for (const input in params) { - //@ts-ignore - const name = params[input].name; - //@ts-ignore - const defaultValue = - params[input].default === false || params[input].default === true - ? "" - : params[input].default.length === 0 - ? "' '" - : params[input].default; - generatedCommand += ` --${name} ${defaultValue}`; - } - } - - const onClick = (event: any, text: any) => { - clipboardCopyFunc(event, text); - setCopied(true); - }; - - const actions = ( - - - - {pluginPiping && pluginPiping.length > 0 - ? `${pluginPiping[0].data.plugin_name}:${pluginPiping[0].data.plugin_version}` - : "N/A"} - - onClick(e, generatedCommand)} - exitDelay={600} - maxWidth="110px" - variant="plain" - > - {copied ? "Successfully copied to clipboard" : "Copy to clipboard"} - - - - ); - - return ( - - {generatedCommand} - - ); -}; -export default ClipboardCopyCommand; diff --git a/src/components/Pipelines/ConfigurationPage.tsx b/src/components/Pipelines/ConfigurationPage.tsx deleted file mode 100644 index afbb308c7..000000000 --- a/src/components/Pipelines/ConfigurationPage.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { PluginPiping } from "@fnndsc/chrisapi"; -import { - ActionGroup, - Button, - ExpandableSection, - Form, - FormGroup, - Grid, - GridItem, - TextInput, -} from "@patternfly/react-core"; -import React from "react"; -import { PipelineContext } from "../CreateFeed/context"; -import type { ConfiguartionPageProps } from "../CreateFeed/types/pipeline"; -import ClipboardCopyCommand from "./ClipboardCopyCommand"; -import ListCompute from "./ListCompute"; -import TitleChange from "./TitleChange"; - -const ConfigurationPage = (props: ConfiguartionPageProps) => { - const [isExpanded, setIsExpanded] = React.useState(false); - const { - currentPipelineId, - state, - handleSetCurrentNodeTitle, - handleSetCurrentComputeEnv, - handleFormParameters, - justDisplay, - } = props; - - const { currentNode, computeEnvs, pluginPipings, pluginParameters } = state; - const computeEnvList = - computeEnvs && currentNode && computeEnvs[currentNode] - ? computeEnvs[currentNode].computeEnvs - : []; - const [selectedPlugin, setSelectedPlugin] = React.useState(); - - const onToggle = (_event: React.MouseEvent, isExpanded: boolean) => { - setIsExpanded(isExpanded); - }; - - React.useEffect(() => { - async function fetchResources() { - if (pluginPipings && currentNode && pluginParameters) { - const pluginPiping = pluginPipings.filter((piping: any) => { - return piping.data.id === currentNode; - }); - - const selectedPlugin = await pluginPiping[0].getPlugin(); - setSelectedPlugin(pluginPiping[0]); - - const params = await selectedPlugin.getPluginParameters({ - limit: 1000, - }); - - const paramDict: { - [key: string]: string; - } = {}; - //@ts-ignore - const filteredParameters = pluginParameters?.data.filter( - (param: any) => { - return param.plugin_piping_id === pluginPiping[0].data.id; - }, - ); - - for (const param of filteredParameters) { - paramDict[param.param_name] = param; - } - - const paramItems = params.getItems(); - - if (paramItems) { - const newParamDict: any[] = []; - - for (const param of paramItems) { - if (paramDict[param.data.name]) { - const defaultParam = paramDict[param.data.name]; - //@ts-ignore - const newParam = { - name: param.data.name, - default: param.data.default, - }; - //@ts-ignore - newParam.default = defaultParam.value; - newParamDict.push(newParam); - } - } - - handleFormParameters(currentNode, currentPipelineId, newParamDict); - } - } - } - - fetchResources(); - }, [ - currentNode, - pluginPipings, - pluginParameters, - handleFormParameters, - currentPipelineId, - ]); - - const generalCompute = - computeEnvs && - currentNode && - computeEnvs[currentNode] && - computeEnvs[currentNode].currentlySelected; - - const dispatchFn = (item: any) => { - if (currentNode) - handleSetCurrentComputeEnv( - item, - currentNode, - currentPipelineId, - computeEnvList, - ); - }; - - return ( - <> - - {!justDisplay && ( -
-
- -
-
- -
-
- )} - - {!justDisplay && ( - - - - - - - - )} - - ); -}; - -export default ConfigurationPage; - -export const ConfigurePipelineParameters = ({ - currentPipelineId, - handleFormParameters, -}: { - handleFormParameters: ( - currentNode: number, - currentPipelineId: number, - paramDict: any[], - ) => void; - currentPipelineId: number; -}) => { - const { state } = React.useContext(PipelineContext); - const { pipelineData } = state; - const { parameterList, currentNode } = pipelineData[currentPipelineId]; - const params = parameterList && currentNode && parameterList[currentNode]; - - const onFinish = (values: any) => { - const newParams = params.map((param: any) => { - const newValue = values[param.name]; - if (newValue) { - param.default = - newValue === "true" || newValue === "false" - ? Boolean(newValue) - : values[param.name]; - } - return param; - }); - handleFormParameters(currentNode, currentPipelineId, newParams); - }; - - return ( -
- {params && - params.length > 0 && - params.map((param: any) => { - return ( - - - - ); - })} - - - -
- ); -}; diff --git a/src/components/Pipelines/CreatePipeline.tsx b/src/components/Pipelines/CreatePipeline.tsx deleted file mode 100644 index b197479fd..000000000 --- a/src/components/Pipelines/CreatePipeline.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React from "react"; -import ReactJson from "react-json-view"; -import { Spin } from "antd"; -import { Button, TextInput } from "@patternfly/react-core"; -import { CreatePipelineProps } from "../CreateFeed/types/pipeline"; -import { generatePipelineWithData } from "../CreateFeed/utils"; - -const CreatingPipeline = ({ - pipelines, - pipeline, - state, - handleDispatchPipelines, -}: CreatePipelineProps) => { - const { pluginParameters, pluginPipings, title, parameterList } = state; - const [creatingPipeline, setCreatingPipeline] = React.useState({ - loading: false, - error: {}, - pipelineName: "", - }); - const handlePipelineCreate = async () => { - setCreatingPipeline({ - ...creatingPipeline, - loading: true, - }); - const mappedArr: any[] = []; - try { - pluginPipings?.forEach((piping: any) => { - const defaults = pluginParameterDefaults( - //@ts-ignore - pluginParameters.data, - piping.data.id, - parameterList, - ); - - const id = pluginPipings.findIndex( - (pipe: any) => pipe.data.id === piping.data.previous_id, - ); - - let titleChange = piping.data.title; - if (title && title[piping.data.id]) { - titleChange = title[piping.data.id]; - } - - const treeObl = { - plugin_name: piping.data.plugin_name, - plugin_version: piping.data.plugin_version, - previous_index: id === -1 ? null : id, - title: titleChange, - plugin_parameter_defaults: defaults, - }; - mappedArr.push(treeObl); - }); - - const result = { - name: `${creatingPipeline.pipelineName}`, - authors: pipeline.data.authors, - locked: pipeline.data.locked, - description: pipeline.data.description, - plugin_tree: JSON.stringify(mappedArr), - }; - - const { pipelineInstance } = await generatePipelineWithData(result); - setCreatingPipeline({ - ...creatingPipeline, - loading: false, - }); - if (pipelineInstance) { - handleDispatchPipelines([pipelineInstance, ...pipelines]); - } - } catch (error: any) { - setCreatingPipeline({ - ...creatingPipeline, - error: error.response.data, - loading: false, - }); - } - }; - return ( -
- { - event.key === "Enter" && handlePipelineCreate(); - }} - onChange={(_event, value) => - setCreatingPipeline({ - ...creatingPipeline, - pipelineName: value, - error: {}, - }) - } - /> - - - {creatingPipeline.loading && } - {Object.keys(creatingPipeline.error).length > 0 && ( - - - - )} -
- ); -}; - -export default CreatingPipeline; - -const pluginParameterDefaults = (parameters: any[], id: number, input: any) => { - const currentInput = input[id]; - - const defaults = []; - - if (currentInput && currentInput.length > 0) { - defaults.push(...currentInput); - } else { - for (let i = 0; i < parameters.length; i++) { - const parameter = parameters[i]; - if (parameter.plugin_piping_id === id) { - defaults.push({ - name: parameter.param_name, - default: parameter.value, - }); - } - } - } - - return defaults; -}; diff --git a/src/components/Pipelines/GeneralCompute.tsx b/src/components/Pipelines/GeneralCompute.tsx deleted file mode 100644 index 608f75a70..000000000 --- a/src/components/Pipelines/GeneralCompute.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, { useContext } from "react"; -import ChrisAPIClient from "../../api/chrisapiclient"; -import { PipelineContext } from "../CreateFeed/context"; -import { PipelineTypes } from "../CreateFeed/types/pipeline"; -import ListCompute from "./ListCompute"; - -type NewType = { - currentPipelineId: number; - handleSetGeneralCompute: ( - currentPipelineId: number, - computeEnv: string, - ) => void; -}; - -function GeneralCompute({ currentPipelineId }: NewType) { - const { state, dispatch } = useContext(PipelineContext); - const [computes, setComputes] = React.useState([]); - - const generalCompute = state.pipelineData[currentPipelineId].generalCompute; - - React.useEffect(() => { - async function fetchCompute() { - const client = ChrisAPIClient.getClient(); - const computeResourceList = await client.getComputeResources({ - limit: 100, - offset: 0, - }); - setComputes(computeResourceList.data); - } - - fetchCompute(); - }, []); - - const dispatchFn = (item: any) => { - dispatch({ - type: PipelineTypes.SetGeneralCompute, - payload: { currentPipelineId, computeEnv: item.name }, - }); - }; - - return ( -
- -
- ); -} - -export default GeneralCompute; diff --git a/src/components/Pipelines/ListCompute.tsx b/src/components/Pipelines/ListCompute.tsx deleted file mode 100644 index cf9539730..000000000 --- a/src/components/Pipelines/ListCompute.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { List, Avatar, Checkbox } from "antd"; -import { stringToColour } from "../CreateFeed/utils"; - -const ListCompute = ({ - computeList, - generalCompute, - dispatchFn, -}: { - computeList: any[]; - generalCompute?: string; - dispatchFn: (item: any) => void; -}) => { - return ( - <> - 0 ? computeList : []} - renderItem={(item: any) => { - return ( - - - { - dispatchFn(item); - }} - /> - - - } - title={item.name} - description={item.description} - /> - - ); - }} - /> - - ); -}; - -export default ListCompute; diff --git a/src/components/Pipelines/NodeData.tsx b/src/components/Pipelines/NodeData.tsx deleted file mode 100644 index a873fb025..000000000 --- a/src/components/Pipelines/NodeData.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { HierarchyPointNode } from "d3-hierarchy"; -import { select } from "d3-selection"; -import React, { useContext, useRef } from "react"; -import { TreeNode } from "../../api/common"; -import { SinglePipeline } from "../CreateFeed/types/pipeline"; -import { fetchComputeInfo, stringToColour } from "../CreateFeed/utils"; -import { ThemeContext } from "../DarkTheme/useTheme"; - -export interface Point { - x: number; - y: number; -} - -type NodeProps = { - state: SinglePipeline; - data: TreeNode; - parent: HierarchyPointNode | null; - position: Point; - orientation: string; - handleNodeClick: ( - pluginName: number, - pipelineId: number, - plugin_id: number, - ) => void; - currentPipelineId: number; - handleSetCurrentNodeTitle: ( - currentPipelineId: number, - currentNode: number, - title: string, - ) => void; - handleSetPipelineEnvironments: ( - pipelineId: number, - computeEnvData: { - [x: number]: { - computeEnvs: any[]; - currentlySelected: any; - }; - }, - ) => void; -}; - -const setNodeTransform = (orientation: string, position: Point) => { - return orientation === "horizontal" - ? `translate(${position.y},${position.x})` - : `translate(${position.x}, ${position.y})`; -}; -const DEFAULT_NODE_CIRCLE_RADIUS = 12; - -const NodeData = (props: NodeProps) => { - const { isDarkTheme } = useContext(ThemeContext); - const nodeRef = useRef(null); - const textRef = useRef(null); - const { - data, - position, - orientation, - handleNodeClick, - currentPipelineId, - state, - handleSetPipelineEnvironments, - } = props; - const { computeEnvs, title, currentNode, pluginPipings } = state; - - const root = pluginPipings?.[0]; - - let currentId = NaN; - if (data.previous_id && root) { - currentId = data.id - root.data.id; - } else { - currentId = 0; - } - - let currentComputeEnv = ""; - if (computeEnvs?.[data.id]) { - currentComputeEnv = computeEnvs[data.id].currentlySelected; - } - - const titleName = title?.[data.id]; - - const applyNodeTransform = (transform: string, opacity = 1) => { - select(nodeRef.current) - .attr("transform", transform) - .style("opacity", opacity); - - select(textRef.current).attr("transform", "translate(-30, 30)"); - }; - - React.useEffect(() => { - const nodeTransform = setNodeTransform(orientation, position); - applyNodeTransform(nodeTransform); - }, [orientation, position, applyNodeTransform]); - - const handleSetComputeEnvironmentsWrap = React.useCallback( - (computeEnvData: { - [x: number]: { - computeEnvs: any[]; - currentlySelected: any; - }; - }) => { - handleSetPipelineEnvironments(currentPipelineId, computeEnvData); - }, - [currentPipelineId, handleSetPipelineEnvironments], - ); - - React.useEffect(() => { - async function fetchComputeEnvironments() { - const computeEnvData = await fetchComputeInfo(data.plugin_id, data.id); - if (computeEnvData) { - handleSetComputeEnvironmentsWrap(computeEnvData); - } - } - - fetchComputeEnvironments(); - }, [data, handleSetComputeEnvironmentsWrap]); - - const textLabel = ( - - - {currentId}:{data.id}:{titleName ? titleName : data.title} - - - ); - - const strokeColor = isDarkTheme ? "white" : "#F0AB00"; - - return ( - { - if (data) handleNodeClick(data.id, currentPipelineId, data.plugin_id); - }} - > - - {textLabel} - - ); -}; - -export default NodeData; diff --git a/src/components/Pipelines/TitleChange.tsx b/src/components/Pipelines/TitleChange.tsx deleted file mode 100644 index a9e12fae2..000000000 --- a/src/components/Pipelines/TitleChange.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { TextInput } from "@patternfly/react-core"; -import React from "react"; -import { SinglePipeline } from "../CreateFeed/types/pipeline"; -import Check from "@patternfly/react-icons/dist/esm/icons/check-icon"; -import Edit from "@patternfly/react-icons/dist/esm/icons/edit-icon"; -import Close from "@patternfly/react-icons/dist/esm/icons/close-icon"; -import { PluginPiping } from "@fnndsc/chrisapi"; - -const TitleChange = ({ - currentPipelineId, - state, - handleSetCurrentNodeTitle, - selectedPlugin, -}: { - currentPipelineId: number; - state: SinglePipeline; - handleSetCurrentNodeTitle: ( - currentPipelineId: number, - currentNode: number, - title: string, - ) => void; - selectedPlugin?: PluginPiping; -}) => { - const iconFontSize = { - fontSize: "1.25rem", - }; - const [edit, setEdit] = React.useState(false); - const [value, setValue] = React.useState(""); - const { title, currentNode } = state; - const handleCorrectInput = () => { - setEdit(false); - if (currentNode) - handleSetCurrentNodeTitle(currentPipelineId, currentNode, value); - }; - - return ( -
- { - setValue(value); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - handleCorrectInput(); - } - }} - /> - {!edit && ( - { - setEdit(true); - }} - /> - )} - {edit && ( - <> - - { - setEdit(false); - }} - style={{ - ...iconFontSize, - }} - /> - - )} -
- ); -}; -export default TitleChange; diff --git a/src/components/Pipelines/Tree.tsx b/src/components/Pipelines/Tree.tsx deleted file mode 100644 index d593f14c3..000000000 --- a/src/components/Pipelines/Tree.tsx +++ /dev/null @@ -1,362 +0,0 @@ -import { Spin } from "antd"; -import { hierarchy, tree } from "d3-hierarchy"; -import { event, select } from "d3-selection"; -import { linkVertical } from "d3-shape"; -import { zoom as d3Zoom, zoomIdentity } from "d3-zoom"; -import React, { - Fragment, - useEffect, - useContext, - useRef, - useState, -} from "react"; -import { TreeNode, getFeedTree } from "../../api/common"; -import { SpinContainer } from "../Common"; -import { SinglePipeline } from "../CreateFeed/types/pipeline"; -import { ThemeContext } from "../DarkTheme/useTheme"; -import TransitionGroupWrapper from "../FeedTree/TransitionGroupWrapper"; -import { getTsNodesWithPipings } from "../FeedTree/data"; -import { type Point, type Separation } from "../FeedTree/data"; -import useSize from "../FeedTree/useSize"; -import NodeData from "./NodeData"; - -const nodeSize = { x: 200, y: 80 }; -const svgClassName = "feed-tree__svg"; -const graphClassName = "feed-tree__graph"; -const scale = 1; - -export interface TreeProps { - state: SinglePipeline; - currentPipelineId: number; - handleSetCurrentNode: (pipelineId: number, currentNode: number) => void; - handleNodeClick: ( - nodeName: number, - pipelineId: number, - plugin_id: number, - ) => void; - handleSetCurrentNodeTitle: ( - currentPipelineId: number, - currentNode: number, - title: string, - ) => void; - handleSetPipelineEnvironments: ( - pipelineId: number, - computeEnvData: { - [x: number]: { - computeEnvs: any[]; - currentlySelected: any; - }; - }, - ) => void; - - translate?: Point; - scaleExtent: { - min: number; - max: number; - }; - zoom?: number; - nodeSize?: { - x: number; - y: number; - }; - separation?: Separation; - orientation?: "horizontal" | "vertical"; -} - -const Tree = (props: TreeProps) => { - const divRef = useRef(null); - const [translate, setTranslate] = React.useState({ - x: 0, - y: 0, - }); - const size = useSize(divRef); - const { currentPipelineId, state, handleSetCurrentNode } = props; - const { pluginPipings, pipelinePlugins, pluginParameters } = state; - const [loading, setLoading] = React.useState(false); - const { - handleNodeClick, - handleSetCurrentNodeTitle, - handleSetPipelineEnvironments, - } = props; - - const [data, setData] = React.useState(); - const [tsIds, setTsIds] = React.useState(); - const { zoom, scaleExtent } = props; - const bindZoomListener = React.useCallback(() => { - const svg = select(`.${svgClassName}`); - const g = select(`.${graphClassName}`); - - svg.call( - ///@ts-ignore - d3Zoom().transform, - zoomIdentity.translate(translate.x, translate.y).scale(zoom), - ); - - svg.call( - //@ts-ignore - d3Zoom() - .scaleExtent([scaleExtent.min, scaleExtent.max]) - .on("zoom", () => { - g.attr("transform", event.transform); - }), - ); - }, [zoom, scaleExtent, translate.x, translate.y]); - - React.useEffect(() => { - bindZoomListener(); - }, [bindZoomListener]); - - const handleSetCurrentNodeCallback = React.useCallback( - (id: number) => { - handleSetCurrentNode(currentPipelineId, id); - }, - [currentPipelineId, handleSetCurrentNode], - ); - - React.useEffect(() => { - if (pluginPipings) { - setLoading(true); - const tree = getFeedTree(pluginPipings); - getTsNodesWithPipings(pluginPipings, pluginParameters).then((tsIds) => { - setTsIds(tsIds); - }); - setData(tree); - } - if (pipelinePlugins) { - const defaultPlugin = pipelinePlugins[0]; - const defaultPluginId = pluginPipings?.filter((piping: any) => { - if (piping.data.plugin_id === defaultPlugin.data.id) { - return piping.data.id; - } - }); - - if (defaultPluginId) { - handleSetCurrentNodeCallback(defaultPluginId[0].data.id); - } - } - setLoading(false); - }, [ - pluginPipings, - pipelinePlugins, - pluginParameters, - currentPipelineId, - handleSetCurrentNodeCallback, - pipelinePlugins?.[0], - ]); - - React.useEffect(() => { - //@ts-ignore - if (size?.width) { - setTranslate({ - //@ts-ignore - x: size.width / 2.5, - //@ts-ignore - y: size.height / 3, - }); - } - }, [size]); - - const generateTree = () => { - const d3Tree = tree().nodeSize([nodeSize.x, nodeSize.y]); - let nodes; - let links: any[] = []; - let newLinks: any[] = []; - if (data) { - const rootNode = d3Tree(hierarchy(data[0])); - nodes = rootNode.descendants(); - links = rootNode.links(); - const newLinksToAdd: any[] = []; - - if (tsIds) { - for (const link of links) { - const targetId = link.target.data.id; - const sourceId = link.target.data.id; - - if (targetId && sourceId && (tsIds[targetId] || tsIds[sourceId])) { - // tsPlugin found - let topologicalLink: any; - - if (tsIds[targetId]) { - topologicalLink = link.target; - } else { - topologicalLink = link.source; - } - - const parents = tsIds[topologicalLink.data.id]; - const dict: any = {}; - - for (const link of links) { - for (let i = 0; i < parents.length; i++) { - if ( - link.source.data.id === parents[i] && - !dict[link.source.data.id] - ) { - dict[link.source.data.id] = link.source; - } else if ( - link.target.data.id === parents[i] && - !dict[link.target.data.id] - ) { - dict[link.target.data.id] = link.target; - } - } - } - - for (const i in dict) { - newLinksToAdd.push({ - source: dict[i], - target: topologicalLink, - }); - } - } - } - } - newLinks = [...links, ...newLinksToAdd]; - } - return { nodes, newLinks: newLinks }; - }; - - const { nodes, newLinks: links } = generateTree(); - - return ( - <> -
- {loading ? ( - - ) : translate.x > 0 && translate.y > 0 ? ( - - Pipeline Tree - - {links?.map((linkData, i) => { - return ( - - ); - })} - {nodes?.map(({ data, x, y, parent }, i) => { - return ( - - ); - })} - - - ) : ( - Drawing out the pipelines tree - )} -
- - ); -}; - -Tree.defaultProps = { - orientation: "vertical", - scaleExtent: { min: 0.1, max: 1 }, - zoom: 1, - nodeSize: { x: 120, y: 80 }, -}; - -interface LinkProps { - linkData: any; - key: string; - orientation: "vertical"; -} - -type LinkState = { - initialStyle: { - opacity: number; - }; -}; - -const LinkData: React.FC = ({ linkData }) => { - const { isDarkTheme } = useContext(ThemeContext); - const linkRef = useRef(null); - const [initialStyle] = useState({ opacity: 1 }); - const nodeRadius = 12; - - useEffect(() => { - applyOpacity(1); - }, []); - - const applyOpacity = ( - opacity: number, - done = () => { - return null; - }, - ) => { - select(linkRef.current).style("opacity", opacity).on("end", done); - }; - - const { source, target } = linkData; - - const drawPath = (ts: boolean) => { - const deltaX = target.x - source.x; - const deltaY = target.y - source.y; - const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY); - const normX = deltaX / dist; - const normY = deltaY / dist; - const sourcePadding = nodeRadius; - const targetPadding = nodeRadius + 4; - const sourceX = source.x + sourcePadding * normX; - const sourceY = source.y + sourcePadding * normY; - const targetX = target.x - targetPadding * normX; - const targetY = target.y - targetPadding * normY; - - if (ts) { - return linkVertical()({ - source: [sourceX, sourceY], - target: [targetX, targetY], - }); - } - return `M${sourceX} ${sourceY} L${targetX} ${targetY}`; - }; - - const ts = target.data.plugin_name === "pl-topologicalcopy"; - - const strokeWidthColor = isDarkTheme ? "#F2F9F9" : "#6A6E73"; - - return ( - - - - ); -}; - -export default Tree; diff --git a/src/components/Pipelines/UploadJson.tsx b/src/components/Pipelines/UploadJson.tsx deleted file mode 100644 index 51312e575..000000000 --- a/src/components/Pipelines/UploadJson.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React from "react"; -import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon"; -import ReactJSON from "react-json-view"; -import { Alert, Button } from "@patternfly/react-core"; -import { PipelineList } from "@fnndsc/chrisapi"; -import { generatePipelineWithData } from "../CreateFeed/utils"; -import ChrisAPIClient from "../../api/chrisapiclient"; -import { UploadJsonProps } from "../CreateFeed/types/pipeline"; - -export const UploadJson = ({ - handleDispatch, -}: { - handleDispatch: (result: UploadJsonProps) => void; -}) => { - const fileOpen = React.useRef(null); - const [fileName, setFileName] = React.useState(""); - const [error, setError] = React.useState({}); - const [pipelineWarning, setPipelineWarning] = React.useState(""); - const [showSuccessIcon, setShowSuccessIcon] = React.useState(false); - - const showOpenFile = () => { - setPipelineWarning(""); - if (fileOpen.current) { - fileOpen.current.click(); - } - }; - - const cleanUp = (event: any) => { - event.target.value = null; - if (fileOpen.current) { - fileOpen.current.value = ""; - } - }; - - const readFile = (file: any, event: any) => { - const reader = new FileReader(); - - reader.onloadend = async () => { - try { - if (reader.result) { - const client = ChrisAPIClient.getClient(); - const result = JSON.parse(reader.result as string); - result["plugin_tree"] = JSON.stringify(result["plugin_tree"]); - setFileName(result.name); - const pipelineInstanceList: PipelineList = await client.getPipelines({ - name: result.name, - }); - if (!pipelineInstanceList.data) { - const { resources, pipelineInstance } = - await generatePipelineWithData(result); - const { parameters, pluginPipings, pipelinePlugins } = resources; - - handleDispatch({ - parameters, - pluginPipings, - pipelinePlugins, - pipelineInstance, - }); - setShowSuccessIcon(true); - cleanUp(event); - } else { - setPipelineWarning( - `pipeline with the name ${result.name} already exists`, - ); - cleanUp(event); - } - } - } catch (error) { - //@ts-ignore - const errorMessage = error.response.data; - cleanUp(event); - setError(errorMessage); - } - }; - if (file) { - reader.readAsText(file); - } - }; - - const handleUpload = (event: any) => { - const file = event?.target.files[0]; - setError({}); - readFile(file, event); - }; - - const keys = Object.keys(error).length; - - const alertStyle = { - marginTop: "1em", - }; - - return ( - <> -
- - {fileName} - {showSuccessIcon && ( - - )} -
- {pipelineWarning && ( - - )} - {keys > 0 && ( -
- -
- )} - - - - ); -}; - -export default UploadJson; diff --git a/src/components/Pipelines/index.ts b/src/components/Pipelines/index.ts deleted file mode 100644 index f04b306de..000000000 --- a/src/components/Pipelines/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import NodeData from "./NodeData"; -import Tree from "./Tree"; -import UploadJson from "./UploadJson"; -import ConfigurationPage from "./ConfigurationPage"; -import GeneralCompute from "./GeneralCompute"; - -export { NodeData, Tree, UploadJson, ConfigurationPage, GeneralCompute }; diff --git a/src/components/PipelinesCopy/GeneralCompute.tsx b/src/components/PipelinesCopy/GeneralCompute.tsx index cd390a3bf..b27941561 100644 --- a/src/components/PipelinesCopy/GeneralCompute.tsx +++ b/src/components/PipelinesCopy/GeneralCompute.tsx @@ -50,7 +50,9 @@ function GeneralCompute() { isExpanded={isExpanded} onToggle={onToggle} toggleText={ - isExpanded ? "Hide All Compute" : "Show All the registered to ChRIS" + isExpanded + ? "Hide All Compute" + : "Show all the compute registered to ChRIS" } > diff --git a/src/components/PipelinesCopy/NodeData.tsx b/src/components/PipelinesCopy/NodeData.tsx index 456b65d47..876c2b050 100644 --- a/src/components/PipelinesCopy/NodeData.tsx +++ b/src/components/PipelinesCopy/NodeData.tsx @@ -1,4 +1,5 @@ import { useQuery } from "@tanstack/react-query"; +import { Alert } from "antd"; import { HierarchyPointNode } from "d3-hierarchy"; import { select } from "d3-selection"; import React, { useContext, useEffect, useRef } from "react"; @@ -129,7 +130,7 @@ const NodeData = (props: NodeProps) => { return ( <> - {isError && {error.message}} + {isError && } {isLoading && ( Fetching the compute environments for this node... )} diff --git a/src/components/PipelinesCopy/Pipelines.css b/src/components/PipelinesCopy/Pipelines.css new file mode 100644 index 000000000..7132a15f2 --- /dev/null +++ b/src/components/PipelinesCopy/Pipelines.css @@ -0,0 +1,4 @@ + +.ant-form-item-label label { + color: #73bcf7 !important; +} \ No newline at end of file diff --git a/src/components/PipelinesCopy/Pipelines.module.css b/src/components/PipelinesCopy/Pipelines.module.css deleted file mode 100644 index 81c804e23..000000000 --- a/src/components/PipelinesCopy/Pipelines.module.css +++ /dev/null @@ -1,4 +0,0 @@ - - label { - color:#1fa7f8 !important; - } diff --git a/src/components/PipelinesCopy/PipelinesComponent.tsx b/src/components/PipelinesCopy/PipelinesComponent.tsx index faa0d334b..73695802b 100644 --- a/src/components/PipelinesCopy/PipelinesComponent.tsx +++ b/src/components/PipelinesCopy/PipelinesComponent.tsx @@ -1,7 +1,7 @@ import { Pipeline } from "@fnndsc/chrisapi"; +import { Grid, GridItem } from "@patternfly/react-core"; import CodeBlockComponent from "./CodeBlockComponent"; import ComputeListForSingleCompute from "./ComputeListForSingleCompute"; -import { Grid, GridItem } from "@patternfly/react-core"; import GeneralCompute from "./GeneralCompute"; import TitleChange from "./TitleChange"; import Tree from "./Tree"; @@ -15,21 +15,22 @@ function PipelinesComponent(props: OwnProps) { return ( <> + - + - + - + diff --git a/src/components/PipelinesCopy/TitleChange.tsx b/src/components/PipelinesCopy/TitleChange.tsx index ba1956ff5..471f02308 100644 --- a/src/components/PipelinesCopy/TitleChange.tsx +++ b/src/components/PipelinesCopy/TitleChange.tsx @@ -1,8 +1,7 @@ import { Pipeline, PluginPiping } from "@fnndsc/chrisapi"; -import { Alert, Button, Input, Space, Form } from "antd"; +import { Alert, Button, Form, Input, Space } from "antd"; import { useContext, useEffect, useState } from "react"; import { PipelineContext, Types } from "./context"; -import styles from "./Pipelines.module.css"; type OwnProps = { currentPipeline: Pipeline; @@ -59,10 +58,7 @@ function TitleChange({ currentPipeline }: OwnProps) { return ( <> - + setValue(e.target.value)} value={value} />