Add "model alerts indicator" component (#3441)
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
|||||||
import type { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
import type { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
||||||
import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions";
|
import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions";
|
||||||
import { getCandidates } from "../../model-editor/shared/auto-model-candidates";
|
import { getCandidates } from "../../model-editor/shared/auto-model-candidates";
|
||||||
|
import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state";
|
||||||
|
|
||||||
const LibraryContainer = styled.div`
|
const LibraryContainer = styled.div`
|
||||||
background-color: var(--vscode-peekViewResult-background);
|
background-color: var(--vscode-peekViewResult-background);
|
||||||
@@ -80,6 +81,7 @@ export type LibraryRowProps = {
|
|||||||
hideModeledMethods: boolean;
|
hideModeledMethods: boolean;
|
||||||
revealedMethodSignature: string | null;
|
revealedMethodSignature: string | null;
|
||||||
accessPathSuggestions?: AccessPathSuggestionOptions;
|
accessPathSuggestions?: AccessPathSuggestionOptions;
|
||||||
|
evaluationRun: ModelEvaluationRunState | undefined;
|
||||||
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
|
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
|
||||||
onMethodClick: (methodSignature: string) => void;
|
onMethodClick: (methodSignature: string) => void;
|
||||||
onSaveModelClick: (methodSignatures: string[]) => void;
|
onSaveModelClick: (methodSignatures: string[]) => void;
|
||||||
@@ -105,6 +107,7 @@ export const LibraryRow = ({
|
|||||||
hideModeledMethods,
|
hideModeledMethods,
|
||||||
revealedMethodSignature,
|
revealedMethodSignature,
|
||||||
accessPathSuggestions,
|
accessPathSuggestions,
|
||||||
|
evaluationRun,
|
||||||
onChange,
|
onChange,
|
||||||
onMethodClick,
|
onMethodClick,
|
||||||
onSaveModelClick,
|
onSaveModelClick,
|
||||||
@@ -260,6 +263,7 @@ export const LibraryRow = ({
|
|||||||
hideModeledMethods={hideModeledMethods}
|
hideModeledMethods={hideModeledMethods}
|
||||||
revealedMethodSignature={revealedMethodSignature}
|
revealedMethodSignature={revealedMethodSignature}
|
||||||
accessPathSuggestions={accessPathSuggestions}
|
accessPathSuggestions={accessPathSuggestions}
|
||||||
|
evaluationRun={evaluationRun}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onMethodClick={onMethodClick}
|
onMethodClick={onMethodClick}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ import type { AccessPathOption } from "../../model-editor/suggestions";
|
|||||||
import { ModelInputSuggestBox } from "./ModelInputSuggestBox";
|
import { ModelInputSuggestBox } from "./ModelInputSuggestBox";
|
||||||
import { ModelOutputSuggestBox } from "./ModelOutputSuggestBox";
|
import { ModelOutputSuggestBox } from "./ModelOutputSuggestBox";
|
||||||
import { getModelsAsDataLanguage } from "../../model-editor/languages";
|
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`
|
const ApiOrMethodRow = styled.div`
|
||||||
min-height: calc(var(--input-height) * 1px);
|
min-height: calc(var(--input-height) * 1px);
|
||||||
@@ -47,6 +49,14 @@ const ApiOrMethodRow = styled.div`
|
|||||||
gap: 0.5em;
|
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`
|
const UsagesButton = styled.button`
|
||||||
color: var(--vscode-editor-foreground);
|
color: var(--vscode-editor-foreground);
|
||||||
background-color: var(--vscode-input-background);
|
background-color: var(--vscode-input-background);
|
||||||
@@ -82,6 +92,7 @@ export type MethodRowProps = {
|
|||||||
revealedMethodSignature: string | null;
|
revealedMethodSignature: string | null;
|
||||||
inputAccessPathSuggestions?: AccessPathOption[];
|
inputAccessPathSuggestions?: AccessPathOption[];
|
||||||
outputAccessPathSuggestions?: AccessPathOption[];
|
outputAccessPathSuggestions?: AccessPathOption[];
|
||||||
|
evaluationRun: ModelEvaluationRunState | undefined;
|
||||||
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
|
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
|
||||||
onMethodClick: (methodSignature: string) => void;
|
onMethodClick: (methodSignature: string) => void;
|
||||||
};
|
};
|
||||||
@@ -119,6 +130,7 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
|
|||||||
revealedMethodSignature,
|
revealedMethodSignature,
|
||||||
inputAccessPathSuggestions,
|
inputAccessPathSuggestions,
|
||||||
outputAccessPathSuggestions,
|
outputAccessPathSuggestions,
|
||||||
|
evaluationRun,
|
||||||
onChange,
|
onChange,
|
||||||
onMethodClick,
|
onMethodClick,
|
||||||
} = props;
|
} = props;
|
||||||
@@ -349,30 +361,37 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
|
|||||||
/>
|
/>
|
||||||
</DataGridCell>
|
</DataGridCell>
|
||||||
<DataGridCell>
|
<DataGridCell>
|
||||||
{index === 0 ? (
|
<ModelButtonsContainer>
|
||||||
<CodiconRow
|
<ModelAlertsIndicator
|
||||||
appearance="icon"
|
viewState={viewState}
|
||||||
aria-label="Add new model"
|
modeledMethod={modeledMethod}
|
||||||
onClick={(event: React.MouseEvent) => {
|
evaluationRun={evaluationRun}
|
||||||
event.stopPropagation();
|
></ModelAlertsIndicator>
|
||||||
handleAddModelClick();
|
{index === 0 ? (
|
||||||
}}
|
<CodiconRow
|
||||||
disabled={addModelButtonDisabled}
|
appearance="icon"
|
||||||
>
|
aria-label="Add new model"
|
||||||
<Codicon name="add" />
|
onClick={(event: React.MouseEvent) => {
|
||||||
</CodiconRow>
|
event.stopPropagation();
|
||||||
) : (
|
handleAddModelClick();
|
||||||
<CodiconRow
|
}}
|
||||||
appearance="icon"
|
disabled={addModelButtonDisabled}
|
||||||
aria-label="Remove model"
|
>
|
||||||
onClick={(event: React.MouseEvent) => {
|
<Codicon name="add" />
|
||||||
event.stopPropagation();
|
</CodiconRow>
|
||||||
removeModelClickedHandlers[index]();
|
) : (
|
||||||
}}
|
<CodiconRow
|
||||||
>
|
appearance="icon"
|
||||||
<Codicon name="trash" />
|
aria-label="Remove model"
|
||||||
</CodiconRow>
|
onClick={(event: React.MouseEvent) => {
|
||||||
)}
|
event.stopPropagation();
|
||||||
|
removeModelClickedHandlers[index]();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Codicon name="trash" />
|
||||||
|
</CodiconRow>
|
||||||
|
)}
|
||||||
|
</ModelButtonsContainer>
|
||||||
</DataGridCell>
|
</DataGridCell>
|
||||||
</DataGridRow>
|
</DataGridRow>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<ModelAlertsButton
|
||||||
|
onClick={(event: React.MouseEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{number}
|
||||||
|
</ModelAlertsButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -436,6 +436,7 @@ export function ModelEditor({
|
|||||||
hideModeledMethods={hideModeledMethods}
|
hideModeledMethods={hideModeledMethods}
|
||||||
revealedMethodSignature={revealedMethodSignature}
|
revealedMethodSignature={revealedMethodSignature}
|
||||||
accessPathSuggestions={accessPathSuggestions}
|
accessPathSuggestions={accessPathSuggestions}
|
||||||
|
evaluationRun={evaluationRun}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onMethodClick={onMethodClick}
|
onMethodClick={onMethodClick}
|
||||||
onSaveModelClick={onSaveModelClick}
|
onSaveModelClick={onSaveModelClick}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { ModelEditorViewState } from "../../model-editor/shared/view-state"
|
|||||||
import { ScreenReaderOnly } from "../common/ScreenReaderOnly";
|
import { ScreenReaderOnly } from "../common/ScreenReaderOnly";
|
||||||
import { DataGrid, DataGridCell } from "../common/DataGrid";
|
import { DataGrid, DataGridCell } from "../common/DataGrid";
|
||||||
import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions";
|
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 =
|
export const MULTIPLE_MODELS_GRID_TEMPLATE_COLUMNS =
|
||||||
"0.5fr 0.125fr 0.125fr 0.125fr 0.125fr max-content";
|
"0.5fr 0.125fr 0.125fr 0.125fr 0.125fr max-content";
|
||||||
@@ -23,6 +24,7 @@ export type ModeledMethodDataGridProps = {
|
|||||||
hideModeledMethods: boolean;
|
hideModeledMethods: boolean;
|
||||||
revealedMethodSignature: string | null;
|
revealedMethodSignature: string | null;
|
||||||
accessPathSuggestions?: AccessPathSuggestionOptions;
|
accessPathSuggestions?: AccessPathSuggestionOptions;
|
||||||
|
evaluationRun: ModelEvaluationRunState | undefined;
|
||||||
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
|
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
|
||||||
onMethodClick: (methodSignature: string) => void;
|
onMethodClick: (methodSignature: string) => void;
|
||||||
};
|
};
|
||||||
@@ -38,6 +40,7 @@ export const ModeledMethodDataGrid = ({
|
|||||||
hideModeledMethods,
|
hideModeledMethods,
|
||||||
revealedMethodSignature,
|
revealedMethodSignature,
|
||||||
accessPathSuggestions,
|
accessPathSuggestions,
|
||||||
|
evaluationRun,
|
||||||
onChange,
|
onChange,
|
||||||
onMethodClick,
|
onMethodClick,
|
||||||
}: ModeledMethodDataGridProps) => {
|
}: ModeledMethodDataGridProps) => {
|
||||||
@@ -101,6 +104,7 @@ export const ModeledMethodDataGrid = ({
|
|||||||
revealedMethodSignature={revealedMethodSignature}
|
revealedMethodSignature={revealedMethodSignature}
|
||||||
inputAccessPathSuggestions={inputAccessPathSuggestions}
|
inputAccessPathSuggestions={inputAccessPathSuggestions}
|
||||||
outputAccessPathSuggestions={outputAccessPathSuggestions}
|
outputAccessPathSuggestions={outputAccessPathSuggestions}
|
||||||
|
evaluationRun={evaluationRun}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onMethodClick={onMethodClick}
|
onMethodClick={onMethodClick}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "../../model-editor/shared/sorting";
|
} from "../../model-editor/shared/sorting";
|
||||||
import type { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
import type { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
||||||
import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions";
|
import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions";
|
||||||
|
import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state";
|
||||||
|
|
||||||
export type ModeledMethodsListProps = {
|
export type ModeledMethodsListProps = {
|
||||||
methods: Method[];
|
methods: Method[];
|
||||||
@@ -19,6 +20,7 @@ export type ModeledMethodsListProps = {
|
|||||||
processedByAutoModelMethods: Set<string>;
|
processedByAutoModelMethods: Set<string>;
|
||||||
revealedMethodSignature: string | null;
|
revealedMethodSignature: string | null;
|
||||||
accessPathSuggestions?: AccessPathSuggestionOptions;
|
accessPathSuggestions?: AccessPathSuggestionOptions;
|
||||||
|
evaluationRun: ModelEvaluationRunState | undefined;
|
||||||
viewState: ModelEditorViewState;
|
viewState: ModelEditorViewState;
|
||||||
hideModeledMethods: boolean;
|
hideModeledMethods: boolean;
|
||||||
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
|
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
|
||||||
@@ -48,6 +50,7 @@ export const ModeledMethodsList = ({
|
|||||||
hideModeledMethods,
|
hideModeledMethods,
|
||||||
revealedMethodSignature,
|
revealedMethodSignature,
|
||||||
accessPathSuggestions,
|
accessPathSuggestions,
|
||||||
|
evaluationRun,
|
||||||
onChange,
|
onChange,
|
||||||
onMethodClick,
|
onMethodClick,
|
||||||
onSaveModelClick,
|
onSaveModelClick,
|
||||||
@@ -98,6 +101,7 @@ export const ModeledMethodsList = ({
|
|||||||
hideModeledMethods={hideModeledMethods}
|
hideModeledMethods={hideModeledMethods}
|
||||||
revealedMethodSignature={revealedMethodSignature}
|
revealedMethodSignature={revealedMethodSignature}
|
||||||
accessPathSuggestions={accessPathSuggestions}
|
accessPathSuggestions={accessPathSuggestions}
|
||||||
|
evaluationRun={evaluationRun}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onMethodClick={onMethodClick}
|
onMethodClick={onMethodClick}
|
||||||
onSaveModelClick={onSaveModelClick}
|
onSaveModelClick={onSaveModelClick}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ describe(LibraryRow.name, () => {
|
|||||||
selectedSignatures={new Set()}
|
selectedSignatures={new Set()}
|
||||||
inProgressMethods={new Set()}
|
inProgressMethods={new Set()}
|
||||||
processedByAutoModelMethods={new Set()}
|
processedByAutoModelMethods={new Set()}
|
||||||
|
evaluationRun={undefined}
|
||||||
viewState={viewState}
|
viewState={viewState}
|
||||||
hideModeledMethods={false}
|
hideModeledMethods={false}
|
||||||
revealedMethodSignature={null}
|
revealedMethodSignature={null}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ describe(MethodRow.name, () => {
|
|||||||
modelingInProgress={false}
|
modelingInProgress={false}
|
||||||
processedByAutoModel={false}
|
processedByAutoModel={false}
|
||||||
revealedMethodSignature={null}
|
revealedMethodSignature={null}
|
||||||
|
evaluationRun={undefined}
|
||||||
viewState={viewState}
|
viewState={viewState}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onMethodClick={onMethodClick}
|
onMethodClick={onMethodClick}
|
||||||
|
|||||||
@@ -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<Props> = {}) =>
|
||||||
|
reactRender(
|
||||||
|
<ModelAlertsIndicator
|
||||||
|
viewState={createMockModelEditorViewState({ showEvaluationUi: true })}
|
||||||
|
modeledMethod={modeledMethod}
|
||||||
|
evaluationRun={evaluationRun}
|
||||||
|
{...props}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -59,6 +59,7 @@ describe(ModeledMethodDataGrid.name, () => {
|
|||||||
selectedSignatures={new Set()}
|
selectedSignatures={new Set()}
|
||||||
inProgressMethods={new Set()}
|
inProgressMethods={new Set()}
|
||||||
processedByAutoModelMethods={new Set()}
|
processedByAutoModelMethods={new Set()}
|
||||||
|
evaluationRun={undefined}
|
||||||
viewState={viewState}
|
viewState={viewState}
|
||||||
hideModeledMethods={false}
|
hideModeledMethods={false}
|
||||||
revealedMethodSignature={null}
|
revealedMethodSignature={null}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ describe(ModeledMethodsList.name, () => {
|
|||||||
selectedSignatures={new Set()}
|
selectedSignatures={new Set()}
|
||||||
inProgressMethods={new Set()}
|
inProgressMethods={new Set()}
|
||||||
processedByAutoModelMethods={new Set()}
|
processedByAutoModelMethods={new Set()}
|
||||||
|
evaluationRun={undefined}
|
||||||
viewState={viewState}
|
viewState={viewState}
|
||||||
hideModeledMethods={false}
|
hideModeledMethods={false}
|
||||||
revealedMethodSignature={null}
|
revealedMethodSignature={null}
|
||||||
|
|||||||
Reference in New Issue
Block a user