Skip to content
Merged
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
26 changes: 15 additions & 11 deletions frontend/unified/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
SessionQueryQuery,
SessionQueryQueryVariables,
} from "./__generated__/App.generated";
import { WorkflowsQueryQuery } from "./components/JobsViewer/__generated__/JobsTable.generated";

const VERTICAL_SPACING = 2;
const HORIZONTAL_SPACING = 2;
Expand Down Expand Up @@ -95,9 +94,7 @@ export const App: React.FC = () => {
const [customSession, setCustomSession] = useState<InstrumentSession | null>(
null
);
const [TableInfo, setTableInfo] = useState<WorkflowsQueryQuery | undefined>(
undefined
);
const [selectedWorkflow, setSelectedWorkflow] = useState<string | null>(null);
const { loading, error, data } = useQuery(SESSION_QUERY, { variables: {} });

if (loading) return <p>Loading...</p>;
Expand Down Expand Up @@ -304,17 +301,20 @@ export const App: React.FC = () => {
<Typography variant="h5">Plot</Typography>

<Plot
workflowName={
beamline === Beamline.DIAD
? "example-template-599zg"
: "generate-multi-page-tiff-7mwgh"
}
workflowName={selectedWorkflow}
visit={selectedVisit}
key={sessionName}
/>
<Divider sx={{ width: "100%" }} />
<Typography variant="h5">Log</Typography>
<DisplayLogMeta visit={selectedVisit} TableInfo={TableInfo} />
{selectedWorkflow !== null ? (
<DisplayLogMeta
visit={selectedVisit}
workflowName={selectedWorkflow}
/>
) : (
<p>No workflow selected</p>
)}

<PlaceholderComponent
placeholderText="Log component placeholder"
Expand All @@ -324,7 +324,11 @@ export const App: React.FC = () => {

<Divider sx={{ width: "100%" }} />
<Typography variant="h5">Jobs</Typography>
<JobsViewer visit={selectedVisit} setInfo={setTableInfo} />
<JobsViewer
visit={selectedVisit}
selectedWorkflow={selectedWorkflow}
setSelectedWorkflow={setSelectedWorkflow}
/>
</Stack>
</Grid>
</ApolloProvider>
Expand Down
147 changes: 31 additions & 116 deletions frontend/unified/src/components/InspectLogMeta.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
import React, { FC, useState } from "react";
import {
Button,
Stack,
Menu,
MenuItem,
List,
ListItemButton,
ListItemText,
Paper,
Typography,
Divider,
} from "@mui/material";
import React, { FC } from "react";
import { Button, Stack } from "@mui/material";

import { useQuery } from "@apollo/client/react";
import { gql, type TypedDocumentNode } from "@apollo/client";
Expand All @@ -20,7 +9,6 @@ import {
LogQueryQueryVariables,
} from "./__generated__/InspectLogMeta.generated";
import { Visit } from "@diamondlightsource/sci-react-ui";
import { WorkflowsQueryQuery } from "./JobsViewer/__generated__/JobsTable.generated";

export const InspectLog_Query: TypedDocumentNode<
LogQueryQuery,
Expand Down Expand Up @@ -73,50 +61,19 @@ export const InspectLog_Query: TypedDocumentNode<

type DisplayLogMetaProps = {
visit: Visit;
TableInfo: WorkflowsQueryQuery;
workflowName: string;
};

export const DisplayLogMeta: FC<DisplayLogMetaProps> = (props: {
visit: Visit;
TableInfo: WorkflowsQueryQuery;
workflowName: string;
}) => {
let artifactUrlAndLogFileTuples: [string, string][] = [];
let workflownames: string[] = [];
let y: any = [];

//ToDO maybe need to Consider what to display if there is no workflow as then workflowsnames is empty
if (props.TableInfo !== undefined) {
props.TableInfo.workflows?.nodes.forEach((workflow) => {
if (workflow.status?.__typename == "WorkflowSucceededStatus") {
workflownames.push(workflow.name);
}
});
}

const [selectedWorkflow, setSelectedWorkflow] = useState(0);

const { loading, error, data } = useQuery(InspectLog_Query, {
variables: { visitobj: props.visit, name: workflownames[0] },
variables: { visitobj: props.visit, name: props.workflowName },
});

if (data !== undefined) {
if (data.workflow !== null) {
if (data.workflow.status?.__typename == "WorkflowSucceededStatus") {
data.workflow.status.tasks.forEach((task) => {
task.artifacts.forEach((artifact) => {
if (artifact.mimeType == "text/plain") {
artifactUrlAndLogFileTuples.push([
artifact.url,
task.name + ".log",
]);
}
});
});
} else
y[0] = ["http://localhost:5173/unified", "Error-No-logs-found.log"];
}
} else {
y = ["http://localhost:5173/unified", "Error-No-logs-found.log"];
if (data === undefined) {
return <p>Data undefined</p>;
}

const openInNewTab = (url: string) => {
Expand All @@ -126,11 +83,32 @@ export const DisplayLogMeta: FC<DisplayLogMetaProps> = (props: {
}
};

function makeButtonArray(artifactUrlsAndLogFilenames: [string, string][]) {
function makeButtonArray(data: LogQueryQuery) {
const artifactUrlAndLogFileTuples: [string, string][] = [];

switch (data.workflow?.status?.__typename) {
case "WorkflowSucceededStatus":
{
data.workflow.status.tasks.forEach((task) => {
task.artifacts.forEach((artifact) => {
if (artifact.mimeType == "text/plain") {
artifactUrlAndLogFileTuples.push([
artifact.url,
task.name + ".log",
]);
}
});
});
}
break;
default:
console.error("Handle other workflow status cases");
}

return (
<Stack direction="row" spacing={1}>
{" "}
{artifactUrlsAndLogFilenames.map(([artifactUrl, logFilename]) => {
{artifactUrlAndLogFileTuples.map(([artifactUrl, logFilename]) => {
return (
<Button
key={logFilename}
Expand All @@ -146,70 +124,7 @@ export const DisplayLogMeta: FC<DisplayLogMetaProps> = (props: {
);
}

//Menu handling
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClose = () => {
setAnchorEl(null);
};
const handleClickListItem = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleMenuListItem = (
event: React.MouseEvent<HTMLElement>,
index: number
) => {
setSelectedWorkflow(index);
setAnchorEl(null);
};

return (
<div>
<Paper sx={{ width: 400 }}>
<Divider
flexItem={true}
sx={{ width: "100%", Color: "rgba(2, 2, 1, 0.5)" }}
variant="fullWidth"
/>
<List>
<ListItemButton onClick={handleClickListItem}>
<ListItemText
primary="Select a previous workflow by clicking here"
secondary={`Workflow: ${workflownames[selectedWorkflow]}`}
/>
</ListItemButton>
</List>
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
{workflownames.map((option: string, index: number) => (
<MenuItem
key={option}
role="menuitemradio"
selected={workflownames[selectedWorkflow] === option}
onClick={(event) => handleMenuListItem(event, index)}
>
<ListItemText>{option}</ListItemText>
<Divider
flexItem={true}
variant="fullWidth"
sx={{ width: "100%", Color: "rgba(2, 2, 1, 0.5)" }}
/>
</MenuItem>
))}
</Menu>
</Paper>
<p />
<Divider
flexItem={true}
variant="fullWidth"
sx={{ mb: 2, width: "100%", Color: "rgba(2, 2, 1, 0.5)" }}
/>
<Typography>
Choose a log from {workflownames[selectedWorkflow]}:
</Typography>
<p />
{makeButtonArray(artifactUrlAndLogFileTuples)}
</div>
);
return makeButtonArray(data);
};

export default DisplayLogMeta;
50 changes: 46 additions & 4 deletions frontend/unified/src/components/JobsViewer/BaseTableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { gql } from "@apollo/client";
import { TableCell, TableRow } from "@mui/material";
import { Box, TableCell, Typography } from "@mui/material";
import { Eye } from "lucide-react";
import { useFragment } from "@apollo/client/react";
import { BaseTableRowFragmentFragment } from "./__generated__/BaseTableRow.generated";
import { getWorkflowStatusIcon } from "./StatusIcons";
import { DeepPartial } from "@apollo/client/utilities";
import { TableRowRelayFragmentFragment } from "./__generated__/TableRowRelay.generated";
import { JobTableRow } from "./JobTableRow";

export const BASETABLEROW_FRAGMENT = gql`
fragment BaseTableRowFragment on Workflow {
Expand All @@ -20,9 +22,13 @@ export const BASETABLEROW_FRAGMENT = gql`

const BaseTableRow = ({
queryData,
selectedWorkflow,
setSelectedWorkflow,
}: {
queryData:
TableRowRelayFragmentFragment | DeepPartial<TableRowRelayFragmentFragment>;
selectedWorkflow: string | null;
setSelectedWorkflow: (_: string | null) => void;
}) => {
const { data } = useFragment<BaseTableRowFragmentFragment>({
fragment: BASETABLEROW_FRAGMENT,
Expand All @@ -31,15 +37,51 @@ const BaseTableRow = ({
});

return (
<TableRow key={data.name}>
<TableCell>{data.name}</TableCell>
<JobTableRow
hover
key={data.name}
onClick={(_: React.MouseEvent<unknown>) => {
if (selectedWorkflow === data.name) {
setSelectedWorkflow(null);
return;
}
setSelectedWorkflow(data.name);
}}
selected={data.name === selectedWorkflow}
className="JobTableRow"
>
<TableCell>
<Eye
visibility={data.name === selectedWorkflow ? "visible" : "hidden"}
/>
</TableCell>
<TableCell>
<Box sx={{ display: "grid" }}>
<Typography
gridRow={1}
gridColumn={1}
fontWeight="bold"
visibility={data.name === selectedWorkflow ? "visible" : "hidden"}
>
{data.name}
</Typography>
<Typography
gridRow={1}
gridColumn={1}
fontWeight="normal"
visibility={data.name === selectedWorkflow ? "hidden" : "visible"}
>
{data.name}
</Typography>
</Box>
Comment on lines +59 to +76

@yousefmoazzam yousefmoazzam Sep 16, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the styling is self-explanatory, but this one deserves an explicit explanation.

The problem

In order to achieve the bolding of text when a row was selected, the text naturally changes from normal to bold. When the font weight changes between normal and bold, the table cell "shifts" ever so slightly, due to the text shrinking/expanding when going between normal and bold.

The solution chosen here

From some searching online, there are some CSS hacks which seemed interesting and from which I took some inspiration from.

What is going here is that there's a two "versions" of the workflow name text: one normal, and one bolded. They both occupy the same space via the parent Box having the grid display and them both being put into the same single cell of the grid.

When a workflow is unselected, the normal version is display and the bolded version is hidden, and when a workflow is selected, the normal version is hidden and the bolded version is displayed.

Because both normal and bolded versions exist in the DOM simultaneously, the width of the rendered table cell in the jobs table will always stay the same no matter which one is visible.

Any other suggestions are most welcome, I settled for this due to it not requiring separate CSS files involving pseudo elements or anything like that, which solutions online I found made use of.

</TableCell>
<TableCell>1</TableCell>
<TableCell>
{getWorkflowStatusIcon(data.status?.__typename ?? "Unknown")}
</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</JobTableRow>
);
};

Expand Down
14 changes: 14 additions & 0 deletions frontend/unified/src/components/JobsViewer/JobTableRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { TableRow, TableRowProps } from "@mui/material";
import { alpha, styled } from "@mui/material/styles";

export const JobTableRow = styled(TableRow)<TableRowProps>(({ theme }) => ({
// Duplicating the state selector `Mui-selected` to have the background colour of a selected
// row take precendence over the background colour of a hovered-over row
"&.JobTableRow.Mui-selected.Mui-selected.Mui-selected": {
backgroundColor: alpha(theme.palette.primary.main, 0.25),
},
"&.JobTableRow.MuiTableRow-hover:hover": {
backgroundColor: alpha(theme.palette.primary.main, 0.15),
cursor: "pointer",
},
}));
12 changes: 6 additions & 6 deletions frontend/unified/src/components/JobsViewer/JobsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ export const JOBSTABLE_QUERY: TypedDocumentNode<

const JobsTable = ({
visit,
setInfo,
setSelectedWorkflow,
selectedWorkflow,
}: {
visit: Visit;
setInfo: (_: WorkflowsQueryQuery | undefined) => void;
selectedWorkflow: string | null;
setSelectedWorkflow: (_: string | null) => void;
}) => {
const [selectedLimit, setSelectedLimit] = useState<number>(5);
const [currentPage, setCurrentPage] = useState<number>(0);
Expand All @@ -51,10 +53,6 @@ const JobsTable = ({
fetchPolicy: "cache-and-network",
});

useEffect(() => {
setInfo(data);
});

return (
<Box width="600px" height="600px">
<Suspense>
Expand All @@ -66,6 +64,8 @@ const JobsTable = ({
selectedLimit={selectedLimit}
setSelectedLimit={onChangeLimit}
setCursor={setCursor}
setSelectedWorkflow={setSelectedWorkflow}
selectedWorkflow={selectedWorkflow}
/>
)}
</Suspense>
Expand Down
Loading
Loading