Skip to content

Commit aae3495

Browse files
committed
feat: add workflows to run page
1 parent c6e46a7 commit aae3495

File tree

9 files changed

+381
-88
lines changed

9 files changed

+381
-88
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"@rjsf/validator-ajv8": "5.24.10",
5454
"@sentry/nextjs": "8.55.0",
5555
"@squonk/account-server-client": "4.2.1",
56-
"@squonk/data-manager-client": "4.1.0",
56+
"@squonk/data-manager-client": "4.1.5",
5757
"@squonk/mui-theme": "5.0.0",
5858
"@squonk/sdf-parser": "1.3.1",
5959
"@tanstack/match-sorter-utils": "8.19.4",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/instances/JobDetails/JobInputSection/useGetJobInputs.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { type InputFieldSchema } from "../../../runCards/JobCard/JobInputFields"
55
import { TEST_JOB_ID } from "../../../runCards/TestJob/jobId";
66

77
// Contains only fields we are interested in
8-
type ApplicationSpecification = { variables: Record<string, unknown> };
8+
type ApplicationSpecification = { variables?: Record<string, unknown> };
99

1010
// Contains only fields we are interested in
1111
type JobInput = { title: string; type: InputFieldSchema["type"] };
@@ -27,6 +27,8 @@ export const useGetJobInputs = (instance: InstanceGetResponse | InstanceSummary)
2727
{ query: { enabled: inputsEnabled, retry: instance.job_id === TEST_JOB_ID ? 1 : 3 } },
2828
);
2929

30+
console.log(instance);
31+
3032
// Parse application specification
3133
const applicationSpecification: ApplicationSpecification = instance.application_specification
3234
? JSON.parse(instance.application_specification)
@@ -40,10 +42,10 @@ export const useGetJobInputs = (instance: InstanceGetResponse | InstanceSummary)
4042
// Get information about inputs that were provided when creating the job with their respective
4143
// values
4244
const usedInputs = Object.entries(jobVariables.properties)
43-
.filter(([name]) => Boolean(applicationSpecification.variables[name]))
45+
.filter(([name]) => Boolean(applicationSpecification.variables?.[name]))
4446
.map(([name, jobInput]) => {
4547
// Let's assume inputs can only contain string or array of strings as values
46-
const value = applicationSpecification.variables[name] as string[] | string;
48+
const value = applicationSpecification.variables?.[name] as string[] | string;
4749

4850
return {
4951
name,
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { type Dispatch, type RefObject, type SetStateAction } from "react";
2+
3+
import { type JobOrderDetail } from "@squonk/data-manager-client";
4+
5+
import { Grid2 as Grid, Typography } from "@mui/material";
6+
import { Form } from "@rjsf/mui";
7+
import validator from "@rjsf/validator-ajv8";
8+
9+
import { JobInputFields } from "./JobInputFields";
10+
import { type InputData } from "./JobModal";
11+
12+
interface JobInputsAndOptionsFormProps {
13+
inputs?: any;
14+
options?: any;
15+
order: JobOrderDetail["options"];
16+
projectId: string;
17+
inputsData: InputData;
18+
setInputsData: Dispatch<SetStateAction<InputData>>;
19+
optionsFormData: any;
20+
setOptionsFormData: Dispatch<SetStateAction<any>>;
21+
formRef: RefObject<any>;
22+
specVariables?: any;
23+
}
24+
25+
export const JobInputsAndOptionsForm = ({
26+
inputs,
27+
options,
28+
order,
29+
projectId,
30+
inputsData,
31+
setInputsData,
32+
optionsFormData,
33+
setOptionsFormData,
34+
formRef,
35+
specVariables,
36+
}: JobInputsAndOptionsFormProps) => {
37+
return (
38+
<Grid container spacing={2}>
39+
<>
40+
<Grid size={{ xs: 12 }}>
41+
<Typography component="h3" sx={{ fontWeight: "bold" }} variant="subtitle1">
42+
Inputs
43+
</Typography>
44+
</Grid>
45+
{!!inputs && (
46+
<JobInputFields
47+
initialValues={specVariables}
48+
inputs={inputs}
49+
inputsData={inputsData}
50+
projectId={projectId}
51+
onChange={setInputsData}
52+
/>
53+
)}
54+
</>
55+
<Grid size={{ xs: 12 }}>
56+
{!!options && (
57+
<>
58+
<Typography component="h3" sx={{ fontWeight: "bold" }} variant="subtitle1">
59+
Options
60+
</Typography>
61+
<Form
62+
liveValidate
63+
noHtml5Validate
64+
formData={optionsFormData}
65+
ref={formRef}
66+
schema={options}
67+
showErrorList="bottom"
68+
uiSchema={{ "ui:order": order }}
69+
validator={validator}
70+
onChange={(event) => setOptionsFormData(event.formData)}
71+
onError={() => {}}
72+
>
73+
{/* Remove the default submit button */}
74+
<div />
75+
</Form>
76+
</>
77+
)}
78+
</Grid>
79+
</Grid>
80+
);
81+
};

src/components/runCards/JobCard/JobModal.tsx

Lines changed: 17 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,17 @@ import {
99
import { getGetInstancesQueryKey, useCreateInstance } from "@squonk/data-manager-client/instance";
1010
import { useGetJob } from "@squonk/data-manager-client/job";
1111

12-
import { Box, Grid2 as Grid, TextField, Typography } from "@mui/material";
13-
import { type FormProps } from "@rjsf/core";
14-
import validator from "@rjsf/validator-ajv8";
12+
import { Box, TextField } from "@mui/material";
1513
import { useQueryClient } from "@tanstack/react-query";
16-
import dynamic from "next/dynamic";
1714

1815
import { useEnqueueError } from "../../../hooks/useEnqueueStackError";
1916
import { CenterLoader } from "../../CenterLoader";
2017
import { ModalWrapper } from "../../modals/ModalWrapper";
2118
import { DebugCheckbox, type DebugValue } from "../DebugCheckbox";
2219
import { TEST_JOB_ID } from "../TestJob/jobId";
2320
import { type CommonModalProps } from "../types";
24-
import { type InputSchema, type JobInputFieldsProps, validateInputData } from "./JobInputFields";
25-
26-
const JobInputFields = dynamic<JobInputFieldsProps>(
27-
() => import("./JobInputFields").then((mod) => mod.JobInputFields),
28-
{ loading: () => <CenterLoader /> },
29-
);
30-
31-
// this dynamic import is necessary to avoid hydration issues with the form
32-
const Form = dynamic<FormProps>(() => import("@rjsf/mui").then((mod) => mod.Form), {
33-
loading: () => <CenterLoader />,
34-
});
21+
import { type InputSchema, validateInputData } from "./JobInputFields";
22+
import { JobInputsAndOptionsForm } from "./JobInputsAndOptionsForm";
3523

3624
export type InputData = Record<string, string[] | string | undefined>;
3725

@@ -167,16 +155,14 @@ export const JobModal = ({
167155
}
168156
};
169157

170-
const validateForm = formRef.current?.validateForm;
158+
const variables = job?.variables;
171159

172160
return (
173161
<ModalWrapper
174162
DialogProps={{ maxWidth: "md", fullWidth: true }}
175163
id={`job-${jobId}`}
176164
open={open}
177-
submitDisabled={
178-
(validateForm && validateForm() !== undefined && validateForm()) ?? !inputsValid
179-
}
165+
submitDisabled={!inputsValid}
180166
submitText="Run"
181167
title={job?.name ?? "Run Job"}
182168
onClose={onClose}
@@ -194,51 +180,18 @@ export const JobModal = ({
194180
</Box>
195181

196182
<DebugCheckbox value={debug} onChange={(debug) => setDebug(debug)} />
197-
{!!job.variables && (
198-
<Grid container spacing={2}>
199-
{!!job.variables.inputs && (
200-
<>
201-
<Grid size={{ xs: 12 }}>
202-
<Typography component="h3" sx={{ fontWeight: "bold" }} variant="subtitle1">
203-
Inputs
204-
</Typography>
205-
</Grid>
206-
<JobInputFields
207-
initialValues={specVariables}
208-
inputs={job.variables.inputs as any} // TODO: should validate this with zod
209-
inputsData={inputsData}
210-
projectId={projectId}
211-
onChange={setInputsData}
212-
/>
213-
</>
214-
)}
215-
216-
<Grid size={{ xs: 12 }}>
217-
{!!job.variables.options && (
218-
<>
219-
<Typography component="h3" sx={{ fontWeight: "bold" }} variant="subtitle1">
220-
Options
221-
</Typography>
222-
<Form
223-
liveValidate
224-
noHtml5Validate
225-
formData={optionsFormData}
226-
ref={formRef}
227-
schema={job.variables.options} // TODO: should validate this with zod
228-
showErrorList="bottom"
229-
uiSchema={{ "ui:order": job.variables.order?.options }}
230-
validator={validator}
231-
onChange={(event) => setOptionsFormData(event.formData)}
232-
onError={() => {}}
233-
>
234-
{/* Remove the default submit button */}
235-
<div />
236-
</Form>
237-
</>
238-
)}
239-
</Grid>
240-
</Grid>
241-
)}
183+
<JobInputsAndOptionsForm
184+
formRef={formRef}
185+
inputs={variables?.inputs}
186+
inputsData={inputsData}
187+
options={variables?.options}
188+
optionsFormData={optionsFormData}
189+
order={variables?.order?.options ?? []}
190+
projectId={projectId}
191+
setInputsData={setInputsData}
192+
setOptionsFormData={setOptionsFormData}
193+
specVariables={specVariables}
194+
/>
242195
</>
243196
) : (
244197
<CenterLoader />
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { useState } from "react";
2+
3+
import { type WorkflowSummary } from "@squonk/data-manager-client";
4+
5+
import { Button, CircularProgress, Tooltip } from "@mui/material";
6+
import dynamic from "next/dynamic";
7+
8+
export interface RunWorkflowButtonProps {
9+
workflowId: WorkflowSummary["id"];
10+
projectId: string;
11+
onLaunch?: (instanceId: string) => void;
12+
disabled?: boolean;
13+
}
14+
15+
const WorkflowModal = dynamic<any>(
16+
() => import("./WorkflowModal").then((mod) => mod.WorkflowModal),
17+
{ loading: () => <CircularProgress size="1rem" /> },
18+
);
19+
20+
/**
21+
* MuiButton that controls a modal to run a workflow instance
22+
*/
23+
export const RunWorkflowButton = ({
24+
workflowId,
25+
projectId,
26+
onLaunch,
27+
disabled,
28+
}: RunWorkflowButtonProps) => {
29+
const [open, setOpen] = useState(false);
30+
const [hasOpened, setHasOpened] = useState(false);
31+
32+
return (
33+
<>
34+
<Tooltip title="Run workflow">
35+
<span>
36+
<Button
37+
color="primary"
38+
disabled={disabled ?? !projectId}
39+
onClick={() => {
40+
setOpen(true);
41+
setHasOpened(true);
42+
}}
43+
>
44+
Run
45+
</Button>
46+
</span>
47+
</Tooltip>
48+
{!!hasOpened && (
49+
<WorkflowModal
50+
open={open}
51+
projectId={projectId}
52+
workflowId={workflowId}
53+
onClose={() => setOpen(false)}
54+
onLaunch={onLaunch}
55+
/>
56+
)}
57+
</>
58+
);
59+
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { type WorkflowSummary } from "@squonk/data-manager-client";
2+
3+
import { Chip, Typography } from "@mui/material";
4+
5+
import { useCurrentProjectId } from "../../../hooks/projectHooks";
6+
import { BaseCard } from "../../BaseCard";
7+
import { RunWorkflowButton } from "./RunWorkflowButton";
8+
9+
export interface WorkflowCardProps {
10+
workflow: WorkflowSummary;
11+
}
12+
13+
/**
14+
* MuiCard that displays a summary of a workflow definition.
15+
*/
16+
export const WorkflowCard = ({ workflow }: WorkflowCardProps) => {
17+
const { projectId } = useCurrentProjectId();
18+
return (
19+
<BaseCard
20+
actions={() => (
21+
<RunWorkflowButton
22+
disabled={!projectId}
23+
projectId={projectId ?? ""}
24+
workflowId={workflow.id}
25+
/>
26+
)}
27+
header={{
28+
color: "#f1c40f",
29+
subtitle: workflow.name,
30+
avatar: workflow.name[0],
31+
title: workflow.workflow_name ?? workflow.name,
32+
}}
33+
key={workflow.id}
34+
>
35+
<Typography gutterBottom>
36+
{workflow.workflow_description ?? <em>No description</em>}
37+
</Typography>
38+
<Typography gutterBottom variant="body2">
39+
Version: {workflow.version ?? <em>n/a</em>}
40+
</Typography>
41+
<Typography gutterBottom variant="body2">
42+
Scope: {workflow.scope}
43+
{workflow.scope_id ? ` (${workflow.scope_id})` : null}
44+
</Typography>
45+
<Typography gutterBottom variant="body2">
46+
Validated:{" "}
47+
{workflow.validated ? (
48+
<Chip color="success" label="Validated" size="small" />
49+
) : (
50+
<Chip color="warning" label="Not validated" size="small" />
51+
)}
52+
</Typography>
53+
{!!workflow.source_id && (
54+
<Typography gutterBottom variant="body2">
55+
Source Workflow ID: {workflow.source_id}
56+
</Typography>
57+
)}
58+
</BaseCard>
59+
);
60+
};

0 commit comments

Comments
 (0)