Skip to content

Commit adee81c

Browse files
committed
feat(workflow-details): add service logs tab (#426)
Closes reanahub/reana#880 Closes reanahub/reana-workflow-controller#627
1 parent 48ea4c3 commit adee81c

File tree

3 files changed

+106
-8
lines changed

3 files changed

+106
-8
lines changed

reana-ui/src/pages/workflowDetails/WorkflowDetails.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
-*- coding: utf-8 -*-
33
44
This file is part of REANA.
5-
Copyright (C) 2020, 2022, 2023, 2024 CERN.
5+
Copyright (C) 2020, 2022, 2023, 2024, 2025 CERN.
66
77
REANA is free software; you can redistribute it and/or modify it
88
under the terms of the MIT License; see LICENSE file for more details.
@@ -115,6 +115,14 @@ export default function WorkflowDetails() {
115115
menuItem: { key: "job-logs", icon: "terminal", content: "Job logs" },
116116
render: () => <WorkflowLogs workflow={workflow} />,
117117
},
118+
{
119+
menuItem: {
120+
key: "service-logs",
121+
icon: "cloud",
122+
content: "Service logs",
123+
},
124+
render: () => <WorkflowLogs service workflow={workflow} />,
125+
},
118126
{
119127
menuItem: {
120128
key: "workspace",

reana-ui/src/pages/workflowDetails/components/WorkflowLogs.js

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
-*- coding: utf-8 -*-
33
44
This file is part of REANA.
5-
Copyright (C) 2020, 2022 CERN.
5+
Copyright (C) 2020, 2022, 2025 CERN.
66
77
REANA is free software; you can redistribute it and/or modify it
88
under the terms of the MIT License; see LICENSE file for more details.
@@ -46,6 +46,79 @@ EngineLogs.propTypes = {
4646
workflowStatus: PropTypes.string.isRequired,
4747
};
4848

49+
// The ui currently handles only Dask service logs, but this component can be extended to handle other services in the future.
50+
// UI design should be changed to allow for multiple services to be displayed at once if another service is introduced.
51+
function ServiceLogs({ isDask, components, workflowStatus }) {
52+
const isExecuting = NON_FINISHED_STATUSES.includes(workflowStatus);
53+
const [selectedComponentIndex, setSelectedComponentIndex] = useState("0");
54+
55+
if (!isDask) {
56+
return (
57+
<Message
58+
icon="info circle"
59+
content={
60+
"The service logs are only available for Dask workflows at the moment."
61+
}
62+
info
63+
/>
64+
);
65+
}
66+
67+
if (isExecuting) {
68+
return (
69+
<Message
70+
icon="info circle"
71+
content={
72+
"The service logs will be available after the workflow run finishes."
73+
}
74+
info
75+
/>
76+
);
77+
}
78+
79+
const dropdownOptions = Object.entries(components).map(([id, component]) => ({
80+
key: id,
81+
text: component.component,
82+
icon: {
83+
name: "dot circle outline",
84+
size: "small",
85+
color: "green",
86+
},
87+
value: id,
88+
}));
89+
90+
return (
91+
<>
92+
<section className={styles["step-info"]}>
93+
<div className={styles["step-dropdown"]}>
94+
<Label size="large" className={styles["step-label"]}>
95+
Component
96+
</Label>
97+
<Dropdown
98+
placeholder="Select a component"
99+
search
100+
selection
101+
options={dropdownOptions}
102+
value={selectedComponentIndex}
103+
onChange={(_, { value }) => setSelectedComponentIndex(value)}
104+
className={styles.dropdown}
105+
/>
106+
</div>
107+
</section>
108+
{selectedComponentIndex && (
109+
<CodeSnippet dollarPrefix={false} classes={styles.logs}>
110+
{components[selectedComponentIndex].content}
111+
</CodeSnippet>
112+
)}
113+
</>
114+
);
115+
}
116+
117+
ServiceLogs.propTypes = {
118+
components: PropTypes.array.isRequired,
119+
workflowStatus: PropTypes.string.isRequired,
120+
};
121+
49122
function JobLogs({ logs }) {
50123
function chooseLastStepID(logs) {
51124
const failedStepId = findKey(logs, (log) => log.status === "failed");
@@ -141,12 +214,18 @@ JobLogs.propTypes = {
141214
logs: PropTypes.object.isRequired,
142215
};
143216

144-
export default function WorkflowLogs({ workflow, engine = false }) {
217+
export default function WorkflowLogs({
218+
workflow,
219+
engine = false,
220+
service = false,
221+
}) {
145222
const dispatch = useDispatch();
146223
const loading = useSelector(loadingDetails);
147-
const { engineLogs = "", jobLogs = {} } = useSelector(
148-
getWorkflowLogs(workflow.id),
149-
);
224+
const {
225+
engineLogs = "",
226+
jobLogs = {},
227+
serviceLogs = {},
228+
} = useSelector(getWorkflowLogs(workflow.id));
150229

151230
useEffect(() => {
152231
dispatch(fetchWorkflowLogs(workflow.id));
@@ -156,6 +235,16 @@ export default function WorkflowLogs({ workflow, engine = false }) {
156235
<Loader active inline="centered" />
157236
) : engine ? (
158237
<EngineLogs workflowStatus={workflow.status} logs={engineLogs} />
238+
) : service ? (
239+
<ServiceLogs
240+
isDask={workflow.services.length > 0}
241+
workflowStatus={workflow.status}
242+
components={
243+
Object.keys(serviceLogs).length === 0
244+
? []
245+
: Object.values(serviceLogs)[0]
246+
}
247+
/>
159248
) : (
160249
<JobLogs logs={jobLogs} />
161250
);
@@ -164,4 +253,5 @@ export default function WorkflowLogs({ workflow, engine = false }) {
164253
WorkflowLogs.propTypes = {
165254
workflow: PropTypes.object.isRequired,
166255
engine: PropTypes.bool,
256+
service: PropTypes.bool,
167257
};

reana-ui/src/util.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
-*- coding: utf-8 -*-
33
44
This file is part of REANA.
5-
Copyright (C) 2020, 2022, 2023 CERN.
5+
Copyright (C) 2020, 2022, 2023, 2025 CERN.
66
77
REANA is free software; you can redistribute it and/or modify it
88
under the terms of the MIT License; see LICENSE file for more details.
@@ -215,10 +215,10 @@ export function parseLogs(logs) {
215215
);
216216
}
217217
}
218-
219218
return {
220219
jobLogs: parsedLogs.job_logs,
221220
engineLogs: parsedLogs.workflow_logs,
221+
serviceLogs: parsedLogs.service_logs,
222222
};
223223
}
224224

0 commit comments

Comments
 (0)