diff --git a/src/components/AddPipeline/AddPipeline.tsx b/src/components/AddPipeline/AddPipeline.tsx index 46d72c7ba..cdce86290 100644 --- a/src/components/AddPipeline/AddPipeline.tsx +++ b/src/components/AddPipeline/AddPipeline.tsx @@ -24,10 +24,12 @@ const AddPipeline = () => { (state) => state.plugin.nodeOperations, ); - const alreadyAvailableInstances = useTypedSelector( - (state) => state.instance.pluginInstances.data, + const { pluginInstances, selectedPlugin } = useTypedSelector( + (state) => state.instance, ); + const alreadyAvailableInstances = pluginInstances.data; + const handleToggle = () => { if (childPipeline) { dispatch({ @@ -38,14 +40,11 @@ const AddPipeline = () => { reactDispatch(getNodeOperations("childPipeline")); }; - const feed = useTypedSelector((state) => state.feed.currentFeed.data); - const { selectedPlugin } = useTypedSelector((state) => state.instance); - const addPipeline = async () => { const id = pipelineToAdd?.data.id; const resources = selectedPipeline?.[id]; - if (selectedPlugin && feed && resources) { + if (selectedPlugin && resources) { const { parameters } = resources; const client = ChrisAPIClient.getClient(); @@ -73,6 +72,7 @@ const AddPipeline = () => { nodes_info: JSON.stringify(nodes_info), }); + // Use the pagination helper here const pluginInstances = await workflow.getPluginInstances({ limit: 1000, }); @@ -100,6 +100,14 @@ const AddPipeline = () => { mutationFn: () => addPipeline(), }); + React.useEffect(() => { + if (mutation.isSuccess) { + setTimeout(() => { + handleToggle(); + }, 1000); + } + }, [mutation.isSuccess]); + React.useEffect(() => { const el = document.querySelector("#indicators"); diff --git a/src/components/DownloadNode/index.tsx b/src/components/DownloadNode/index.tsx new file mode 100644 index 000000000..d628e7b49 --- /dev/null +++ b/src/components/DownloadNode/index.tsx @@ -0,0 +1,145 @@ +import { Button } from "@patternfly/react-core"; +import FaDownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon"; +import { useMutation } from "@tanstack/react-query"; +import { Alert } from "antd"; +import { useEffect } from "react"; +import { useDispatch } from "react-redux"; +import ChrisAPIClient from "../../api/chrisapiclient"; +import { fetchComputeInfo, fetchResources } from "../../api/common"; +import { useTypedSelector } from "../../store/hooks"; +import { + getPluginInstancesSuccess, + getSelectedPlugin, +} from "../../store/pluginInstance/actions"; +import { getPluginInstanceStatusRequest } from "../../store/resources/actions"; +import { SpinContainer } from "../Common"; +import { PerPipelinePayload } from "../PipelinesCopy/context"; + +function DownloadNode() { + const { pluginInstances, selectedPlugin } = useTypedSelector( + (state) => state.instance, + ); + const reactDispatch = useDispatch(); + + const alreadyAvailableInstances = pluginInstances.data; + + async function fetchPipelines() { + const client = ChrisAPIClient.getClient(); + + try { + const pipelineList = await client.getPipelines({ + name: "zip v20240311", + }); + + const pipelines = pipelineList.getItems(); + + if (pipelines && pipelines.length > 0) { + const pipeline = pipelines[0]; + const { id } = pipeline.data; + + const data: PerPipelinePayload = await fetchResources(pipeline); + const { parameters, pluginPipings } = data; + + const nodes_info = client.computeWorkflowNodesInfo(parameters.data); + + // This is an assumption as I know the zip file will only have on pluginPiping + const piping = pluginPipings[0]; + const computeEnvPayload = await fetchComputeInfo( + piping.data.plugin_id, + `${piping.data.id}`, + ); + + for (const node of nodes_info) { + const activeNode = computeEnvPayload?.[node.piping_id]; + + if (activeNode) { + const compute_node = activeNode.currentlySelected; + node.compute_resource_name = compute_node; + } + } + + const workflow = await client.createWorkflow(id, { + previous_plugin_inst_id: selectedPlugin?.data.id, // Ensure selectedPlugin is defined + nodes_info: JSON.stringify(nodes_info), + }); + + const pluginInstances = await workflow.getPluginInstances({ + limit: 1000, + }); + + const instanceItems = pluginInstances.getItems(); + + if ( + instanceItems && + instanceItems.length > 0 && + alreadyAvailableInstances + ) { + const firstInstance = instanceItems[instanceItems.length - 1]; + const completeList = [...alreadyAvailableInstances, ...instanceItems]; + + // Assuming reactDispatch, getSelectedPlugin, getPluginInstanceStatusSuccess, and getPluginInstanceStatusRequest are defined elsewhere + reactDispatch(getSelectedPlugin(firstInstance)); + + const pluginInstanceObj = { + selected: firstInstance, + pluginInstances: completeList, + }; + + reactDispatch(getPluginInstancesSuccess(pluginInstanceObj)); + reactDispatch(getPluginInstanceStatusRequest(pluginInstanceObj)); + } + } else { + throw new Error( + "The pipeline to zip is not registered. Please contact an admin", + ); + } + return pipelines; + } catch (error) { + throw error; + } + } + + const mutation = useMutation({ + mutationFn: () => fetchPipelines(), + }); + + useEffect(() => { + if (mutation.isSuccess) { + setTimeout(() => { + mutation.reset(); + }, 1000); + } + }, [mutation.isSuccess]); + + return ( + <> + + {mutation.isError || mutation.isSuccess || mutation.isPending ? ( +
+ {mutation.isError && ( + + )} + {mutation.isSuccess && ( + + )} + {mutation.isPending && ( + + )} +
+ ) : null} + + ); +} + +export default DownloadNode; diff --git a/src/components/FeedOutputBrowser/FileBrowser.tsx b/src/components/FeedOutputBrowser/FileBrowser.tsx index e64fd55f7..9af2f2a17 100644 --- a/src/components/FeedOutputBrowser/FileBrowser.tsx +++ b/src/components/FeedOutputBrowser/FileBrowser.tsx @@ -133,7 +133,7 @@ const FileBrowser = (props: FileBrowserProps) => { } else { toggleAnimation(); dispatch(setSelectedFile(item)); - !drawerState["preview"].open && dispatch(setFilePreviewPanel()); + !drawerState.preview.open && dispatch(setFilePreviewPanel()); } }; @@ -239,10 +239,10 @@ const FileBrowser = (props: FileBrowserProps) => { ) : ( {items.map((item: string | FeedFile, index) => { - let type; - let icon; - let fsize; - let fileName; + let type: string; + let icon: React.ReactNode; + let fsize: string; + let fileName: string; type = "UNKNOWN FORMAT"; const isPreviewing = selectedFile === item; let currentStatus = 0; diff --git a/src/components/IconContainer/index.tsx b/src/components/IconContainer/index.tsx index 4e12a64d3..8c8cb9129 100644 --- a/src/components/IconContainer/index.tsx +++ b/src/components/IconContainer/index.tsx @@ -71,7 +71,7 @@ const IconContainer = () => { let prefix = ""; if (action === "merge") { prefix = "Merge of "; - } else if (action === "download") { + } else if (action === "") { prefix = "archive-"; } else { prefix = ""; @@ -208,7 +208,7 @@ const IconContainer = () => { let newFeedName = feedNames.toString().replace(/[, ]+/g, "_"); let createdFeed: Feed | null = null; - if (operation === "download") { + if (operation === "archive") { newFeedName = `archive-${newFeedName}`; newFeedName = newFeedName.substring(0, 100); newFeedName = feedName === "" ? newFeedName : feedName; @@ -286,11 +286,11 @@ const IconContainer = () => { currentAction === "share" && shareFeedMutation.mutate({ bulkSelect, sharePublically, feedName }); currentAction === "delete" && deleteFeedMutation.mutate(bulkSelect); - currentAction === "download" && + currentAction === "archive" && handleDownloadMutation.mutate({ feedList: bulkSelect, feedName, - operation: "download", + operation: "archive", }); currentAction === "merge" && handleDownloadMutation.mutate({ @@ -304,7 +304,7 @@ const IconContainer = () => { return ( - {["download", "merge", "duplicate", "share", "delete"].map((action) => { + {["archive", "merge", "duplicate", "share", "delete"].map((action) => { return ( , + archive: , merge: , duplicate: , share: , diff --git a/src/components/NodeDetails/NodeDetails.tsx b/src/components/NodeDetails/NodeDetails.tsx index 733ea5cfa..3f81e2e7c 100644 --- a/src/components/NodeDetails/NodeDetails.tsx +++ b/src/components/NodeDetails/NodeDetails.tsx @@ -10,7 +10,7 @@ import { Grid, GridItem, } from "@patternfly/react-core"; -import { EyeIcon, CalendarAltIcon } from "@patternfly/react-icons"; +import { CalendarAltIcon, EyeIcon } from "@patternfly/react-icons"; import React, { Fragment, ReactNode } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { useNavigate } from "react-router"; @@ -20,16 +20,17 @@ import { AddNodeProvider } from "../AddNode/context"; import AddPipeline from "../AddPipeline/AddPipeline"; import GraphNodeContainer from "../AddTsNode"; import { SpinContainer } from "../Common"; -import { PipelineProvider } from "../PipelinesCopy/context"; +import { isPlVisualDataset } from "../DatasetRedirect/getDatasets"; import DeleteNode from "../DeleteNode"; +import DownloadNode from "../DownloadNode"; import FeedNote from "../FeedDetails/FeedNote"; +import { PipelineProvider } from "../PipelinesCopy/context"; import "./NodeDetails.css"; import PluginLog from "./PluginLog"; import PluginTitle from "./PluginTitle"; import Status from "./Status"; import StatusTitle from "./StatusTitle"; import { getErrorCodeMessage } from "./utils"; -import { isPlVisualDataset } from "../DatasetRedirect/getDatasets"; interface INodeState { plugin?: Plugin; @@ -55,7 +56,7 @@ const NodeDetails: React.FC = () => { const drawerState = useTypedSelector((state) => state.drawers); const { plugin, instanceParameters, pluginParameters } = nodeState; - const [isExpanded, setIsExpanded] = React.useState(true); + const [isExpanded, setIsExpanded] = React.useState(false); const [isErrorExpanded, setisErrorExpanded] = React.useState(false); React.useEffect(() => { @@ -253,6 +254,14 @@ const NodeDetails: React.FC = () => { )} + + + + + + + + { // Jennings: hastily adding an extra button here. // IMO the Node Details pane should be cleaned up. diff --git a/src/components/Pacs/pfdcmClient.tsx b/src/components/Pacs/pfdcmClient.tsx index d678cde56..4ab919136 100644 --- a/src/components/Pacs/pfdcmClient.tsx +++ b/src/components/Pacs/pfdcmClient.tsx @@ -303,6 +303,7 @@ class PfdcmClient { !imagestatus.push && images.pushed === 0 && requestedFiles && + requestedFiles > 0 && images.packed === +requestedFiles; newImageStatus[1].icon = showPushDetails && ; @@ -327,6 +328,7 @@ class PfdcmClient { const showRegisterDetails = requestedFiles && + requestedFiles > 0 && images.pushed === +requestedFiles && !imagestatus.register && images.registered === 0; diff --git a/src/store/pluginInstance/reducer.ts b/src/store/pluginInstance/reducer.ts index cd2b4ff46..efba5da93 100644 --- a/src/store/pluginInstance/reducer.ts +++ b/src/store/pluginInstance/reducer.ts @@ -96,7 +96,8 @@ const reducer: Reducer = ( }, selectedPlugin: action.payload, }; - } else return { ...state }; + } + return { ...state }; } case PluginInstanceTypes.ADD_NODE_REQUEST: { @@ -118,16 +119,16 @@ const reducer: Reducer = ( }, loadingAddNode: false, }; - } else - return { - ...state, - pluginInstances: { - data: action.payload, - error: "", - loading: false, - }, - loadingAddNode: false, - }; + } + return { + ...state, + pluginInstances: { + data: action.payload, + error: "", + loading: false, + }, + loadingAddNode: false, + }; } case PluginInstanceTypes.ADD_SPLIT_NODES_SUCCESS: { @@ -145,7 +146,8 @@ const reducer: Reducer = ( loading: false, }, }; - } else return state; + } + return state; } case PluginInstanceTypes.DELETE_NODE_SUCCESS: {