Skip to content

Commit ad9f78c

Browse files
authored
Add link to open "Evaluation Run" from the model editor (#3435)
1 parent 54737a0 commit ad9f78c

3 files changed

Lines changed: 170 additions & 26 deletions

File tree

extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,10 @@ export function ModelEditor({
276276
});
277277
}, []);
278278

279+
const openModelAlertsView = useCallback(() => {
280+
// TODO
281+
}, []);
282+
279283
const onGenerateFromSourceClick = useCallback(() => {
280284
vscode.postMessage({
281285
t: "generateMethod",
@@ -401,6 +405,7 @@ export function ModelEditor({
401405
modifiedSignatures={modifiedSignatures}
402406
onStartEvaluation={onStartEvaluation}
403407
onStopEvaluation={onStopEvaluation}
408+
openModelAlertsView={openModelAlertsView}
404409
evaluationRun={evaluationRun}
405410
/>
406411
</ButtonsContainer>
Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react";
1+
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react";
22
import type { ModeledMethod } from "../../model-editor/modeled-method";
33
import type { ModelEditorViewState } from "../../model-editor/shared/view-state";
44
import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state";
55
import { modelEvaluationRunIsRunning } from "../../model-editor/shared/model-evaluation-run-state";
66
import { ModelEditorProgressRing } from "./ModelEditorProgressRing";
7+
import { LinkIconButton } from "../variant-analysis/LinkIconButton";
78

8-
type Props = {
9+
export type Props = {
910
viewState: ModelEditorViewState;
1011
modeledMethods: Record<string, ModeledMethod[]>;
1112
modifiedSignatures: Set<string>;
1213
onStartEvaluation: () => void;
1314
onStopEvaluation: () => void;
15+
openModelAlertsView: () => void;
1416
evaluationRun: ModelEvaluationRunState | undefined;
1517
};
1618

@@ -20,34 +22,51 @@ export const ModelEvaluation = ({
2022
modifiedSignatures,
2123
onStartEvaluation,
2224
onStopEvaluation,
25+
openModelAlertsView,
2326
evaluationRun,
2427
}: Props) => {
2528
if (!viewState.showEvaluationUi) {
2629
return null;
2730
}
2831

29-
if (!evaluationRun || !modelEvaluationRunIsRunning(evaluationRun)) {
30-
const customModelsExist = Object.values(modeledMethods).some(
31-
(methods) => methods.filter((m) => m.type !== "none").length > 0,
32-
);
33-
34-
const unsavedChanges = modifiedSignatures.size > 0;
35-
36-
return (
37-
<VSCodeButton
38-
onClick={onStartEvaluation}
39-
appearance="secondary"
40-
disabled={!customModelsExist || unsavedChanges}
41-
>
42-
Evaluate
43-
</VSCodeButton>
44-
);
45-
} else {
46-
return (
47-
<VSCodeButton onClick={onStopEvaluation} appearance="secondary">
48-
<ModelEditorProgressRing />
49-
Stop evaluation
50-
</VSCodeButton>
51-
);
52-
}
32+
const shouldShowEvaluateButton =
33+
!evaluationRun || !modelEvaluationRunIsRunning(evaluationRun);
34+
35+
const shouldShowStopButton = !shouldShowEvaluateButton;
36+
37+
const shouldShowEvaluationRunLink = !!evaluationRun;
38+
39+
const customModelsExist = Object.values(modeledMethods).some(
40+
(methods) => methods.filter((m) => m.type !== "none").length > 0,
41+
);
42+
43+
const unsavedChanges = modifiedSignatures.size > 0;
44+
45+
return (
46+
<>
47+
{shouldShowEvaluateButton && (
48+
<VSCodeButton
49+
onClick={onStartEvaluation}
50+
appearance="secondary"
51+
disabled={!customModelsExist || unsavedChanges}
52+
>
53+
Evaluate
54+
</VSCodeButton>
55+
)}
56+
{shouldShowStopButton && (
57+
<VSCodeButton onClick={onStopEvaluation} appearance="secondary">
58+
<ModelEditorProgressRing />
59+
Stop evaluation
60+
</VSCodeButton>
61+
)}
62+
{shouldShowEvaluationRunLink && (
63+
<VSCodeLink>
64+
<LinkIconButton onClick={openModelAlertsView}>
65+
<span slot="end" className="codicon codicon-link-external"></span>
66+
Evaluation run
67+
</LinkIconButton>
68+
</VSCodeLink>
69+
)}
70+
</>
71+
);
5372
};
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { render as reactRender, screen } from "@testing-library/react";
2+
import type { Props } from "../ModelEvaluation";
3+
import { ModelEvaluation } from "../ModelEvaluation";
4+
import { createMockModelEditorViewState } from "../../../../test/factories/model-editor/view-state";
5+
import type { ModeledMethod } from "../../../model-editor/modeled-method";
6+
import { createMethod } from "../../../../test/factories/model-editor/method-factories";
7+
import { createMockVariantAnalysis } from "../../../../test/factories/variant-analysis/shared/variant-analysis";
8+
import { VariantAnalysisStatus } from "../../../variant-analysis/shared/variant-analysis";
9+
import { createSummaryModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories";
10+
11+
describe(ModelEvaluation.name, () => {
12+
const method = createMethod();
13+
const modeledMethodsMap: Record<string, ModeledMethod[]> = {};
14+
modeledMethodsMap[method.signature] = [createSummaryModeledMethod(method)];
15+
16+
const render = (props: Partial<Props> = {}) =>
17+
reactRender(
18+
<ModelEvaluation
19+
viewState={createMockModelEditorViewState({ showEvaluationUi: true })}
20+
modeledMethods={modeledMethodsMap}
21+
modifiedSignatures={new Set()}
22+
onStartEvaluation={jest.fn()}
23+
onStopEvaluation={jest.fn()}
24+
openModelAlertsView={jest.fn()}
25+
evaluationRun={undefined}
26+
{...props}
27+
/>,
28+
);
29+
30+
describe("when showEvaluationUi is false", () => {
31+
it("does not render anything", () => {
32+
render({
33+
viewState: createMockModelEditorViewState({ showEvaluationUi: false }),
34+
});
35+
expect(screen.queryByText("Evaluate")).not.toBeInTheDocument();
36+
expect(screen.queryByText("Stop evaluation")).not.toBeInTheDocument();
37+
expect(screen.queryByText("Evaluation run")).not.toBeInTheDocument();
38+
});
39+
});
40+
41+
describe("when showEvaluationUi is true", () => {
42+
it("renders evaluation UI with 'Evaluate' button enabled", () => {
43+
render();
44+
45+
const evaluateButton = screen.queryByText("Evaluate");
46+
expect(evaluateButton).toBeInTheDocument();
47+
expect(evaluateButton?.getElementsByTagName("input")[0]).toBeEnabled();
48+
49+
expect(screen.queryByText("Stop evaluation")).not.toBeInTheDocument();
50+
51+
expect(screen.queryByText("Evaluation run")).not.toBeInTheDocument();
52+
});
53+
54+
it("disables 'Evaluate' button when there are no custom models", () => {
55+
render({
56+
modeledMethods: {},
57+
});
58+
59+
const evaluateButton = screen.queryByText("Evaluate");
60+
expect(evaluateButton).toBeInTheDocument();
61+
expect(evaluateButton?.getElementsByTagName("input")[0]).toBeDisabled();
62+
63+
expect(screen.queryByText("Stop evaluation")).not.toBeInTheDocument();
64+
65+
expect(screen.queryByText("Evaluation run")).not.toBeInTheDocument();
66+
});
67+
68+
it("disables 'Evaluate' button when there are unsaved changes", () => {
69+
render({
70+
modifiedSignatures: new Set([method.signature]),
71+
});
72+
73+
const evaluateButton = screen.queryByText("Evaluate");
74+
expect(evaluateButton).toBeInTheDocument();
75+
expect(evaluateButton?.getElementsByTagName("input")[0]).toBeDisabled();
76+
77+
expect(screen.queryByText("Stop evaluation")).not.toBeInTheDocument();
78+
79+
expect(screen.queryByText("Evaluation run")).not.toBeInTheDocument();
80+
});
81+
82+
it("renders 'Evaluate' button and 'Evaluation run' link when there is a completed evaluation", () => {
83+
render({
84+
evaluationRun: {
85+
isPreparing: false,
86+
variantAnalysis: createMockVariantAnalysis({
87+
status: VariantAnalysisStatus.Succeeded,
88+
}),
89+
},
90+
});
91+
92+
const evaluateButton = screen.queryByText("Evaluate");
93+
expect(evaluateButton).toBeInTheDocument();
94+
expect(evaluateButton?.getElementsByTagName("input")[0]).toBeEnabled();
95+
96+
expect(screen.queryByText("Evaluation run")).toBeInTheDocument();
97+
98+
expect(screen.queryByText("Stop evaluation")).not.toBeInTheDocument();
99+
});
100+
101+
it("renders 'Stop evaluation' button and 'Evaluation run' link when there is an in progress evaluation", () => {
102+
render({
103+
evaluationRun: {
104+
isPreparing: true,
105+
variantAnalysis: undefined,
106+
},
107+
});
108+
109+
const stopEvaluationButton = screen.queryByText("Stop evaluation");
110+
expect(stopEvaluationButton).toBeInTheDocument();
111+
expect(
112+
stopEvaluationButton?.getElementsByTagName("input")[0],
113+
).toBeEnabled();
114+
115+
expect(screen.queryByText("Evaluation run")).toBeInTheDocument();
116+
117+
expect(screen.queryByText("Evaluate")).not.toBeInTheDocument();
118+
});
119+
});
120+
});

0 commit comments

Comments
 (0)