diff --git a/extensions/ql-vscode/src/common/interface-types.ts b/extensions/ql-vscode/src/common/interface-types.ts index 25c54208e05..ffd6d423547 100644 --- a/extensions/ql-vscode/src/common/interface-types.ts +++ b/extensions/ql-vscode/src/common/interface-types.ts @@ -600,6 +600,10 @@ interface StopModelEvaluationMessage { t: "stopModelEvaluation"; } +interface OpenModelAlertsViewMessage { + t: "openModelAlertsView"; +} + interface ModelDependencyMessage { t: "modelDependency"; } @@ -671,7 +675,8 @@ export type FromModelEditorMessage = | HideModeledMethodsMessage | SetMultipleModeledMethodsMessage | StartModelEvaluationMessage - | StopModelEvaluationMessage; + | StopModelEvaluationMessage + | OpenModelAlertsViewMessage; interface RevealInEditorMessage { t: "revealInModelEditor"; @@ -721,3 +726,11 @@ export type ToMethodModelingMessage = | SetInModelingModeMessage | SetInProgressMessage | SetProcessedByAutoModelMessage; + +interface SetModelAlertsMessage { + t: "setModelAlerts"; +} + +export type ToModelAlertsMessage = SetModelAlertsMessage; + +export type FromModelAlertsMessage = CommonFromViewMessages; diff --git a/extensions/ql-vscode/src/common/vscode/webview-html.ts b/extensions/ql-vscode/src/common/vscode/webview-html.ts index 2605a754e7a..7ad1f5d08e4 100644 --- a/extensions/ql-vscode/src/common/vscode/webview-html.ts +++ b/extensions/ql-vscode/src/common/vscode/webview-html.ts @@ -10,7 +10,8 @@ export type WebviewKind = | "variant-analysis" | "data-flow-paths" | "model-editor" - | "method-modeling"; + | "method-modeling" + | "model-alerts"; export interface WebviewMessage { t: string; diff --git a/extensions/ql-vscode/src/model-editor/model-alerts/model-alerts-view.ts b/extensions/ql-vscode/src/model-editor/model-alerts/model-alerts-view.ts new file mode 100644 index 00000000000..0713ff10fee --- /dev/null +++ b/extensions/ql-vscode/src/model-editor/model-alerts/model-alerts-view.ts @@ -0,0 +1,67 @@ +import { ViewColumn } from "vscode"; +import type { WebviewPanelConfig } from "../../common/vscode/abstract-webview"; +import { AbstractWebview } from "../../common/vscode/abstract-webview"; +import { assertNever } from "../../common/helpers-pure"; +import { telemetryListener } from "../../common/vscode/telemetry"; +import type { + FromModelAlertsMessage, + ToModelAlertsMessage, +} from "../../common/interface-types"; +import type { App } from "../../common/app"; +import { redactableError } from "../../common/errors"; +import { extLogger } from "../../common/logging/vscode"; +import { showAndLogExceptionWithTelemetry } from "../../common/logging"; + +export class ModelAlertsView extends AbstractWebview< + ToModelAlertsMessage, + FromModelAlertsMessage +> { + public static readonly viewType = "codeQL.modelAlerts"; + + public constructor(app: App) { + super(app); + } + + public async showView() { + const panel = await this.getPanel(); + panel.reveal(undefined, true); + + await this.waitForPanelLoaded(); + } + + protected async getPanelConfig(): Promise { + return { + viewId: ModelAlertsView.viewType, + title: "Model Alerts", + viewColumn: ViewColumn.Active, + preserveFocus: true, + view: "model-alerts", + }; + } + + protected onPanelDispose(): void { + // Nothing to dispose + } + + protected async onMessage(msg: FromModelAlertsMessage): Promise { + switch (msg.t) { + case "viewLoaded": + this.onWebViewLoaded(); + break; + case "telemetry": + telemetryListener?.sendUIInteraction(msg.action); + break; + case "unhandledError": + void showAndLogExceptionWithTelemetry( + extLogger, + telemetryListener, + redactableError( + msg.error, + )`Unhandled error in model alerts view: ${msg.error.message}`, + ); + break; + default: + assertNever(msg); + } + } +} diff --git a/extensions/ql-vscode/src/model-editor/model-editor-view.ts b/extensions/ql-vscode/src/model-editor/model-editor-view.ts index 3399a0f8745..2328e3cb44a 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-view.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-view.ts @@ -125,7 +125,7 @@ export class ModelEditorView extends AbstractWebview< this.languageDefinition = getModelsAsDataLanguage(language); this.modelEvaluator = new ModelEvaluator( - this.app.logger, + this.app, this.cliServer, modelingStore, modelingEvents, @@ -380,6 +380,9 @@ export class ModelEditorView extends AbstractWebview< case "stopModelEvaluation": await this.modelEvaluator.stopEvaluation(); break; + case "openModelAlertsView": + await this.modelEvaluator.openModelAlertsView(); + break; case "telemetry": telemetryListener?.sendUIInteraction(msg.action); break; diff --git a/extensions/ql-vscode/src/model-editor/model-evaluator.ts b/extensions/ql-vscode/src/model-editor/model-evaluator.ts index 6303aa6c7e3..895f1927a38 100644 --- a/extensions/ql-vscode/src/model-editor/model-evaluator.ts +++ b/extensions/ql-vscode/src/model-editor/model-evaluator.ts @@ -4,7 +4,6 @@ import type { DatabaseItem } from "../databases/local-databases"; import type { ModelEvaluationRun } from "./model-evaluation-run"; import { DisposableObject } from "../common/disposable-object"; import type { ModelEvaluationRunState } from "./shared/model-evaluation-run-state"; -import type { BaseLogger } from "../common/logging"; import type { CodeQLCliServer } from "../codeql-cli/cli"; import type { VariantAnalysisManager } from "../variant-analysis/variant-analysis-manager"; import type { QueryLanguage } from "../common/query-language"; @@ -18,6 +17,8 @@ import type { VariantAnalysis } from "../variant-analysis/shared/variant-analysi import type { CancellationToken } from "vscode"; import { CancellationTokenSource } from "vscode"; import type { QlPackDetails } from "../variant-analysis/ql-pack-details"; +import type { App } from "../common/app"; +import { ModelAlertsView } from "./model-alerts/model-alerts-view"; export class ModelEvaluator extends DisposableObject { // Cancellation token source to allow cancelling of the current run @@ -26,7 +27,7 @@ export class ModelEvaluator extends DisposableObject { private cancellationSource: CancellationTokenSource; public constructor( - private readonly logger: BaseLogger, + private readonly app: App, private readonly cliServer: CodeQLCliServer, private readonly modelingStore: ModelingStore, private readonly modelingEvents: ModelingEvents, @@ -54,7 +55,7 @@ export class ModelEvaluator extends DisposableObject { // Build pack const qlPack = await resolveCodeScanningQueryPack( - this.logger, + this.app.logger, this.cliServer, this.language, ); @@ -82,7 +83,7 @@ export class ModelEvaluator extends DisposableObject { public async stopEvaluation() { const evaluationRun = this.modelingStore.getModelEvaluationRun(this.dbItem); if (!evaluationRun) { - void this.logger.log("No active evaluation run to stop"); + void this.app.logger.log("No active evaluation run to stop"); return; } @@ -105,6 +106,11 @@ export class ModelEvaluator extends DisposableObject { } } + public async openModelAlertsView() { + const view = new ModelAlertsView(this.app); + await view.showView(); + } + private registerToModelingEvents() { this.push( this.modelingEvents.onModelEvaluationRunChanged(async (event) => { diff --git a/extensions/ql-vscode/src/view/model-alerts/ModelAlerts.tsx b/extensions/ql-vscode/src/view/model-alerts/ModelAlerts.tsx new file mode 100644 index 00000000000..b6da439ab4d --- /dev/null +++ b/extensions/ql-vscode/src/view/model-alerts/ModelAlerts.tsx @@ -0,0 +1,22 @@ +import { useEffect } from "react"; + +export function ModelAlerts(): React.JSX.Element { + useEffect(() => { + const listener = (evt: MessageEvent) => { + if (evt.origin === window.origin) { + // TODO: handle messages + } else { + // sanitize origin + const origin = evt.origin.replace(/\n|\r/g, ""); + console.error(`Invalid event origin ${origin}`); + } + }; + window.addEventListener("message", listener); + + return () => { + window.removeEventListener("message", listener); + }; + }, []); + + return <>hello world; +} diff --git a/extensions/ql-vscode/src/view/model-alerts/index.tsx b/extensions/ql-vscode/src/view/model-alerts/index.tsx new file mode 100644 index 00000000000..40786cab41d --- /dev/null +++ b/extensions/ql-vscode/src/view/model-alerts/index.tsx @@ -0,0 +1,8 @@ +import type { WebviewDefinition } from "../webview-definition"; +import { ModelAlerts } from "./ModelAlerts"; + +const definition: WebviewDefinition = { + component: , +}; + +export default definition; diff --git a/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx b/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx index fce9ea92177..6d68da43ad4 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx @@ -277,7 +277,9 @@ export function ModelEditor({ }, []); const openModelAlertsView = useCallback(() => { - // TODO + vscode.postMessage({ + t: "openModelAlertsView", + }); }, []); const onGenerateFromSourceClick = useCallback(() => { diff --git a/extensions/ql-vscode/src/view/webview.tsx b/extensions/ql-vscode/src/view/webview.tsx index 5bc08c1c97a..d3adadf74a1 100644 --- a/extensions/ql-vscode/src/view/webview.tsx +++ b/extensions/ql-vscode/src/view/webview.tsx @@ -11,6 +11,7 @@ import methodModelingView from "./method-modeling"; import modelEditorView from "./model-editor"; import resultsView from "./results"; import variantAnalysisView from "./variant-analysis"; +import modelAlertsView from "./model-alerts"; // Allow all views to use Codicons import "@vscode/codicons/dist/codicon.css"; @@ -22,6 +23,7 @@ const views: Record = { "model-editor": modelEditorView, results: resultsView, "variant-analysis": variantAnalysisView, + "model-alerts": modelAlertsView, }; const render = () => { diff --git a/extensions/ql-vscode/test/vscode-tests/activated-extension/model-editor/model-evaluator.test.ts b/extensions/ql-vscode/test/vscode-tests/activated-extension/model-editor/model-evaluator.test.ts index e5a15d0500b..058a4c76832 100644 --- a/extensions/ql-vscode/test/vscode-tests/activated-extension/model-editor/model-evaluator.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/activated-extension/model-editor/model-evaluator.test.ts @@ -1,4 +1,5 @@ import type { CodeQLCliServer } from "../../../../src/codeql-cli/cli"; +import type { App } from "../../../../src/common/app"; import type { BaseLogger } from "../../../../src/common/logging"; import { QueryLanguage } from "../../../../src/common/query-language"; import type { DatabaseItem } from "../../../../src/databases/local-databases"; @@ -15,6 +16,7 @@ import { mockedObject } from "../../../mocked-object"; describe("Model Evaluator", () => { let modelEvaluator: ModelEvaluator; let logger: BaseLogger; + let app: App; let cliServer: CodeQLCliServer; let modelingStore: ModelingStore; let modelingEvents: ModelingEvents; @@ -26,6 +28,7 @@ describe("Model Evaluator", () => { beforeEach(() => { logger = createMockLogger(); + app = mockedObject({ logger }); cliServer = mockedObject({}); getModelEvaluationRunMock = jest.fn(); modelingStore = createMockModelingStore({ @@ -40,7 +43,7 @@ describe("Model Evaluator", () => { updateView = jest.fn(); modelEvaluator = new ModelEvaluator( - logger, + app, cliServer, modelingStore, modelingEvents,