Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions frontend/i14/src/api/services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ApiVisitSchema, Proposal } from "../../types/APIresponse";
import type { Option } from "../../types/workflowFields";

const formatDate = (d: Date): string =>
`${d.toLocaleString("en-GB", { month: "long" })} ${d.getFullYear()}`;

const makeVisitId = (proposal: Proposal, sessionNumber: number): string =>
`${String(proposal.proposalCategory).toLowerCase()}${proposal.proposalNumber}-${sessionNumber}`;

const makeLabel = (
proposal: Proposal,
sessionNumber: number,
d: string
): string => {
const visit = makeVisitId(proposal, sessionNumber);
const date = formatDate(new Date(d));
return `${visit} - ${date}`;
};

export const dataToOptions = (d: ApiVisitSchema): Option[] =>
d.visitArray.account.proposalRoles
?.flatMap((r) =>
(r.proposal?.instrumentSessions ?? [])
.filter((s) => s?.startTime)
.map((s) => ({
desc: r.proposal?.title ?? "",
label: makeLabel(r.proposal, s.instrumentSessionNumber, s.startTime),
value: makeVisitId(r.proposal, s.instrumentSessionNumber),
_ts: Date.parse(s.startTime),
}))
)
.filter((o) => !Number.isNaN(o._ts))
.sort((a, b) => b._ts - a._ts)
.slice(0, 5)
.map(({ _ts, ...rest }) => rest);
51 changes: 51 additions & 0 deletions frontend/i14/src/components/workflows/OptionSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { FC } from "react";
import {
FormControl,
InputLabel,
Select,
IconButton,
Tooltip,
Grid,
} from "@mui/material";
import InfoIcon from "@mui/icons-material/Info";
import type { SelectChangeEvent } from "@mui/material/Select";
import type { Option } from "../../types/workflowFields";

type Props = {
label: string;
value: string;
options: Option[];
onChange: (e: SelectChangeEvent<string>) => void;
};

const OptionSelect: FC<Props> = ({ label, value, options, onChange }) => {
const selected = options.find((o) => o.value === value);

return (
<FormControl>
<InputLabel shrink>{label}</InputLabel>
<Grid>
<Select
native
label={label}
size="small"
value={value}
onChange={onChange}
>
{options.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</Select>
<Tooltip title={selected?.desc ?? ""}>
<IconButton>
<InfoIcon />
</IconButton>
</Tooltip>
</Grid>
</FormControl>
);
};

export default OptionSelect;
150 changes: 37 additions & 113 deletions frontend/i14/src/components/workflows/WorkflowForm.tsx
Original file line number Diff line number Diff line change
@@ -1,136 +1,60 @@
import React, { FC, ChangeEvent, useState } from "react";
import {
Button,
MenuItem,
IconButton,
InputLabel,
Grid,
Select,
Stack,
TextField,
Tooltip,
Typography,
} from "@mui/material";
import InfoIcon from "@mui/icons-material/Info";
import type { SelectChangeEvent } from "@mui/material/Select";
import React, { FC, useMemo, useState } from "react";
import { Button, Grid, Stack, Typography } from "@mui/material";
import { visitRegex } from "@diamondlightsource/sci-react-ui";

type FormData = { visit: string; workflow: string };
import { initialData } from "../../data/form";
import { dataToOptions } from "../../api/services";
import visitArray from "../../data/visits.json";
import { workflowOptions } from "../../data/workflows";
import OptionSelect from "./OptionSelect";

const initialData: FormData = {
visit: "cm23467-2",
workflow: "dpc-batch",
};
import type { WorkflowFormData, Option } from "../../types/workflowFields";

export const WorkflowForm: FC = () => {
const [data, setData] = useState<FormData>(initialData);
const visitMatch = visitRegex.exec(data.visit);
const visitOptions: Option[] = useMemo(() => dataToOptions(visitArray), []);
const [data, setData] = useState<WorkflowFormData>(() => ({
...initialData,
visit: visitOptions[0]?.value ?? initialData.visit,
}));

const openInNewTab = (url: string) => {
const w = window.open(url, "_blank");
if (w) w.focus();
w?.focus();
};

const openLink = () => {
if (!visitMatch) return;
const linkString = `https://workflows.diamond.ac.uk/templates/${data.workflow}/${data.visit}`;
openInNewTab(linkString);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const url = `https://workflows.diamond.ac.uk/templates/${data.workflow}/${data.visit}`;
openInNewTab(url);
};

const workflowOptions = [
{
label: "DPC",
value: "dpc-batch",
desc: "DPC imaging produces an image of the phase shifts to the X-ray beam as a result of interaction with the sample by measuring the gradient of the phase and retrieving the phase shifts by a straightforward integration step.",
},
{
label: "XANES Auto-processing",
value: "xanes",
desc: "XANES is a utility which attempts to stack a sequence of datasets acquired at different energy for a particular line group and perform alignment based on a line group.",
},
{
label: "XANES Point",
value: "xanes-point",
desc: "XANES-point is a utility which takes in a scan file (inpath) and a line group (edge_element), performs windowing of this line group, and saves a two-column text file (outpath): the first column is the energy in keV, and the second column is the summed windowed MCA intensity across the 4 channels.",
},
{
label: "XANES Sparse",
value: "xanes-sparse",
desc: "XANES-sparse is a utility which takes the last scan file of a sparse XANES scan (inpath), defines the 2D full grid, inserts the data in the correct rows, stack the images, and completes the missing data by using looped alternating steepest descent (ASD).",
},
{
label: "XRD 1D",
value: "xrd1d-batch",
desc: "XRD can be used to spatially map changes in crystallographic direction, d-spacing or strain across a sample.",
},
{
label: "XRD 2D",
value: "xrd2d-batch",
desc: "XRD 2D is a utility which performs Azimuthal integration (ExcaliburXRDIntegration) and saves result to a nxs file",
},
];

const option = workflowOptions.find(
(option) => option.value === data.workflow
);

return (
<Grid container justifyContent="center" spacing={1}>
<Grid size={6}>
<Grid item s={6}>
<Typography variant="h4">I14 Workflows</Typography>

<form
onSubmit={(e) => {
e.preventDefault();
openLink();
}}
>
<br />
<form onSubmit={handleSubmit}>
<Stack direction="column" spacing={2}>
<InputLabel size="small" id="workflow-select-label">
Workflow
</InputLabel>
<Grid>
<Select
labelId="workflow-select-label"
label="Workflow"
variant="outlined"
size="small"
name="workflow"
value={data.workflow}
onChange={(e: SelectChangeEvent<string>) =>
setData((prev) => ({ ...prev, workflow: e.target.value }))
}
>
{workflowOptions.map((opt) => (
<MenuItem key={opt.value} value={opt.value}>
{opt.label}
</MenuItem>
))}
</Select>
<Tooltip title={option.desc}>
<IconButton>
<InfoIcon />
</IconButton>
</Tooltip>
</Grid>
<TextField
name="visit"
<OptionSelect
label="Workflow"
value={data.workflow}
options={workflowOptions}
onChange={(e) =>
setData((prev) => ({ ...prev, workflow: e.target.value }))
}
/>

<OptionSelect
label="Visit"
variant="outlined"
size="small"
placeholder="Visit"
type="text"
value={data.visit}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setData((prev) => ({ ...prev, visit: value }));
}}
helperText={visitMatch ? "" : "Expected format: xx12345-1"}
error={!visitMatch}
options={visitOptions}
onChange={(e) =>
setData((prev) => ({ ...prev, visit: e.target.value }))
}
/>

<Button variant="contained" type="submit" disabled={!visitMatch}>
Submit
<Button variant="contained" type="submit">
Open workflow form in a new tab
</Button>
</Stack>
</form>
Expand Down
6 changes: 6 additions & 0 deletions frontend/i14/src/data/form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { WorkflowFormData } from "../types/workflowFormData";

export const initialData: WorkflowFormData = {
visit: "mg23967-1",
workflow: "dpc-batch",
};
34 changes: 34 additions & 0 deletions frontend/i14/src/data/workflows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Option } from "../types/workflowFormData";

export const workflowOptions: Option[] = [
{
label: "DPC",
value: "dpc-batch",
desc: "DPC imaging produces an image of the phase shifts to the X-ray beam as a result of interaction with the sample by measuring the gradient of the phase and retrieving the phase shifts by a straightforward integration step.",
},
{
label: "XANES Auto-processing",
value: "xanes",
desc: "XANES is a utility which attempts to stack a sequence of datasets acquired at different energy for a particular line group and perform alignment based on a line group.",
},
{
label: "XANES Point",
value: "xanes-point",
desc: "XANES-point is a utility which takes in a scan file (inpath) and a line group (edge_element), performs windowing of this line group, and saves a two-column text file (outpath): the first column is the energy in keV, and the second column is the summed windowed MCA intensity across the 4 channels.",
},
{
label: "XANES Sparse",
value: "xanes-sparse",
desc: "XANES-sparse is a utility which takes the last scan file of a sparse XANES scan (inpath), defines the 2D full grid, inserts the data in the correct rows, stack the images, and completes the missing data by using looped alternating steepest descent (ASD).",
},
{
label: "XRD 1D",
value: "xrd1d-batch",
desc: "XRD can be used to spatially map changes in crystallographic direction, d-spacing or strain across a sample.",
},
{
label: "XRD 2D",
value: "xrd2d-batch",
desc: "XRD 2D is a utility which performs Azimuthal integration (ExcaliburXRDIntegration) and saves result to a nxs file",
},
];
24 changes: 24 additions & 0 deletions frontend/i14/src/types/APIresponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
type ProposalCategory = "mg" | "cm" | "nt" | "nr" | "jm" | "bi" | "si";

interface InstrumentSession {
startTime: string;
endTime: string;
instrumentSessionNumber: number;
}

interface Proposal {
proposalNumber: number;
proposalCategory: ProposalCategory;
title: string;
instrumentSessions: InstrumentSession[];
}

export type ApiVisitSchema = {
data: {
account: {
proposalRoles: {
proposal: Proposal;
}[];
};
};
};
2 changes: 2 additions & 0 deletions frontend/i14/src/types/workflowFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
type WorkflowFormData = { visit: string; workflow: string };
type Option = { label: string; value: string; desc?: string };
Loading