Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions extensions/ql-vscode/src/model-editor/auto-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import { groupMethods, sortGroupNames, sortMethods } from "./shared/sorting";
* the order in the UI.
* @param mode Whether it is application or framework mode.
* @param methods all methods.
* @param modeledMethods the currently modeled methods.
* @param modeledMethodsBySignature the currently modeled methods.
* @returns list of modeled methods that are candidates for modeling.
*/
export function getCandidates(
mode: Mode,
methods: Method[],
modeledMethods: Record<string, ModeledMethod>,
modeledMethodsBySignature: Record<string, ModeledMethod[]>,
): MethodSignature[] {
// Sort the same way as the UI so we send the first ones listed in the UI first
const grouped = groupMethods(methods, mode);
Expand All @@ -32,12 +32,11 @@ export function getCandidates(
const candidates: MethodSignature[] = [];

for (const method of sortedMethods) {
const modeledMethod: ModeledMethod = modeledMethods[method.signature] ?? {
type: "none",
};
const modeledMethods: ModeledMethod[] =
modeledMethodsBySignature[method.signature] ?? [];

// Anything that is modeled is not a candidate
if (modeledMethod.type !== "none") {
if (modeledMethods.some((m) => m.type !== "none")) {
continue;
}

Expand Down
39 changes: 19 additions & 20 deletions extensions/ql-vscode/src/model-editor/auto-modeler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { QueryRunner } from "../query-server";
import { DatabaseItem } from "../databases/local-databases";
import { Mode } from "./shared/mode";
import { CancellationTokenSource } from "vscode";
import { convertToLegacyModeledMethods } from "./modeled-methods-legacy";

// Limit the number of candidates we send to the model in each request
// to avoid long requests.
Expand All @@ -43,7 +42,7 @@ export class AutoModeler {
inProgressMethods: string[],
) => Promise<void>,
private readonly addModeledMethods: (
modeledMethods: Record<string, ModeledMethod>,
modeledMethods: Record<string, ModeledMethod[]>,
) => Promise<void>,
) {
this.jobs = new Map<string, CancellationTokenSource>();
Expand All @@ -60,7 +59,7 @@ export class AutoModeler {
public async startModeling(
packageName: string,
methods: Method[],
modeledMethods: Record<string, ModeledMethod>,
modeledMethods: Record<string, ModeledMethod[]>,
mode: Mode,
): Promise<void> {
if (this.jobs.has(packageName)) {
Expand Down Expand Up @@ -107,7 +106,7 @@ export class AutoModeler {
private async modelPackage(
packageName: string,
methods: Method[],
modeledMethods: Record<string, ModeledMethod>,
modeledMethods: Record<string, ModeledMethod[]>,
mode: Mode,
cancellationTokenSource: CancellationTokenSource,
): Promise<void> {
Expand Down Expand Up @@ -193,31 +192,31 @@ export class AutoModeler {
filename: "auto-model.yml",
});

const rawLoadedMethods = loadDataExtensionYaml(models);
if (!rawLoadedMethods) {
const loadedMethods = loadDataExtensionYaml(models);
if (!loadedMethods) {
return;
}

const loadedMethods = convertToLegacyModeledMethods(rawLoadedMethods);

// Any candidate that was part of the response is a negative result
// meaning that the canidate is not a sink for the kinds that the LLM is checking for.
// For now we model this as a sink neutral method, however this is subject
// to discussion.
for (const candidate of candidateMethods) {
if (!(candidate.signature in loadedMethods)) {
loadedMethods[candidate.signature] = {
type: "neutral",
kind: "sink",
input: "",
output: "",
provenance: "ai-generated",
signature: candidate.signature,
packageName: candidate.packageName,
typeName: candidate.typeName,
methodName: candidate.methodName,
methodParameters: candidate.methodParameters,
};
loadedMethods[candidate.signature] = [
{
type: "neutral",
kind: "sink",
input: "",
output: "",
provenance: "ai-generated",
signature: candidate.signature,
packageName: candidate.packageName,
typeName: candidate.typeName,
methodName: candidate.methodName,
methodParameters: candidate.methodParameters,
},
];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import { assertNever } from "../../common/helpers-pure";
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
import { ModelConfigListener } from "../../config";
import { DatabaseItem } from "../../databases/local-databases";
import {
convertFromLegacyModeledMethod,
convertToLegacyModeledMethod,
} from "../modeled-methods-legacy";

export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
ToMethodModelingMessage,
Expand Down Expand Up @@ -70,7 +74,9 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
await this.postMessage({
t: "setSelectedMethod",
method: selectedMethod.method,
modeledMethod: selectedMethod.modeledMethod,
modeledMethod: convertToLegacyModeledMethod(
selectedMethod.modeledMethods,
),
isModified: selectedMethod.isModified,
});
}
Expand Down Expand Up @@ -107,9 +113,10 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
case "setModeledMethod": {
const activeState = this.ensureActiveState();

this.modelingStore.updateModeledMethod(
this.modelingStore.updateModeledMethods(
activeState.databaseItem,
msg.method,
msg.method.signature,
convertFromLegacyModeledMethod(msg.method),
);
this.modelingStore.addModifiedMethod(
activeState.databaseItem,
Expand Down Expand Up @@ -158,12 +165,15 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
this.push(
this.modelingStore.onModeledMethodsChanged(async (e) => {
if (this.webviewView && e.isActiveDb) {
const modeledMethod = e.modeledMethods[this.method?.signature ?? ""];
if (modeledMethod) {
await this.postMessage({
t: "setModeledMethod",
method: modeledMethod,
});
const modeledMethods = e.modeledMethods[this.method?.signature ?? ""];
if (modeledMethods) {
const modeledMethod = convertToLegacyModeledMethod(modeledMethods);
if (modeledMethod) {
await this.postMessage({
t: "setModeledMethod",
method: modeledMethod,
});
}
}
}
}),
Expand All @@ -190,7 +200,7 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
await this.postMessage({
t: "setSelectedMethod",
method: e.method,
modeledMethod: e.modeledMethod,
modeledMethod: convertToLegacyModeledMethod(e.modeledMethods),
isModified: e.isModified,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class MethodsUsageDataProvider
private databaseItem: DatabaseItem | undefined = undefined;
private sourceLocationPrefix: string | undefined = undefined;
private hideModeledMethods: boolean = INITIAL_HIDE_MODELED_METHODS_VALUE;
private modeledMethods: Record<string, ModeledMethod> = {};
private modeledMethods: Record<string, ModeledMethod[]> = {};
private modifiedMethodSignatures: Set<string> = new Set();

private readonly onDidChangeTreeDataEmitter = this.push(
Expand All @@ -52,7 +52,7 @@ export class MethodsUsageDataProvider
methods: Method[],
databaseItem: DatabaseItem,
hideModeledMethods: boolean,
modeledMethods: Record<string, ModeledMethod>,
modeledMethods: Record<string, ModeledMethod[]>,
modifiedMethodSignatures: Set<string>,
): Promise<void> {
if (
Expand Down Expand Up @@ -99,13 +99,10 @@ export class MethodsUsageDataProvider
}

private getModelingStatusIcon(method: Method): ThemeIcon {
const modeledMethod = this.modeledMethods[method.signature];
const modeledMethods = this.modeledMethods[method.signature];
const modifiedMethod = this.modifiedMethodSignatures.has(method.signature);

const status = getModelingStatus(
modeledMethod ? [modeledMethod] : [],
modifiedMethod,
);
const status = getModelingStatus(modeledMethods, modifiedMethod);
switch (status) {
case "unmodeled":
return new ThemeIcon("error", new ThemeColor("errorForeground"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class MethodsUsagePanel extends DisposableObject {
methods: Method[],
databaseItem: DatabaseItem,
hideModeledMethods: boolean,
modeledMethods: Record<string, ModeledMethod>,
modeledMethods: Record<string, ModeledMethod[]>,
modifiedMethodSignatures: Set<string>,
): Promise<void> {
await this.dataProvider.setState(
Expand Down
38 changes: 24 additions & 14 deletions extensions/ql-vscode/src/model-editor/model-editor-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import { telemetryListener } from "../common/vscode/telemetry";
import { ModelingStore } from "./modeling-store";
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
import {
convertFromLegacyModeledMethods,
convertFromLegacyModeledMethod,
convertToLegacyModeledMethods,
} from "./modeled-methods-legacy";

Expand Down Expand Up @@ -224,7 +224,7 @@ export class ModelEditorView extends AbstractWebview<
this.extensionPack,
this.databaseItem.language,
methods,
convertFromLegacyModeledMethods(modeledMethods),
modeledMethods,
this.mode,
this.cliServer,
this.app.logger,
Expand Down Expand Up @@ -311,7 +311,10 @@ export class ModelEditorView extends AbstractWebview<
);
break;
case "setModeledMethod": {
this.setModeledMethod(msg.method);
this.setModeledMethods(
msg.method.signature,
convertFromLegacyModeledMethod(msg.method),
);
break;
}
default:
Expand Down Expand Up @@ -371,10 +374,7 @@ export class ModelEditorView extends AbstractWebview<
this.cliServer,
this.app.logger,
);
this.modelingStore.setModeledMethods(
this.databaseItem,
convertToLegacyModeledMethods(modeledMethods),
);
this.modelingStore.setModeledMethods(this.databaseItem, modeledMethods);
} catch (e: unknown) {
void showAndLogErrorMessage(
this.app.logger,
Expand Down Expand Up @@ -446,10 +446,16 @@ export class ModelEditorView extends AbstractWebview<
queryStorageDir: this.queryStorageDir,
databaseItem: addedDatabase ?? this.databaseItem,
onResults: async (modeledMethods) => {
const modeledMethodsByName: Record<string, ModeledMethod> = {};
const modeledMethodsByName: Record<string, ModeledMethod[]> = {};

for (const modeledMethod of modeledMethods) {
modeledMethodsByName[modeledMethod.signature] = modeledMethod;
if (!(modeledMethod.signature in modeledMethodsByName)) {
modeledMethodsByName[modeledMethod.signature] = [];
}

modeledMethodsByName[modeledMethod.signature].push(
modeledMethod,
);
}

this.addModeledMethods(modeledMethodsByName);
Expand Down Expand Up @@ -620,7 +626,7 @@ export class ModelEditorView extends AbstractWebview<
if (event.dbUri === this.databaseItem.databaseUri.toString()) {
await this.postMessage({
t: "setModeledMethods",
methods: event.modeledMethods,
methods: convertToLegacyModeledMethods(event.modeledMethods),
});
}
}),
Expand All @@ -646,7 +652,7 @@ export class ModelEditorView extends AbstractWebview<
);
}

private addModeledMethods(modeledMethods: Record<string, ModeledMethod>) {
private addModeledMethods(modeledMethods: Record<string, ModeledMethod[]>) {
this.modelingStore.addModeledMethods(this.databaseItem, modeledMethods);

this.modelingStore.addModifiedMethods(
Expand All @@ -655,13 +661,17 @@ export class ModelEditorView extends AbstractWebview<
);
}

private setModeledMethod(method: ModeledMethod) {
private setModeledMethods(signature: string, methods: ModeledMethod[]) {
const state = this.modelingStore.getStateForActiveDb();
if (!state) {
throw new Error("Attempting to set modeled method without active db");
}

this.modelingStore.updateModeledMethod(state.databaseItem, method);
this.modelingStore.addModifiedMethod(state.databaseItem, method.signature);
this.modelingStore.updateModeledMethods(
state.databaseItem,
signature,
methods,
);
this.modelingStore.addModifiedMethod(state.databaseItem, signature);
}
}
62 changes: 48 additions & 14 deletions extensions/ql-vscode/src/model-editor/modeled-methods-legacy.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,57 @@
import { ModeledMethod } from "./modeled-method";

export function convertFromLegacyModeledMethods(
modeledMethods: Record<string, ModeledMethod>,
): Record<string, ModeledMethod[]> {
// Convert a single ModeledMethod to an array of ModeledMethods
return Object.fromEntries(
Object.entries(modeledMethods).map(([signature, modeledMethod]) => {
return [signature, [modeledMethod]];
}),
);
}

/**
* Converts a record of a single ModeledMethod indexed by signature to a record of ModeledMethod[] indexed by signature
* for legacy usage. This function should always be used instead of the trivial conversion to track usages of this
* conversion.
*
* This method should only be called inside a `postMessage` call. If it's used anywhere else, consider whether the
* boundary is correct: the boundary should as close as possible to the extension host -> webview boundary.
*
* @param modeledMethods The record of a single ModeledMethod indexed by signature
*/
export function convertToLegacyModeledMethods(
modeledMethods: Record<string, ModeledMethod[]>,
): Record<string, ModeledMethod> {
// Always take the first modeled method in the array
return Object.fromEntries(
Object.entries(modeledMethods).map(([signature, modeledMethods]) => {
return [signature, modeledMethods[0]];
}),
Object.entries(modeledMethods)
.map(([signature, modeledMethods]) => {
const modeledMethod = convertToLegacyModeledMethod(modeledMethods);
if (!modeledMethod) {
return null;
}
return [signature, modeledMethod];
})
.filter((entry): entry is [string, ModeledMethod] => entry !== null),
);
}

/**
* Converts a single ModeledMethod to a ModeledMethod[] for legacy usage. This function should always be used instead
* of the trivial conversion to track usages of this conversion.
*
* This method should only be called inside a `onMessage` function (or its equivalent). If it's used anywhere else,
* consider whether the boundary is correct: the boundary should as close as possible to the webview -> extension host
* boundary.
*
* @param modeledMethod The single ModeledMethod
*/
export function convertFromLegacyModeledMethod(modeledMethod: ModeledMethod) {
return [modeledMethod];
}

/**
* Converts a ModeledMethod[] to a single ModeledMethod for legacy usage. This function should always be used instead
* of the trivial conversion to track usages of this conversion.
*
* This method should only be called inside a `postMessage` call. If it's used anywhere else, consider whether the
* boundary is correct: the boundary should as close as possible to the extension host -> webview boundary.
*
* @param modeledMethods The ModeledMethod[]
*/
export function convertToLegacyModeledMethod(
modeledMethods: ModeledMethod[],
): ModeledMethod | undefined {
return modeledMethods[0];
}
Loading