Merge pull request #3439 from github/robertbrignull/modeling-mode

Refactor events to fix the method modeling panel getting stuck showing "start modeling"
This commit is contained in:
Robert
2024-03-06 12:06:22 +00:00
committed by GitHub
8 changed files with 112 additions and 92 deletions

View File

@@ -698,16 +698,15 @@ interface SetMethodModelingPanelViewStateMessage {
viewState: MethodModelingPanelViewState; viewState: MethodModelingPanelViewState;
} }
interface SetMethodMessage {
t: "setMethod";
method: Method | undefined;
}
interface SetMethodModifiedMessage { interface SetMethodModifiedMessage {
t: "setMethodModified"; t: "setMethodModified";
isModified: boolean; isModified: boolean;
} }
interface SetNoMethodSelectedMessage {
t: "setNoMethodSelected";
}
interface SetSelectedMethodMessage { interface SetSelectedMethodMessage {
t: "setSelectedMethod"; t: "setSelectedMethod";
method: Method; method: Method;
@@ -719,9 +718,9 @@ interface SetSelectedMethodMessage {
export type ToMethodModelingMessage = export type ToMethodModelingMessage =
| SetMethodModelingPanelViewStateMessage | SetMethodModelingPanelViewStateMessage
| SetMethodMessage
| SetMultipleModeledMethodsMessage | SetMultipleModeledMethodsMessage
| SetMethodModifiedMessage | SetMethodModifiedMessage
| SetNoMethodSelectedMessage
| SetSelectedMethodMessage | SetSelectedMethodMessage
| SetInModelingModeMessage | SetInModelingModeMessage
| SetInProgressMessage | SetInProgressMessage

View File

@@ -2,10 +2,8 @@ import { window } from "vscode";
import type { App } from "../../common/app"; import type { App } from "../../common/app";
import { DisposableObject } from "../../common/disposable-object"; import { DisposableObject } from "../../common/disposable-object";
import { MethodModelingViewProvider } from "./method-modeling-view-provider"; import { MethodModelingViewProvider } from "./method-modeling-view-provider";
import type { Method } from "../method";
import type { ModelingStore } from "../modeling-store"; import type { ModelingStore } from "../modeling-store";
import { ModelConfigListener } from "../../config"; import { ModelConfigListener } from "../../config";
import type { DatabaseItem } from "../../databases/local-databases";
import type { ModelingEvents } from "../modeling-events"; import type { ModelingEvents } from "../modeling-events";
export class MethodModelingPanel extends DisposableObject { export class MethodModelingPanel extends DisposableObject {
@@ -36,11 +34,4 @@ export class MethodModelingPanel extends DisposableObject {
), ),
); );
} }
public async setMethod(
databaseItem: DatabaseItem,
method: Method,
): Promise<void> {
await this.provider.setMethod(databaseItem, method);
}
} }

View File

@@ -16,6 +16,7 @@ import type { ModelingEvents } from "../modeling-events";
import type { QueryLanguage } from "../../common/query-language"; import type { QueryLanguage } from "../../common/query-language";
import { tryGetQueryLanguage } from "../../common/query-language"; import { tryGetQueryLanguage } from "../../common/query-language";
import { createModelConfig } from "../languages"; import { createModelConfig } from "../languages";
import type { ModeledMethod } from "../modeled-method";
export class MethodModelingViewProvider extends AbstractWebviewViewProvider< export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
ToMethodModelingMessage, ToMethodModelingMessage,
@@ -37,7 +38,7 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
} }
protected override async onWebViewLoaded(): Promise<void> { protected override async onWebViewLoaded(): Promise<void> {
await Promise.all([this.setViewState(), this.setInitialState()]); await this.setInitialState();
this.registerToModelingEvents(); this.registerToModelingEvents();
this.registerToModelConfigEvents(); this.registerToModelConfigEvents();
} }
@@ -52,46 +53,60 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
}); });
} }
public async setMethod( private async setDatabaseItem(databaseItem: DatabaseItem): Promise<void> {
databaseItem: DatabaseItem | undefined, this.databaseItem = databaseItem;
method: Method | undefined,
await this.postMessage({
t: "setInModelingMode",
inModelingMode: true,
});
this.language = tryGetQueryLanguage(databaseItem.language);
await this.setViewState();
}
private async setSelectedMethod(
databaseItem: DatabaseItem,
method: Method,
modeledMethods: readonly ModeledMethod[],
isModified: boolean,
isInProgress: boolean,
processedByAutoModel: boolean,
): Promise<void> { ): Promise<void> {
this.method = method; this.method = method;
this.databaseItem = databaseItem; this.databaseItem = databaseItem;
this.language = databaseItem && tryGetQueryLanguage(databaseItem.language); this.language = tryGetQueryLanguage(databaseItem.language);
if (this.isShowingView) { await this.postMessage({
await this.postMessage({ t: "setSelectedMethod",
t: "setMethod", method,
method, modeledMethods,
}); isModified,
} isInProgress,
processedByAutoModel,
});
} }
private async setInitialState(): Promise<void> { private async setInitialState(): Promise<void> {
if (this.modelingStore.hasStateForActiveDb()) { await this.setViewState();
const selectedMethod = this.modelingStore.getSelectedMethodDetails();
if (selectedMethod) {
this.databaseItem = selectedMethod.databaseItem;
this.language = tryGetQueryLanguage(
selectedMethod.databaseItem.language,
);
this.method = selectedMethod.method;
await this.postMessage({ const stateForActiveDb = this.modelingStore.getStateForActiveDb();
t: "setSelectedMethod", if (!stateForActiveDb) {
method: selectedMethod.method, return;
modeledMethods: selectedMethod.modeledMethods, }
isModified: selectedMethod.isModified,
isInProgress: selectedMethod.isInProgress,
processedByAutoModel: selectedMethod.processedByAutoModel,
});
}
await this.postMessage({ await this.setDatabaseItem(stateForActiveDb.databaseItem);
t: "setInModelingMode",
inModelingMode: true, const selectedMethod = this.modelingStore.getSelectedMethodDetails();
}); if (selectedMethod) {
await this.setSelectedMethod(
stateForActiveDb.databaseItem,
selectedMethod.method,
selectedMethod.modeledMethods,
selectedMethod.isModified,
selectedMethod.isInProgress,
selectedMethod.processedByAutoModel,
);
} }
} }
@@ -183,33 +198,21 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
this.push( this.push(
this.modelingEvents.onSelectedMethodChanged(async (e) => { this.modelingEvents.onSelectedMethodChanged(async (e) => {
if (this.webviewView) { if (this.webviewView) {
this.method = e.method; await this.setSelectedMethod(
this.databaseItem = e.databaseItem; e.databaseItem,
this.language = tryGetQueryLanguage(e.databaseItem.language); e.method,
e.modeledMethods,
await this.postMessage({ e.isModified,
t: "setSelectedMethod", e.isInProgress,
method: e.method, e.processedByAutoModel,
modeledMethods: e.modeledMethods, );
isModified: e.isModified,
isInProgress: e.isInProgress,
processedByAutoModel: e.processedByAutoModel,
});
} }
}), }),
); );
this.push( this.push(
this.modelingEvents.onDbOpened(async (databaseItem) => { this.modelingEvents.onDbOpened(async (databaseItem) => {
this.databaseItem = databaseItem; await this.setDatabaseItem(databaseItem);
await this.postMessage({
t: "setInModelingMode",
inModelingMode: true,
});
this.language = tryGetQueryLanguage(databaseItem.language);
await this.setViewState();
}), }),
); );
@@ -223,7 +226,9 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
} }
if (dbUri === this.databaseItem?.databaseUri.toString()) { if (dbUri === this.databaseItem?.databaseUri.toString()) {
await this.setMethod(undefined, undefined); await this.postMessage({
t: "setNoMethodSelected",
});
} }
}), }),
); );

View File

@@ -57,7 +57,7 @@ export class MethodsUsagePanel extends DisposableObject {
}; };
} }
public async revealItem( private async revealItem(
methodSignature: string, methodSignature: string,
usage: Usage, usage: Usage,
): Promise<void> { ): Promise<void> {
@@ -108,6 +108,12 @@ export class MethodsUsagePanel extends DisposableObject {
} }
}), }),
); );
this.push(
this.modelingEvents.onSelectedMethodChanged(async (event) => {
await this.revealItem(event.method.signature, event.usage);
}),
);
} }
private async handleStateChangeEvent(): Promise<void> { private async handleStateChangeEvent(): Promise<void> {

View File

@@ -37,8 +37,6 @@ export class ModelEditorModule extends DisposableObject {
private readonly queryStorageDir: string; private readonly queryStorageDir: string;
private readonly modelingStore: ModelingStore; private readonly modelingStore: ModelingStore;
private readonly modelingEvents: ModelingEvents; private readonly modelingEvents: ModelingEvents;
private readonly methodsUsagePanel: MethodsUsagePanel;
private readonly methodModelingPanel: MethodModelingPanel;
private readonly modelConfig: ModelConfigListener; private readonly modelConfig: ModelConfigListener;
private constructor( private constructor(
@@ -53,10 +51,10 @@ export class ModelEditorModule extends DisposableObject {
this.queryStorageDir = join(baseQueryStorageDir, "model-editor-results"); this.queryStorageDir = join(baseQueryStorageDir, "model-editor-results");
this.modelingEvents = new ModelingEvents(app); this.modelingEvents = new ModelingEvents(app);
this.modelingStore = new ModelingStore(this.modelingEvents); this.modelingStore = new ModelingStore(this.modelingEvents);
this.methodsUsagePanel = this.push( this.push(
new MethodsUsagePanel(this.modelingStore, this.modelingEvents, cliServer), new MethodsUsagePanel(this.modelingStore, this.modelingEvents, cliServer),
); );
this.methodModelingPanel = this.push( this.push(
new MethodModelingPanel(app, this.modelingStore, this.modelingEvents), new MethodModelingPanel(app, this.modelingStore, this.modelingEvents),
); );
this.modelConfig = this.push(new ModelConfigListener()); this.modelConfig = this.push(new ModelConfigListener());
@@ -107,7 +105,11 @@ export class ModelEditorModule extends DisposableObject {
private registerToModelingEvents(): void { private registerToModelingEvents(): void {
this.push( this.push(
this.modelingEvents.onSelectedMethodChanged(async (event) => { this.modelingEvents.onSelectedMethodChanged(async (event) => {
await this.showMethod(event.databaseItem, event.method, event.usage); await showResolvableLocation(
event.usage.url,
event.databaseItem,
this.app.logger,
);
}), }),
); );
@@ -126,16 +128,6 @@ export class ModelEditorModule extends DisposableObject {
); );
} }
private async showMethod(
databaseItem: DatabaseItem,
method: Method,
usage: Usage,
): Promise<void> {
await this.methodsUsagePanel.revealItem(method.signature, usage);
await this.methodModelingPanel.setMethod(databaseItem, method);
await showResolvableLocation(usage.url, databaseItem, this.app.logger);
}
private async openModelEditor(): Promise<void> { private async openModelEditor(): Promise<void> {
{ {
const db = this.databaseManager.currentDatabaseItem; const db = this.databaseManager.currentDatabaseItem;

View File

@@ -53,15 +53,19 @@ export function MethodModelingView({
case "setInModelingMode": case "setInModelingMode":
setInModelingMode(msg.inModelingMode); setInModelingMode(msg.inModelingMode);
break; break;
case "setMethod":
setMethod(msg.method);
break;
case "setMultipleModeledMethods": case "setMultipleModeledMethods":
setModeledMethods(msg.modeledMethods); setModeledMethods(msg.modeledMethods);
break; break;
case "setMethodModified": case "setMethodModified":
setIsMethodModified(msg.isModified); setIsMethodModified(msg.isModified);
break; break;
case "setNoMethodSelected":
setMethod(undefined);
setModeledMethods([]);
setIsMethodModified(false);
setIsModelingInProgress(false);
setIsProcessedByAutoModel(false);
break;
case "setSelectedMethod": case "setSelectedMethod":
setMethod(msg.method); setMethod(msg.method);
setModeledMethods(msg.modeledMethods); setModeledMethods(msg.modeledMethods);

View File

@@ -4,6 +4,7 @@ import type { ModelingEvents } from "../../../src/model-editor/modeling-events";
export function createMockModelingEvents({ export function createMockModelingEvents({
onActiveDbChanged = jest.fn(), onActiveDbChanged = jest.fn(),
onDbClosed = jest.fn(), onDbClosed = jest.fn(),
onSelectedMethodChanged = jest.fn(),
onMethodsChanged = jest.fn(), onMethodsChanged = jest.fn(),
onHideModeledMethodsChanged = jest.fn(), onHideModeledMethodsChanged = jest.fn(),
onModeChanged = jest.fn(), onModeChanged = jest.fn(),
@@ -16,6 +17,7 @@ export function createMockModelingEvents({
}: { }: {
onActiveDbChanged?: ModelingEvents["onActiveDbChanged"]; onActiveDbChanged?: ModelingEvents["onActiveDbChanged"];
onDbClosed?: ModelingEvents["onDbClosed"]; onDbClosed?: ModelingEvents["onDbClosed"];
onSelectedMethodChanged?: ModelingEvents["onSelectedMethodChanged"];
onMethodsChanged?: ModelingEvents["onMethodsChanged"]; onMethodsChanged?: ModelingEvents["onMethodsChanged"];
onHideModeledMethodsChanged?: ModelingEvents["onHideModeledMethodsChanged"]; onHideModeledMethodsChanged?: ModelingEvents["onHideModeledMethodsChanged"];
onModeChanged?: ModelingEvents["onModeChanged"]; onModeChanged?: ModelingEvents["onModeChanged"];
@@ -29,6 +31,7 @@ export function createMockModelingEvents({
return mockedObject<ModelingEvents>({ return mockedObject<ModelingEvents>({
onActiveDbChanged, onActiveDbChanged,
onDbClosed, onDbClosed,
onSelectedMethodChanged,
onMethodsChanged, onMethodsChanged,
onHideModeledMethodsChanged, onHideModeledMethodsChanged,
onModeChanged, onModeChanged,

View File

@@ -1,5 +1,5 @@
import type { TreeView } from "vscode"; import type { TreeView } from "vscode";
import { window } from "vscode"; import { EventEmitter, window } from "vscode";
import type { CodeQLCliServer } from "../../../../../src/codeql-cli/cli"; import type { CodeQLCliServer } from "../../../../../src/codeql-cli/cli";
import type { Method } from "../../../../../src/model-editor/method"; import type { Method } from "../../../../../src/model-editor/method";
import { MethodsUsagePanel } from "../../../../../src/model-editor/methods-usage/methods-usage-panel"; import { MethodsUsagePanel } from "../../../../../src/model-editor/methods-usage/methods-usage-panel";
@@ -66,6 +66,8 @@ describe("MethodsUsagePanel", () => {
const modeledMethods: Record<string, ModeledMethod[]> = {}; const modeledMethods: Record<string, ModeledMethod[]> = {};
const modifiedMethodSignatures: Set<string> = new Set(); const modifiedMethodSignatures: Set<string> = new Set();
const usage = createUsage(); const usage = createUsage();
const selectedMethodChangedEmitter: ModelingEvents["onSelectedMethodChangedEventEmitter"] =
new EventEmitter();
beforeEach(() => { beforeEach(() => {
mockTreeView = mockedObject<TreeView<unknown>>({ mockTreeView = mockedObject<TreeView<unknown>>({
@@ -74,7 +76,9 @@ describe("MethodsUsagePanel", () => {
jest.spyOn(window, "createTreeView").mockReturnValue(mockTreeView); jest.spyOn(window, "createTreeView").mockReturnValue(mockTreeView);
modelingStore = createMockModelingStore(); modelingStore = createMockModelingStore();
modelingEvents = createMockModelingEvents(); modelingEvents = createMockModelingEvents({
onSelectedMethodChanged: selectedMethodChangedEmitter.event,
});
}); });
it("should reveal the correct item in the tree view", async () => { it("should reveal the correct item in the tree view", async () => {
@@ -97,7 +101,15 @@ describe("MethodsUsagePanel", () => {
modifiedMethodSignatures, modifiedMethodSignatures,
); );
await panel.revealItem(method.signature, usage); selectedMethodChangedEmitter.fire({
databaseItem: dbItem,
method,
usage,
modeledMethods: modeledMethods[method.signature],
isModified: modifiedMethodSignatures.has(method.signature),
isInProgress: false,
processedByAutoModel: false,
});
expect(mockTreeView.reveal).toHaveBeenCalledWith( expect(mockTreeView.reveal).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
@@ -124,7 +136,15 @@ describe("MethodsUsagePanel", () => {
modifiedMethodSignatures, modifiedMethodSignatures,
); );
await panel.revealItem(method.signature, usage); selectedMethodChangedEmitter.fire({
databaseItem: dbItem,
method,
usage,
modeledMethods: modeledMethods[method.signature],
isModified: modifiedMethodSignatures.has(method.signature),
isInProgress: false,
processedByAutoModel: false,
});
expect(mockTreeView.reveal).not.toHaveBeenCalled(); expect(mockTreeView.reveal).not.toHaveBeenCalled();
}); });