Skip to content

Commit bb73494

Browse files
authored
Merge pull request #59 from tiqi-group/14-display-and-highlight-worker-parameter-values-in-job-view
14 display and highlight worker parameter values in job view
2 parents 0dbe2e7 + fe9931e commit bb73494

File tree

17 files changed

+318
-99
lines changed

17 files changed

+318
-99
lines changed

frontend/src/components/ExperimentDetails.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const ExperimentDetails = ({ experimentKey }: { experimentKey: string }) => {
4242
<ParameterGroupDisplay
4343
experimentKey={experimentKey}
4444
experimentGroup={group}
45+
parameters={experiments[experimentKey]?.parameters?.[group] || {}}
4546
/>
4647
</AccordionDetails>
4748
</Accordion>

frontend/src/components/JobView.tsx

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect, useState } from "react";
12
import {
23
Button,
34
Card,
@@ -7,22 +8,23 @@ import {
78
IconButton,
89
Switch,
910
Tooltip,
11+
Divider,
1012
} from "@mui/material";
11-
import { useExperimentData } from "../hooks/useExperimentData";
13+
import { ExpandLess, ExpandMore } from "@mui/icons-material";
1214
import ResultChannelPlot from "../components/ResultChannelPlot";
13-
import { useEffect, useState } from "react";
14-
import { ExperimentMetadata } from "../types/ExperimentMetadata";
15-
import { runMethod } from "../socket";
16-
import { deserialize } from "../utils/deserializer";
17-
import { SerializedObject } from "../types/SerializedObject";
1815
import { JobStatusIndicator } from "../components/JobStatusIndicator";
19-
import { useJobInfo } from "../hooks/useJobInfo";
16+
import { ParameterGroupDisplay } from "../components/ParameterGroupDisplay";
17+
import { useExperimentData } from "../hooks/useExperimentData";
2018
import { useJobRunInfo } from "../hooks/useJobRunInfo";
21-
import { cancelJob } from "../utils/cancelJob";
19+
import { useJobInfo } from "../hooks/useJobInfo";
20+
import { runMethod } from "../socket";
21+
import { ExperimentMetadata } from "../types/ExperimentMetadata";
22+
import { SerializedObject } from "../types/SerializedObject";
2223
import { JobStatus } from "../types/enums";
24+
import { deserialize } from "../utils/deserializer";
2325
import { updateJobParams } from "../utils/updateJobParams";
26+
import { cancelJob } from "../utils/cancelJob";
2427
import HistogramPlot from "./jobView/HistogramPlot";
25-
import { ExpandLess, ExpandMore } from "@mui/icons-material";
2628

2729
function getPlotTitle(scheduledTime?: string, experimentName?: string): string {
2830
if (!scheduledTime) return experimentName || "";
@@ -104,13 +106,16 @@ export const JobView = ({
104106

105107
useEffect(() => {
106108
if (jobInfo?.experiment_source.experiment_id)
107-
runMethod("experiments.get_experiments", [], {}, (ack) => {
108-
setExperimentMetadata(
109-
deserialize(ack as SerializedObject)[
110-
jobInfo?.experiment_source.experiment_id
111-
] as ExperimentMetadata,
112-
);
113-
});
109+
runMethod(
110+
"experiments.get_metadata",
111+
[jobInfo?.experiment_source.experiment_id],
112+
{},
113+
(ack) => {
114+
setExperimentMetadata(
115+
deserialize(ack as SerializedObject) as ExperimentMetadata,
116+
);
117+
},
118+
);
114119
}, [jobInfo]);
115120

116121
useEffect(() => {
@@ -268,8 +273,24 @@ export const JobView = ({
268273
<Grid size={{ xs: 12 }}>
269274
<Card>
270275
<CardContent>
271-
<div style={{ display: "flex" }}>
276+
<div style={{ display: "flex", flexDirection: "column" }}>
272277
<Typography variant="h6">Parameter Values</Typography>
278+
{Object.entries(experimentMetadata?.parameters || {}).map(
279+
([displayGroup, parameters], index) => (
280+
<div key={displayGroup}>
281+
<Typography variant="h6">{displayGroup}</Typography>
282+
<ParameterGroupDisplay
283+
displayGroup={displayGroup}
284+
parameters={parameters}
285+
values={experimentData.parameters}
286+
readOnly={true}
287+
/>
288+
{index <
289+
Object.keys(experimentMetadata?.parameters || {}).length -
290+
1 && <Divider sx={{ pt: 2 }} />}
291+
</div>
292+
),
293+
)}
273294
<div style={{ flexGrow: 1, alignContent: "center" }} />
274295
<Button
275296
variant="outlined"

frontend/src/components/ParameterGroupDisplay.tsx

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
import { useContext, useMemo } from "react";
2-
import { ExperimentsContext } from "../contexts/ExperimentsContext";
3-
import { ParameterDisplayGroupsContext } from "../contexts/ParameterDisplayGroupsContext";
1+
import { useMemo } from "react";
42
import { ButtonComponent } from "./parameterComponents/Button";
3+
import { Output } from "./parameterComponents/Output";
54
import { ParameterNumberComponent } from "./parameterComponents/ParameterNumberComponent";
65
import { Combobox } from "./parameterComponents/Combobox";
76
import { useScanContext } from "../hooks/useScanContext";
87
import { getScanIndex } from "../utils/getScanIndex";
98
import { useResponsiveGridColumns } from "../hooks/useResponsiveGridColumns";
9+
import { ParameterMetadata } from "../types/ExperimentMetadata";
10+
import { ParameterValue } from "../types/ExperimentData";
1011

1112
interface ParameterGroupDisplayProps {
13+
parameters: Record<string, ParameterMetadata>;
14+
values?: Record<string, ParameterValue>;
1215
experimentKey?: string;
1316
experimentGroup?: string;
1417
namespace?: string;
1518
displayGroup?: string;
19+
readOnly?: boolean;
1620
}
1721

1822
const extractNamespaceFromParamId = (parameterId: string): string | null => {
@@ -25,33 +29,13 @@ export const ParameterGroupDisplay = ({
2529
experimentGroup,
2630
namespace,
2731
displayGroup,
32+
parameters,
33+
values,
34+
readOnly = false,
2835
}: ParameterGroupDisplayProps) => {
2936
const { scannedParamKeys, handleRightClick } = useScanContext();
3037
const gridTemplateColumns = useResponsiveGridColumns();
3138

32-
const experiments = useContext(ExperimentsContext);
33-
const { parameterDisplayGroups } = useContext(ParameterDisplayGroupsContext);
34-
35-
// Determine parameters based on props
36-
const parameters = useMemo(() => {
37-
if (experimentKey && experimentGroup) {
38-
// Fetch parameters from ExperimentsContext
39-
return experiments[experimentKey]?.parameters?.[experimentGroup] || {};
40-
}
41-
if (namespace && displayGroup) {
42-
// Fetch parameters from ParameterDisplayGroupsContext
43-
return parameterDisplayGroups[`${namespace} (${displayGroup})`] || {};
44-
}
45-
return {};
46-
}, [
47-
experimentKey,
48-
experimentGroup,
49-
namespace,
50-
displayGroup,
51-
experiments,
52-
parameterDisplayGroups,
53-
]);
54-
5539
// Memoize sorting
5640
const sortedParameters = useMemo(() => {
5741
return Object.entries(parameters).sort((a, b) =>
@@ -69,6 +53,20 @@ export const ParameterGroupDisplay = ({
6953
>
7054
{sortedParameters.map(([paramId, paramMetadata]) => {
7155
const scanIndex = getScanIndex(paramId, scannedParamKeys);
56+
const value = values?.[paramId]?.value;
57+
58+
if (readOnly) {
59+
return (
60+
<Output
61+
id={paramId}
62+
label={paramMetadata.display_name}
63+
value={value}
64+
defaultValue={paramMetadata.default_value}
65+
scanIndex={scanIndex}
66+
description={paramId}
67+
/>
68+
);
69+
}
7270

7371
if (paramId.includes("param_type='ParameterTypes.BOOLEAN'")) {
7472
return (
@@ -79,6 +77,7 @@ export const ParameterGroupDisplay = ({
7977
id={paramId}
8078
displayName={paramMetadata.display_name}
8179
defaultValue={Boolean(paramMetadata.default_value)}
80+
value={value != null ? Boolean(value) : undefined}
8281
namespace={
8382
namespace === undefined
8483
? experimentKey === undefined
@@ -101,6 +100,7 @@ export const ParameterGroupDisplay = ({
101100
key={paramId}
102101
id={paramId}
103102
defaultValue={String(paramMetadata.default_value)}
103+
value={value?.toString()}
104104
displayName={paramMetadata.display_name}
105105
allowedValues={paramMetadata.allowed_values!}
106106
/>
@@ -117,6 +117,7 @@ export const ParameterGroupDisplay = ({
117117
min={paramMetadata?.min_value}
118118
max={paramMetadata?.max_value}
119119
unit={paramMetadata?.unit}
120+
value={value?.toString()}
120121
namespace={
121122
namespace === undefined
122123
? experimentKey === undefined

frontend/src/components/parameterComponents/Button.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface ButtonComponentProps {
1010
scanIndex: number | null;
1111
displayName: string;
1212
defaultValue: boolean;
13+
value?: boolean;
1314
onContextMenu?: (
1415
event: React.MouseEvent<HTMLDivElement | HTMLButtonElement>,
1516
paramId: string,
@@ -27,24 +28,28 @@ export const ButtonComponent = React.memo(
2728
displayName,
2829
defaultValue,
2930
onContextMenu,
31+
value: localValue,
3032
}: ButtonComponentProps) => {
3133
const [value, setValue] = useParameter(id);
32-
const displayValue = Boolean(value ?? defaultValue);
34+
const displayValue = Boolean(localValue ?? value ?? defaultValue);
3335
const onClick = useCallback(
3436
(newValue: boolean) => {
3537
updateParameterValue(id, newValue);
3638
setValue(newValue);
3739
},
3840
[id, setValue],
3941
);
42+
const isUpToDate = localValue == null || value == null || localValue == value;
43+
const backgroundColor =
44+
scanIndex !== null ? "#186fc67e" : !isUpToDate ? "#f851497e" : undefined;
4045

4146
return (
4247
<BaseButton
4348
label={displayName}
4449
color={displayValue === true ? "success" : "inherit"}
4550
onClick={() => onClick(!displayValue)}
4651
onContextMenu={(e) => onContextMenu?.(e, id, displayGroup, namespace)}
47-
backgroundColor={scanIndex !== null ? "#186fc67e" : undefined}
52+
backgroundColor={backgroundColor}
4853
description={id}
4954
title={scanIndex !== null ? `Scan parameter ${scanIndex + 1}` : undefined}
5055
>

frontend/src/components/parameterComponents/Combobox.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,20 @@ interface ComboboxProps {
1414
id: string;
1515
defaultValue: string;
1616
displayName: string;
17+
value?: string;
1718
allowedValues: string[];
1819
}
1920

2021
export const Combobox = React.memo(
21-
({ id, defaultValue, displayName, allowedValues }: ComboboxProps) => {
22+
({
23+
id,
24+
defaultValue,
25+
displayName,
26+
allowedValues,
27+
value: localValue,
28+
}: ComboboxProps) => {
2229
const [value, setValue] = useParameter(id);
23-
const displayValue = value ?? defaultValue;
30+
const displayValue = localValue ?? value ?? defaultValue;
2431

2532
const handleChange = useCallback(
2633
(event: SelectChangeEvent<string | number | boolean>) => {
@@ -31,14 +38,18 @@ export const Combobox = React.memo(
3138
[id, setValue],
3239
);
3340

41+
const isUpToDate = localValue == null || value == null || localValue == value;
42+
const style = isUpToDate ? {} : { backgroundColor: "#f851497e" };
43+
const title = isUpToDate ? undefined : `Value: ${value}`;
44+
3445
return (
3546
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
3647
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
3748
<Typography noWrap>{displayName ?? id}</Typography>
3849
{id && <HelpButton docString={id} />}
3950
</div>
4051
<FormControl size="small">
41-
<Select value={displayValue} onChange={handleChange}>
52+
<Select value={displayValue} onChange={handleChange} sx={style} title={title}>
4253
{allowedValues.map((value) => (
4354
<MenuItem key={value} value={value}>
4455
{value}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from "react";
2+
import { useParameter } from "../../hooks/useParameter";
3+
import { HelpButton } from "../HelpButtonComponent";
4+
import { Input, Typography, Button } from "@mui/material";
5+
6+
interface ButtonComponentProps<T> {
7+
id: string;
8+
label?: string;
9+
description?: string;
10+
scanIndex: number | null;
11+
defaultValue: T;
12+
value?: T | undefined;
13+
}
14+
15+
export const Output = React.memo(
16+
({
17+
id,
18+
scanIndex,
19+
defaultValue,
20+
label,
21+
description,
22+
value: localValue,
23+
}: ButtonComponentProps<string | boolean | number>) => {
24+
const [value, _setValue] = useParameter(id);
25+
const displayValue = localValue ?? value ?? defaultValue;
26+
const isUpToDate = localValue == null || value == null || localValue == value;
27+
const backgroundColor =
28+
scanIndex !== null ? "#186fc67e" : !isUpToDate ? "#f851497e" : undefined;
29+
const style = isUpToDate && scanIndex === null ? {} : { backgroundColor };
30+
31+
const title =
32+
scanIndex !== null
33+
? `Scan parameter ${scanIndex + 1}`
34+
: !isUpToDate
35+
? `Value: ${value}`
36+
: undefined;
37+
38+
return (
39+
<div style={{ display: "flex", alignItems: "center", padding: "4px 0" }}>
40+
{label && (
41+
<div style={{ display: "flex", alignItems: "center", padding: "0 4px 0 0" }}>
42+
<Typography noWrap>{label}</Typography>
43+
{description && <HelpButton docString={description} />}
44+
</div>
45+
)}
46+
{typeof defaultValue == "boolean" ? (
47+
<Button
48+
variant="outlined"
49+
color={displayValue === true ? "success" : "inherit"}
50+
title={title}
51+
sx={{ backgroundColor }}
52+
>
53+
{displayValue === true ? "On" : "Off"}
54+
</Button>
55+
) : (
56+
<Input
57+
type="text"
58+
value={displayValue.toString()}
59+
readOnly={true}
60+
title={title}
61+
sx={style}
62+
/>
63+
)}
64+
</div>
65+
);
66+
},
67+
);

0 commit comments

Comments
 (0)