diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index fe91d1122..ce1218483 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -1765,6 +1765,14 @@ "name": "CodeQL Methods Usage", "when": "config.codeQL.canary && codeql.dataExtensionsEditorOpen" } + ], + "explorer": [ + { + "type": "webview", + "id": "codeQLMethodModeling", + "name": "CodeQL Method Modeling", + "when": "config.codeQL.canary && config.codeQL.modelEditor.methodModelingView && codeql.dataExtensionsEditorOpen" + } ] }, "viewsWelcome": [ diff --git a/extensions/ql-vscode/src/common/interface-types.ts b/extensions/ql-vscode/src/common/interface-types.ts index d05844347..965a218a0 100644 --- a/extensions/ql-vscode/src/common/interface-types.ts +++ b/extensions/ql-vscode/src/common/interface-types.ts @@ -592,3 +592,7 @@ export type FromDataExtensionsEditorMessage = | StopGeneratingExternalApiFromLlmMessage | ModelDependencyMessage | HideModeledApisMessage; + +export type FromMethodModelingMessage = + | TelemetryMessage + | UnhandledErrorMessage; diff --git a/extensions/ql-vscode/src/common/vscode/webview-html.ts b/extensions/ql-vscode/src/common/vscode/webview-html.ts index 9cb0c99d0..787c7969f 100644 --- a/extensions/ql-vscode/src/common/vscode/webview-html.ts +++ b/extensions/ql-vscode/src/common/vscode/webview-html.ts @@ -7,7 +7,8 @@ export type WebviewKind = | "compare" | "variant-analysis" | "data-flow-paths" - | "data-extensions-editor"; + | "data-extensions-editor" + | "method-modeling"; export interface WebviewMessage { t: string; diff --git a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts index a08b2f6eb..a6cb242f5 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts @@ -19,6 +19,7 @@ import { Mode } from "./shared/mode"; import { showResolvableLocation } from "../databases/local-databases/locations"; import { Usage } from "./external-api-usage"; import { setUpPack } from "./data-extensions-editor-queries"; +import { MethodModelingPanel } from "./method-modeling/method-modeling-panel"; const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"]; @@ -43,6 +44,7 @@ export class DataExtensionsEditorModule extends DisposableObject { "data-extensions-editor-results", ); this.methodsUsagePanel = this.push(new MethodsUsagePanel(cliServer)); + this.push(new MethodModelingPanel(ctx)); } private handleViewBecameActive(view: DataExtensionsEditorView): void { diff --git a/extensions/ql-vscode/src/data-extensions-editor/method-modeling/method-modeling-panel.ts b/extensions/ql-vscode/src/data-extensions-editor/method-modeling/method-modeling-panel.ts new file mode 100644 index 000000000..b9b7301bf --- /dev/null +++ b/extensions/ql-vscode/src/data-extensions-editor/method-modeling/method-modeling-panel.ts @@ -0,0 +1,17 @@ +import { ExtensionContext, window } from "vscode"; +import { DisposableObject } from "../../common/disposable-object"; +import { MethodModelingViewProvider } from "./method-modeling-view-provider"; + +export class MethodModelingPanel extends DisposableObject { + constructor(context: ExtensionContext) { + super(); + + const provider = new MethodModelingViewProvider(context); + this.push( + window.registerWebviewViewProvider( + MethodModelingViewProvider.viewType, + provider, + ), + ); + } +} diff --git a/extensions/ql-vscode/src/data-extensions-editor/method-modeling/method-modeling-view-provider.ts b/extensions/ql-vscode/src/data-extensions-editor/method-modeling/method-modeling-view-provider.ts new file mode 100644 index 000000000..14549cb2d --- /dev/null +++ b/extensions/ql-vscode/src/data-extensions-editor/method-modeling/method-modeling-view-provider.ts @@ -0,0 +1,61 @@ +import * as vscode from "vscode"; +import { WebviewViewProvider } from "vscode"; +import { getHtmlForWebview } from "../../common/vscode/webview-html"; +import { FromMethodModelingMessage } from "../../common/interface-types"; +import { telemetryListener } from "../../common/vscode/telemetry"; +import { showAndLogExceptionWithTelemetry } from "../../common/logging/notifications"; +import { extLogger } from "../../common/logging/vscode/loggers"; +import { redactableError } from "../../common/errors"; + +export class MethodModelingViewProvider implements WebviewViewProvider { + public static readonly viewType = "codeQLMethodModeling"; + + constructor(private readonly context: vscode.ExtensionContext) {} + + /** + * This is called when a view first becomes visible. This may happen when the view is + * first loaded or when the user hides and then shows a view again. + */ + public resolveWebviewView( + webviewView: vscode.WebviewView, + _context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken, + ) { + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [this.context.extensionUri], + }; + + const html = getHtmlForWebview( + this.context, + webviewView.webview, + "method-modeling", + { + allowInlineStyles: true, + allowWasmEval: false, + }, + ); + + webviewView.webview.html = html; + + webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg)); + } + + private async onMessage(msg: FromMethodModelingMessage): Promise { + switch (msg.t) { + case "telemetry": { + telemetryListener?.sendUIInteraction(msg.action); + break; + } + case "unhandledError": + void showAndLogExceptionWithTelemetry( + extLogger, + telemetryListener, + redactableError( + msg.error, + )`Unhandled error in method modeling view: ${msg.error.message}`, + ); + break; + } + } +} diff --git a/extensions/ql-vscode/src/view/method-modeling/MethodModeling.tsx b/extensions/ql-vscode/src/view/method-modeling/MethodModeling.tsx new file mode 100644 index 000000000..96c22f7a1 --- /dev/null +++ b/extensions/ql-vscode/src/view/method-modeling/MethodModeling.tsx @@ -0,0 +1,9 @@ +import * as React from "react"; + +export const MethodModeling = (): JSX.Element => { + return ( + <> +

Hello

+ + ); +}; diff --git a/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx b/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx new file mode 100644 index 000000000..7fd8fc307 --- /dev/null +++ b/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; +import { useEffect } from "react"; +import { MethodModeling } from "./MethodModeling"; + +export function MethodModelingView(): JSX.Element { + useEffect(() => { + const listener = (evt: MessageEvent) => { + if (evt.origin === window.origin) { + // Nothing to do yet. + } 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 ; +} diff --git a/extensions/ql-vscode/src/view/method-modeling/index.tsx b/extensions/ql-vscode/src/view/method-modeling/index.tsx new file mode 100644 index 000000000..fb8ec381a --- /dev/null +++ b/extensions/ql-vscode/src/view/method-modeling/index.tsx @@ -0,0 +1,9 @@ +import * as React from "react"; +import { WebviewDefinition } from "../webview-definition"; +import { MethodModelingView } from "./MethodModelingView"; + +const definition: WebviewDefinition = { + component: , +}; + +export default definition;