diff --git a/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx b/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx index 40fe4359b..0b4db0642 100644 --- a/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx +++ b/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx @@ -15,6 +15,7 @@ import { import type { ModelEditorViewState } from "../../model-editor/shared/view-state"; import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions"; import { getCandidates } from "../../model-editor/shared/auto-model-candidates"; +import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state"; const LibraryContainer = styled.div` background-color: var(--vscode-peekViewResult-background); @@ -80,6 +81,7 @@ export type LibraryRowProps = { hideModeledMethods: boolean; revealedMethodSignature: string | null; accessPathSuggestions?: AccessPathSuggestionOptions; + evaluationRun: ModelEvaluationRunState | undefined; onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void; onMethodClick: (methodSignature: string) => void; onSaveModelClick: (methodSignatures: string[]) => void; @@ -105,6 +107,7 @@ export const LibraryRow = ({ hideModeledMethods, revealedMethodSignature, accessPathSuggestions, + evaluationRun, onChange, onMethodClick, onSaveModelClick, @@ -260,6 +263,7 @@ export const LibraryRow = ({ hideModeledMethods={hideModeledMethods} revealedMethodSignature={revealedMethodSignature} accessPathSuggestions={accessPathSuggestions} + evaluationRun={evaluationRun} onChange={onChange} onMethodClick={onMethodClick} /> diff --git a/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx b/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx index 0f317ef81..2f8c68fbb 100644 --- a/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx +++ b/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx @@ -38,6 +38,8 @@ import type { AccessPathOption } from "../../model-editor/suggestions"; import { ModelInputSuggestBox } from "./ModelInputSuggestBox"; import { ModelOutputSuggestBox } from "./ModelOutputSuggestBox"; import { getModelsAsDataLanguage } from "../../model-editor/languages"; +import { ModelAlertsIndicator } from "./ModelAlertsIndicator"; +import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state"; const ApiOrMethodRow = styled.div` min-height: calc(var(--input-height) * 1px); @@ -47,6 +49,14 @@ const ApiOrMethodRow = styled.div` gap: 0.5em; `; +const ModelButtonsContainer = styled.div` + min-height: calc(var(--input-height) * 1px); + display: flex; + flex-direction: row; + align-items: center; + gap: 1em; +`; + const UsagesButton = styled.button` color: var(--vscode-editor-foreground); background-color: var(--vscode-input-background); @@ -82,6 +92,7 @@ export type MethodRowProps = { revealedMethodSignature: string | null; inputAccessPathSuggestions?: AccessPathOption[]; outputAccessPathSuggestions?: AccessPathOption[]; + evaluationRun: ModelEvaluationRunState | undefined; onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void; onMethodClick: (methodSignature: string) => void; }; @@ -119,6 +130,7 @@ const ModelableMethodRow = forwardRef( revealedMethodSignature, inputAccessPathSuggestions, outputAccessPathSuggestions, + evaluationRun, onChange, onMethodClick, } = props; @@ -349,30 +361,37 @@ const ModelableMethodRow = forwardRef( /> - {index === 0 ? ( - { - event.stopPropagation(); - handleAddModelClick(); - }} - disabled={addModelButtonDisabled} - > - - - ) : ( - { - event.stopPropagation(); - removeModelClickedHandlers[index](); - }} - > - - - )} + + + {index === 0 ? ( + { + event.stopPropagation(); + handleAddModelClick(); + }} + disabled={addModelButtonDisabled} + > + + + ) : ( + { + event.stopPropagation(); + removeModelClickedHandlers[index](); + }} + > + + + )} + ); diff --git a/extensions/ql-vscode/src/view/model-editor/ModelAlertsIndicator.tsx b/extensions/ql-vscode/src/view/model-editor/ModelAlertsIndicator.tsx new file mode 100644 index 000000000..e25ba3c01 --- /dev/null +++ b/extensions/ql-vscode/src/view/model-editor/ModelAlertsIndicator.tsx @@ -0,0 +1,46 @@ +import { styled } from "styled-components"; +import type { ModeledMethod } from "../../model-editor/modeled-method"; +import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state"; +import type { ModelEditorViewState } from "../../model-editor/shared/view-state"; + +const ModelAlertsButton = styled.button` + color: var(--vscode-editor-foreground); + background-color: var(--vscode-input-background); + border: none; + border-radius: 40%; + cursor: pointer; +`; + +export type Props = { + viewState: ModelEditorViewState; + modeledMethod: ModeledMethod; + evaluationRun: ModelEvaluationRunState | undefined; +}; + +export const ModelAlertsIndicator = ({ + viewState, + modeledMethod, + evaluationRun, +}: Props) => { + if (!viewState.showEvaluationUi) { + return null; + } + + if (!evaluationRun || !modeledMethod) { + return null; + } + + // TODO: Once we have alert provenance, we can show actual alert counts here. + // For now, we show a random number. + const number = Math.floor(Math.random() * 10); + + return ( + { + event.stopPropagation(); + }} + > + {number} + + ); +}; diff --git a/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx b/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx index 6d68da43a..d61876516 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx @@ -436,6 +436,7 @@ export function ModelEditor({ hideModeledMethods={hideModeledMethods} revealedMethodSignature={revealedMethodSignature} accessPathSuggestions={accessPathSuggestions} + evaluationRun={evaluationRun} onChange={onChange} onMethodClick={onMethodClick} onSaveModelClick={onSaveModelClick} diff --git a/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx b/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx index 172f3736b..44cb7ec2e 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx @@ -8,6 +8,7 @@ import type { ModelEditorViewState } from "../../model-editor/shared/view-state" import { ScreenReaderOnly } from "../common/ScreenReaderOnly"; import { DataGrid, DataGridCell } from "../common/DataGrid"; import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions"; +import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state"; export const MULTIPLE_MODELS_GRID_TEMPLATE_COLUMNS = "0.5fr 0.125fr 0.125fr 0.125fr 0.125fr max-content"; @@ -23,6 +24,7 @@ export type ModeledMethodDataGridProps = { hideModeledMethods: boolean; revealedMethodSignature: string | null; accessPathSuggestions?: AccessPathSuggestionOptions; + evaluationRun: ModelEvaluationRunState | undefined; onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void; onMethodClick: (methodSignature: string) => void; }; @@ -38,6 +40,7 @@ export const ModeledMethodDataGrid = ({ hideModeledMethods, revealedMethodSignature, accessPathSuggestions, + evaluationRun, onChange, onMethodClick, }: ModeledMethodDataGridProps) => { @@ -101,6 +104,7 @@ export const ModeledMethodDataGrid = ({ revealedMethodSignature={revealedMethodSignature} inputAccessPathSuggestions={inputAccessPathSuggestions} outputAccessPathSuggestions={outputAccessPathSuggestions} + evaluationRun={evaluationRun} onChange={onChange} onMethodClick={onMethodClick} /> diff --git a/extensions/ql-vscode/src/view/model-editor/ModeledMethodsList.tsx b/extensions/ql-vscode/src/view/model-editor/ModeledMethodsList.tsx index 400f920c7..b6370ffc4 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModeledMethodsList.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModeledMethodsList.tsx @@ -9,6 +9,7 @@ import { } from "../../model-editor/shared/sorting"; import type { ModelEditorViewState } from "../../model-editor/shared/view-state"; import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions"; +import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state"; export type ModeledMethodsListProps = { methods: Method[]; @@ -19,6 +20,7 @@ export type ModeledMethodsListProps = { processedByAutoModelMethods: Set; revealedMethodSignature: string | null; accessPathSuggestions?: AccessPathSuggestionOptions; + evaluationRun: ModelEvaluationRunState | undefined; viewState: ModelEditorViewState; hideModeledMethods: boolean; onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void; @@ -48,6 +50,7 @@ export const ModeledMethodsList = ({ hideModeledMethods, revealedMethodSignature, accessPathSuggestions, + evaluationRun, onChange, onMethodClick, onSaveModelClick, @@ -98,6 +101,7 @@ export const ModeledMethodsList = ({ hideModeledMethods={hideModeledMethods} revealedMethodSignature={revealedMethodSignature} accessPathSuggestions={accessPathSuggestions} + evaluationRun={evaluationRun} onChange={onChange} onMethodClick={onMethodClick} onSaveModelClick={onSaveModelClick} diff --git a/extensions/ql-vscode/src/view/model-editor/__tests__/LibraryRow.spec.tsx b/extensions/ql-vscode/src/view/model-editor/__tests__/LibraryRow.spec.tsx index 48a26b609..19caa8fd8 100644 --- a/extensions/ql-vscode/src/view/model-editor/__tests__/LibraryRow.spec.tsx +++ b/extensions/ql-vscode/src/view/model-editor/__tests__/LibraryRow.spec.tsx @@ -37,6 +37,7 @@ describe(LibraryRow.name, () => { selectedSignatures={new Set()} inProgressMethods={new Set()} processedByAutoModelMethods={new Set()} + evaluationRun={undefined} viewState={viewState} hideModeledMethods={false} revealedMethodSignature={null} diff --git a/extensions/ql-vscode/src/view/model-editor/__tests__/MethodRow.spec.tsx b/extensions/ql-vscode/src/view/model-editor/__tests__/MethodRow.spec.tsx index 3624b32f4..5213dc15d 100644 --- a/extensions/ql-vscode/src/view/model-editor/__tests__/MethodRow.spec.tsx +++ b/extensions/ql-vscode/src/view/model-editor/__tests__/MethodRow.spec.tsx @@ -45,6 +45,7 @@ describe(MethodRow.name, () => { modelingInProgress={false} processedByAutoModel={false} revealedMethodSignature={null} + evaluationRun={undefined} viewState={viewState} onChange={onChange} onMethodClick={onMethodClick} diff --git a/extensions/ql-vscode/src/view/model-editor/__tests__/ModelAlertsIndicator.spec.tsx b/extensions/ql-vscode/src/view/model-editor/__tests__/ModelAlertsIndicator.spec.tsx new file mode 100644 index 000000000..761b070f7 --- /dev/null +++ b/extensions/ql-vscode/src/view/model-editor/__tests__/ModelAlertsIndicator.spec.tsx @@ -0,0 +1,70 @@ +import { render as reactRender, screen } from "@testing-library/react"; +import { createMethod } from "../../../../test/factories/model-editor/method-factories"; +import { createSummaryModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories"; +import { createMockModelEditorViewState } from "../../../../test/factories/model-editor/view-state"; +import type { Props } from "../ModelAlertsIndicator"; +import { ModelAlertsIndicator } from "../ModelAlertsIndicator"; +import { createMockVariantAnalysis } from "../../../../test/factories/variant-analysis/shared/variant-analysis"; +import { VariantAnalysisStatus } from "../../../variant-analysis/shared/variant-analysis"; + +describe(ModelAlertsIndicator.name, () => { + const method = createMethod(); + const modeledMethod = createSummaryModeledMethod(method); + const evaluationRun = { + isPreparing: false, + variantAnalysis: createMockVariantAnalysis({ + status: VariantAnalysisStatus.Succeeded, + }), + }; + + const render = (props: Partial = {}) => + reactRender( + , + ); + + describe("when showEvaluationUi is false", () => { + it("does not render anything", () => { + render({ + viewState: createMockModelEditorViewState({ showEvaluationUi: false }), + }); + + expect(screen.queryByRole("button")).not.toBeInTheDocument(); + }); + }); + + describe("when there is no evaluation run", () => { + it("does not render anything", () => { + render({ + evaluationRun: undefined, + }); + + expect(screen.queryByRole("button")).not.toBeInTheDocument(); + }); + }); + + describe("when there is no modeled method", () => { + it("does not render anything", () => { + render({ + modeledMethod: undefined, + }); + + expect(screen.queryByRole("button")).not.toBeInTheDocument(); + }); + }); + + describe("when there is an evaluation run and a modeled method", () => { + // TODO: Once we have alert provenance, this will be an actual alert count instead of a random number. + it("renders a button with a random number", () => { + render(); + + const button = screen.queryByRole("button"); + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent(/\d/); + }); + }); +}); diff --git a/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodDataGrid.spec.tsx b/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodDataGrid.spec.tsx index 53973a6b0..36af1f825 100644 --- a/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodDataGrid.spec.tsx +++ b/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodDataGrid.spec.tsx @@ -59,6 +59,7 @@ describe(ModeledMethodDataGrid.name, () => { selectedSignatures={new Set()} inProgressMethods={new Set()} processedByAutoModelMethods={new Set()} + evaluationRun={undefined} viewState={viewState} hideModeledMethods={false} revealedMethodSignature={null} diff --git a/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodsList.spec.tsx b/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodsList.spec.tsx index 413fc12e2..8136ee2df 100644 --- a/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodsList.spec.tsx +++ b/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodsList.spec.tsx @@ -60,6 +60,7 @@ describe(ModeledMethodsList.name, () => { selectedSignatures={new Set()} inProgressMethods={new Set()} processedByAutoModelMethods={new Set()} + evaluationRun={undefined} viewState={viewState} hideModeledMethods={false} revealedMethodSignature={null}