Skip to content

Commit f2d220f

Browse files
committed
feat(frontend): button to select output tasks
1 parent 32cae44 commit f2d220f

2 files changed

Lines changed: 129 additions & 58 deletions

File tree

Lines changed: 108 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { useEffect, useState } from "react";
1+
import { useEffect, useMemo, useState } from "react";
22
import { useLazyLoadQuery } from "react-relay";
33
import { TaskInfo } from "workflows-lib/lib/components/workflow/TaskInfo";
4-
import { Artifact, Task, TaskStatus } from "workflows-lib/lib/types";
4+
import { Artifact, Task, TaskNode, TaskStatus } from "workflows-lib/lib/types";
55
import WorkflowRelay, { workflowRelayQuery } from "./WorkflowRelay";
66
import { isWorkflowWithTasks } from "../utils";
77
import { Visit } from "@diamondlightsource/sci-react-ui";
88
import { WorkflowRelayQuery as WorkflowRelayQueryType } from "./__generated__/WorkflowRelayQuery.graphql";
9+
import { Box, ToggleButton } from "@mui/material";
10+
import { buildTaskTree } from "workflows-lib/lib/utils/tasksFlowUtils";
911

1012
interface SingleWorkflowViewProps {
1113
visit: Visit;
@@ -22,49 +24,124 @@ export default function SingleWorkflowView({
2224
visit: visit,
2325
name: workflowName,
2426
});
25-
const workflow = data.workflow;
2627

2728
const [artifactList, setArtifactList] = useState<Artifact[]>([]);
29+
const [outputSelected, setOutputSelected] = useState<boolean>(false);
30+
const [outputTasks, setOutputTasks] = useState<string[]>([]);
31+
const [selectedTasks, setSelectedTasks] = useState<string[]>([]);
32+
const [fetchedTasks, setFetchedTasks] = useState<Task[]>([]);
33+
34+
const taskTree = useMemo(() => buildTaskTree(fetchedTasks), [fetchedTasks]);
35+
36+
const handleSelectOutput = () => {
37+
setOutputSelected(!outputSelected);
38+
};
2839

2940
useEffect(() => {
30-
let fetchedTasks: Task[] = [];
3141
if (data.workflow.status && isWorkflowWithTasks(data.workflow.status)) {
32-
fetchedTasks = data.workflow.status.tasks.map((task) => ({
33-
id: task.id,
34-
name: task.name,
35-
status: task.status as TaskStatus,
36-
artifacts: task.artifacts.map((artifact) => ({
37-
...artifact,
38-
parentTask: task.name,
39-
key: `${task.name}-${artifact.name}`,
40-
})),
41-
workflow: workflowName,
42-
instrumentSession: visit,
43-
stepType: task.stepType,
44-
}));
42+
setFetchedTasks(
43+
data.workflow.status.tasks.map((task) => ({
44+
id: task.id,
45+
name: task.name,
46+
status: task.status as TaskStatus,
47+
depends: [...task.depends],
48+
artifacts: task.artifacts.map((artifact) => ({
49+
...artifact,
50+
parentTask: task.name,
51+
key: `${task.name}-${artifact.name}`,
52+
})),
53+
workflow: workflowName,
54+
instrumentSession: visit,
55+
stepType: task.stepType,
56+
}))
57+
);
4558
}
59+
}, [data.workflow.status]);
4660

47-
const filteredTasks = tasknames?.length
48-
? tasknames
61+
useEffect(() => {
62+
const filteredTasks = selectedTasks?.length
63+
? selectedTasks
4964
.map((name) => fetchedTasks.find((task) => task.name === name))
5065
.filter((task): task is Task => !!task)
5166
: fetchedTasks;
5267
setArtifactList(filteredTasks.flatMap((task) => task.artifacts));
53-
}, [workflow, tasknames, data.workflow.status]);
68+
}, [selectedTasks, fetchedTasks]);
69+
70+
useEffect(() => {
71+
let newOutputTasks: string[] = [];
72+
const traverse = (tasks: TaskNode[]) => {
73+
const sortedTasks = [...tasks].sort((a, b) =>
74+
a.name.localeCompare(b.name)
75+
);
76+
sortedTasks.forEach((taskNode) => {
77+
if (
78+
taskNode.children &&
79+
taskNode.children.length == 0 &&
80+
!newOutputTasks.includes(taskNode.name)
81+
) {
82+
newOutputTasks.push(taskNode.name);
83+
} else if (taskNode.children && taskNode.children.length > 0) {
84+
traverse(taskNode.children);
85+
}
86+
});
87+
};
88+
traverse(taskTree);
89+
setOutputTasks(newOutputTasks);
90+
}, [taskTree]);
91+
92+
useEffect(() => {
93+
if (outputSelected) {
94+
setSelectedTasks(outputTasks);
95+
} else {
96+
setSelectedTasks(tasknames ? tasknames : []);
97+
}
98+
}, [outputTasks, outputSelected, tasknames]);
5499

55100
return (
56101
<>
57-
<WorkflowRelay
58-
key={workflowName}
59-
visit={visit}
60-
workflowName={workflowName}
61-
workflowLink
62-
expanded={true}
63-
highlightedTaskNames={tasknames}
64-
/>
65-
<div style={{ width: "100%", marginTop: "1rem" }}>
66-
<TaskInfo artifactList={artifactList} />
67-
</div>
102+
<Box
103+
sx={{
104+
position: "relative",
105+
display: "inline-flex",
106+
alignItems: "flex-start",
107+
width: "100%",
108+
height: "100%",
109+
}}
110+
>
111+
<Box
112+
sx={{
113+
display: "flex",
114+
flexDirection: "row",
115+
width: "100%",
116+
height: "100%",
117+
gap: 2,
118+
}}
119+
>
120+
<ToggleButton
121+
value="output"
122+
aria-label="output"
123+
selected={outputSelected}
124+
onClick={handleSelectOutput}
125+
sx={{ position: "absolute", left: "-100px" }}
126+
>
127+
OUTPUT
128+
</ToggleButton>
129+
<WorkflowRelay
130+
key={workflowName}
131+
visit={visit}
132+
workflowName={workflowName}
133+
fetchedTasks={fetchedTasks}
134+
workflowLink
135+
expanded={true}
136+
highlightedTaskNames={selectedTasks}
137+
/>
138+
</Box>
139+
</Box>
140+
{tasknames && (
141+
<div style={{ width: "100%", marginTop: "1rem" }}>
142+
<TaskInfo artifactList={artifactList} />
143+
</div>
144+
)}
68145
</>
69146
);
70147
}

frontend/relay-workflows-lib/lib/components/WorkflowRelay.tsx

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import { graphql, useLazyLoadQuery } from "react-relay";
22
import { ResizableBox } from "react-resizable";
33
import "react-resizable/css/styles.css";
44
import { Box } from "@mui/material";
5-
65
import { TasksFlow, WorkflowAccordion } from "workflows-lib";
7-
import type { Task, TaskStatus, WorkflowStatus } from "workflows-lib";
86
import { Visit, visitToText } from "@diamondlightsource/sci-react-ui";
7+
import type { Task, WorkflowStatus } from "workflows-lib";
98
import { useSearchParams } from "react-router-dom";
109
import RetriggerWorkflow from "./RetriggerWorkflow";
1110
import { WorkflowRelayQuery as WorkflowRelayQueryType } from "./__generated__/WorkflowRelayQuery.graphql";
@@ -106,6 +105,7 @@ export const workflowRelayQuery = graphql`
106105
interface WorkflowRelayProps {
107106
visit: Visit;
108107
workflowName: string;
108+
fetchedTasks: Task[];
109109
highlightedTaskNames?: string[];
110110
workflowLink?: boolean;
111111
expanded?: boolean;
@@ -115,6 +115,7 @@ interface WorkflowRelayProps {
115115
const WorkflowRelay: React.FC<WorkflowRelayProps> = ({
116116
visit,
117117
workflowName,
118+
fetchedTasks,
118119
highlightedTaskNames,
119120
workflowLink,
120121
expanded,
@@ -133,9 +134,11 @@ const WorkflowRelay: React.FC<WorkflowRelayProps> = ({
133134

134135
const [searchParams, setSearchParams] = useSearchParams();
135136
const statusText = data.workflow.status?.__typename ?? "Unknown";
136-
const [selectedTaskNames, setSelectedTaskNames] = useState<string[]>(highlightedTaskNames ?? [])
137+
const [selectedTaskNames, setSelectedTaskNames] = useState<string[]>(
138+
highlightedTaskNames ?? []
139+
);
137140

138-
const taskParam = searchParams.get("tasks")
141+
const taskParam = searchParams.get("tasks");
139142
const tasknames = useMemo(() => {
140143
if (!taskParam) return [];
141144
try {
@@ -149,32 +152,21 @@ const WorkflowRelay: React.FC<WorkflowRelayProps> = ({
149152
setSelectedTaskNames(tasknames);
150153
}, [tasknames]);
151154

152-
const tasks: Task[] =
153-
data.workflow.status && "tasks" in data.workflow.status
154-
? data.workflow.status.tasks.map((task) => ({
155-
id: task.id,
156-
name: task.name,
157-
status: task.status as TaskStatus,
158-
depends: [...task.depends],
159-
artifacts: task.artifacts.map ((artifact) => ({
160-
...artifact,
161-
parentTask: task.name
162-
})),
163-
workflow: data.workflow.name,
164-
instrumentSession: data.workflow.visit as Visit,
165-
stepType: task.stepType,
166-
}))
167-
: [];
168-
155+
useEffect(() => {
156+
setSelectedTaskNames(highlightedTaskNames ? highlightedTaskNames : []);
157+
}, [highlightedTaskNames]);
158+
169159
const onNavigate = React.useCallback(
170160
(path: string, event?: React.MouseEvent) => {
171161
const taskName = String(path.split("/").filter(Boolean).pop());
172162
const isCtrl = event?.ctrlKey || event?.metaKey;
173163

174164
let updatedTasks: string[];
175-
165+
176166
if (isCtrl) {
177-
updatedTasks = tasknames.includes(taskName) ? tasknames.filter(name => name !== taskName) : [...tasknames, taskName];
167+
updatedTasks = tasknames.includes(taskName)
168+
? tasknames.filter((name) => name !== taskName)
169+
: [...tasknames, taskName];
178170
} else {
179171
updatedTasks = [taskName];
180172
}
@@ -188,9 +180,11 @@ const WorkflowRelay: React.FC<WorkflowRelayProps> = ({
188180
if (workflowNameURL !== workflowName) {
189181
void navigate(`/workflows/${visitToText(visit)}/${workflowName}`);
190182
}
191-
setSearchParams(params)
192-
}, [tasknames, searchParams, setSearchParams])
193-
183+
setSearchParams(params);
184+
},
185+
[tasknames, searchParams, setSearchParams]
186+
);
187+
194188
return (
195189
<Box
196190
sx={{
@@ -235,7 +229,7 @@ const WorkflowRelay: React.FC<WorkflowRelayProps> = ({
235229
>
236230
<TasksFlow
237231
workflowName={data.workflow.name}
238-
tasks={tasks}
232+
tasks={fetchedTasks}
239233
highlightedTaskNames={selectedTaskNames}
240234
onNavigate={onNavigate}
241235
></TasksFlow>

0 commit comments

Comments
 (0)