Add modeling indicator to method usages panel (#2876)
This commit is contained in:
@@ -14,6 +14,9 @@ import { DatabaseItem } from "../../databases/local-databases";
|
||||
import { relative } from "path";
|
||||
import { CodeQLCliServer } from "../../codeql-cli/cli";
|
||||
import { INITIAL_HIDE_MODELED_METHODS_VALUE } from "../shared/hide-modeled-methods";
|
||||
import { getModelingStatus } from "../shared/modeling-status";
|
||||
import { assertNever } from "../../common/helpers-pure";
|
||||
import { ModeledMethod } from "../modeled-method";
|
||||
|
||||
export class MethodsUsageDataProvider
|
||||
extends DisposableObject
|
||||
@@ -23,6 +26,8 @@ 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 modifiedMethodSignatures: Set<string> = new Set();
|
||||
|
||||
private readonly onDidChangeTreeDataEmitter = this.push(
|
||||
new EventEmitter<void>(),
|
||||
@@ -47,17 +52,23 @@ export class MethodsUsageDataProvider
|
||||
methods: Method[],
|
||||
databaseItem: DatabaseItem,
|
||||
hideModeledMethods: boolean,
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
modifiedMethodSignatures: Set<string>,
|
||||
): Promise<void> {
|
||||
if (
|
||||
this.methods !== methods ||
|
||||
this.databaseItem !== databaseItem ||
|
||||
this.hideModeledMethods !== hideModeledMethods
|
||||
this.hideModeledMethods !== hideModeledMethods ||
|
||||
this.modeledMethods !== modeledMethods ||
|
||||
this.modifiedMethodSignatures !== modifiedMethodSignatures
|
||||
) {
|
||||
this.methods = methods;
|
||||
this.databaseItem = databaseItem;
|
||||
this.sourceLocationPrefix =
|
||||
await this.databaseItem.getSourceLocationPrefix(this.cliServer);
|
||||
this.hideModeledMethods = hideModeledMethods;
|
||||
this.modeledMethods = modeledMethods;
|
||||
this.modifiedMethodSignatures = modifiedMethodSignatures;
|
||||
|
||||
this.onDidChangeTreeDataEmitter.fire();
|
||||
}
|
||||
@@ -68,7 +79,7 @@ export class MethodsUsageDataProvider
|
||||
return {
|
||||
label: `${item.packageName}.${item.typeName}.${item.methodName}${item.methodParameters}`,
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
iconPath: new ThemeIcon("symbol-method"),
|
||||
iconPath: this.getModelingStatusIcon(item),
|
||||
};
|
||||
} else {
|
||||
const method = this.getParent(item);
|
||||
@@ -83,11 +94,30 @@ export class MethodsUsageDataProvider
|
||||
command: "codeQLModelEditor.jumpToUsageLocation",
|
||||
arguments: [method, item, this.databaseItem],
|
||||
},
|
||||
iconPath: new ThemeIcon("error", new ThemeColor("errorForeground")),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private getModelingStatusIcon(method: Method): ThemeIcon {
|
||||
const modeledMethod = this.modeledMethods[method.signature];
|
||||
const modifiedMethod = this.modifiedMethodSignatures.has(method.signature);
|
||||
|
||||
const status = getModelingStatus(modeledMethod, modifiedMethod);
|
||||
switch (status) {
|
||||
case "unmodeled":
|
||||
return new ThemeIcon("error", new ThemeColor("errorForeground"));
|
||||
case "unsaved":
|
||||
return new ThemeIcon("pass", new ThemeColor("testing.iconPassed"));
|
||||
case "saved":
|
||||
return new ThemeIcon(
|
||||
"pass-filled",
|
||||
new ThemeColor("testing.iconPassed"),
|
||||
);
|
||||
default:
|
||||
assertNever(status);
|
||||
}
|
||||
}
|
||||
|
||||
private relativePathWithinDatabase(uri: string): string {
|
||||
const parsedUri = Uri.parse(uri);
|
||||
if (this.sourceLocationPrefix) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Method, Usage } from "../method";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
import { CodeQLCliServer } from "../../codeql-cli/cli";
|
||||
import { ModelingStore } from "../modeling-store";
|
||||
import { ModeledMethod } from "../modeled-method";
|
||||
|
||||
export class MethodsUsagePanel extends DisposableObject {
|
||||
private readonly dataProvider: MethodsUsageDataProvider;
|
||||
@@ -33,8 +34,16 @@ export class MethodsUsagePanel extends DisposableObject {
|
||||
methods: Method[],
|
||||
databaseItem: DatabaseItem,
|
||||
hideModeledMethods: boolean,
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
modifiedMethodSignatures: Set<string>,
|
||||
): Promise<void> {
|
||||
await this.dataProvider.setState(methods, databaseItem, hideModeledMethods);
|
||||
await this.dataProvider.setState(
|
||||
methods,
|
||||
databaseItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
const numOfApis = hideModeledMethods
|
||||
? methods.filter((api) => !api.supported).length
|
||||
: methods.length;
|
||||
@@ -73,6 +82,14 @@ export class MethodsUsagePanel extends DisposableObject {
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
this.push(
|
||||
this.modelingStore.onModifiedMethodsChanged(async (event) => {
|
||||
if (event.isActiveDb) {
|
||||
await this.handleStateChangeEvent();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async handleStateChangeEvent(): Promise<void> {
|
||||
@@ -82,6 +99,8 @@ export class MethodsUsagePanel extends DisposableObject {
|
||||
activeState.methods,
|
||||
activeState.databaseItem,
|
||||
activeState.hideModeledMethods,
|
||||
activeState.modeledMethods,
|
||||
activeState.modifiedMethodSignatures,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ export class ModelingStore extends DisposableObject {
|
||||
const dbState = this.getState(dbItem);
|
||||
const dbUri = dbItem.databaseUri.toString();
|
||||
|
||||
dbState.methods = methods;
|
||||
dbState.methods = [...methods];
|
||||
|
||||
this.onMethodsChangedEventEmitter.fire({
|
||||
methods,
|
||||
@@ -204,13 +204,15 @@ export class ModelingStore extends DisposableObject {
|
||||
methods: Record<string, ModeledMethod>,
|
||||
) {
|
||||
this.changeModeledMethods(dbItem, (state) => {
|
||||
state.modeledMethods = methods;
|
||||
state.modeledMethods = { ...methods };
|
||||
});
|
||||
}
|
||||
|
||||
public updateModeledMethod(dbItem: DatabaseItem, method: ModeledMethod) {
|
||||
this.changeModeledMethods(dbItem, (state) => {
|
||||
state.modeledMethods[method.signature] = method;
|
||||
const newModeledMethods = { ...state.modeledMethods };
|
||||
newModeledMethods[method.signature] = method;
|
||||
state.modeledMethods = newModeledMethods;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -219,7 +221,7 @@ export class ModelingStore extends DisposableObject {
|
||||
methodSignatures: Set<string>,
|
||||
) {
|
||||
this.changeModifiedMethods(dbItem, (state) => {
|
||||
state.modifiedMethodSignatures = methodSignatures;
|
||||
state.modifiedMethodSignatures = new Set(methodSignatures);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,9 +230,11 @@ export class ModelingStore extends DisposableObject {
|
||||
methodSignatures: Iterable<string>,
|
||||
) {
|
||||
this.changeModifiedMethods(dbItem, (state) => {
|
||||
for (const signature of methodSignatures) {
|
||||
state.modifiedMethodSignatures.add(signature);
|
||||
}
|
||||
const newModifiedMethods = new Set([
|
||||
...state.modifiedMethodSignatures,
|
||||
...methodSignatures,
|
||||
]);
|
||||
state.modifiedMethodSignatures = newModifiedMethods;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -243,9 +247,11 @@ export class ModelingStore extends DisposableObject {
|
||||
methodSignatures: string[],
|
||||
) {
|
||||
this.changeModifiedMethods(dbItem, (state) => {
|
||||
methodSignatures.forEach((signature) => {
|
||||
state.modifiedMethodSignatures.delete(signature);
|
||||
});
|
||||
const newModifiedMethods = Array.from(
|
||||
state.modifiedMethodSignatures,
|
||||
).filter((s) => !methodSignatures.includes(s));
|
||||
|
||||
state.modifiedMethodSignatures = new Set(newModifiedMethods);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
createUsage,
|
||||
} from "../../../../factories/model-editor/method-factories";
|
||||
import { mockedObject } from "../../../utils/mocking.helpers";
|
||||
import { ModeledMethod } from "../../../../../src/model-editor/modeled-method";
|
||||
|
||||
describe("MethodsUsageDataProvider", () => {
|
||||
const mockCliServer = mockedObject<CodeQLCliServer>({});
|
||||
@@ -19,17 +20,31 @@ describe("MethodsUsageDataProvider", () => {
|
||||
describe("setState", () => {
|
||||
const hideModeledMethods = false;
|
||||
const methods: Method[] = [];
|
||||
const modeledMethods: Record<string, ModeledMethod> = {};
|
||||
const modifiedMethodSignatures: Set<string> = new Set();
|
||||
const dbItem = mockedObject<DatabaseItem>({
|
||||
getSourceLocationPrefix: () => "test",
|
||||
});
|
||||
|
||||
it("should not emit onDidChangeTreeData event when state has not changed", async () => {
|
||||
await dataProvider.setState(methods, dbItem, hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
const onDidChangeTreeDataListener = jest.fn();
|
||||
dataProvider.onDidChangeTreeData(onDidChangeTreeDataListener);
|
||||
|
||||
await dataProvider.setState(methods, dbItem, hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
expect(onDidChangeTreeDataListener).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -37,12 +52,24 @@ describe("MethodsUsageDataProvider", () => {
|
||||
it("should emit onDidChangeTreeData event when methods has changed", async () => {
|
||||
const methods2: Method[] = [];
|
||||
|
||||
await dataProvider.setState(methods, dbItem, hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
const onDidChangeTreeDataListener = jest.fn();
|
||||
dataProvider.onDidChangeTreeData(onDidChangeTreeDataListener);
|
||||
|
||||
await dataProvider.setState(methods2, dbItem, hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods2,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
expect(onDidChangeTreeDataListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -52,23 +79,97 @@ describe("MethodsUsageDataProvider", () => {
|
||||
getSourceLocationPrefix: () => "test",
|
||||
});
|
||||
|
||||
await dataProvider.setState(methods, dbItem, hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
const onDidChangeTreeDataListener = jest.fn();
|
||||
dataProvider.onDidChangeTreeData(onDidChangeTreeDataListener);
|
||||
|
||||
await dataProvider.setState(methods, dbItem2, hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem2,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
expect(onDidChangeTreeDataListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should emit onDidChangeTreeData event when hideModeledMethods has changed", async () => {
|
||||
await dataProvider.setState(methods, dbItem, hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
const onDidChangeTreeDataListener = jest.fn();
|
||||
dataProvider.onDidChangeTreeData(onDidChangeTreeDataListener);
|
||||
|
||||
await dataProvider.setState(methods, dbItem, !hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
!hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
expect(onDidChangeTreeDataListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should emit onDidChangeTreeData event when modeled methods has changed", async () => {
|
||||
const modeledMethods2: Record<string, ModeledMethod> = {};
|
||||
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
const onDidChangeTreeDataListener = jest.fn();
|
||||
dataProvider.onDidChangeTreeData(onDidChangeTreeDataListener);
|
||||
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods2,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
expect(onDidChangeTreeDataListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should emit onDidChangeTreeData event when modified method signatures has changed", async () => {
|
||||
const modifiedMethodSignatures2: Set<string> = new Set();
|
||||
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
const onDidChangeTreeDataListener = jest.fn();
|
||||
dataProvider.onDidChangeTreeData(onDidChangeTreeDataListener);
|
||||
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures2,
|
||||
);
|
||||
|
||||
expect(onDidChangeTreeDataListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -79,12 +180,24 @@ describe("MethodsUsageDataProvider", () => {
|
||||
});
|
||||
const methods2: Method[] = [];
|
||||
|
||||
await dataProvider.setState(methods, dbItem, hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
const onDidChangeTreeDataListener = jest.fn();
|
||||
dataProvider.onDidChangeTreeData(onDidChangeTreeDataListener);
|
||||
|
||||
await dataProvider.setState(methods2, dbItem2, !hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods2,
|
||||
dbItem2,
|
||||
!hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
expect(onDidChangeTreeDataListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -100,6 +213,9 @@ describe("MethodsUsageDataProvider", () => {
|
||||
});
|
||||
|
||||
const methods: Method[] = [supportedMethod, unsupportedMethod];
|
||||
const modeledMethods: Record<string, ModeledMethod> = {};
|
||||
const modifiedMethodSignatures: Set<string> = new Set();
|
||||
|
||||
const dbItem = mockedObject<DatabaseItem>({
|
||||
getSourceLocationPrefix: () => "test",
|
||||
});
|
||||
@@ -117,13 +233,25 @@ describe("MethodsUsageDataProvider", () => {
|
||||
|
||||
it("should show all methods if hideModeledMethods is false and looking at the root", async () => {
|
||||
const hideModeledMethods = false;
|
||||
await dataProvider.setState(methods, dbItem, hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
expect(dataProvider.getChildren().length).toEqual(2);
|
||||
});
|
||||
|
||||
it("should filter methods if hideModeledMethods is true and looking at the root", async () => {
|
||||
const hideModeledMethods = true;
|
||||
await dataProvider.setState(methods, dbItem, hideModeledMethods);
|
||||
await dataProvider.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
expect(dataProvider.getChildren().length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "../../../../factories/model-editor/method-factories";
|
||||
import { ModelingStore } from "../../../../../src/model-editor/modeling-store";
|
||||
import { createMockModelingStore } from "../../../../__mocks__/model-editor/modelingStoreMock";
|
||||
import { ModeledMethod } from "../../../../../src/model-editor/modeled-method";
|
||||
|
||||
describe("MethodsUsagePanel", () => {
|
||||
const mockCliServer = mockedObject<CodeQLCliServer>({});
|
||||
@@ -20,6 +21,8 @@ describe("MethodsUsagePanel", () => {
|
||||
describe("setState", () => {
|
||||
const hideModeledMethods = false;
|
||||
const methods: Method[] = [createMethod()];
|
||||
const modeledMethods: Record<string, ModeledMethod> = {};
|
||||
const modifiedMethodSignatures: Set<string> = new Set();
|
||||
|
||||
it("should update the tree view with the correct batch number", async () => {
|
||||
const mockTreeView = {
|
||||
@@ -30,7 +33,13 @@ describe("MethodsUsagePanel", () => {
|
||||
const modelingStore = createMockModelingStore();
|
||||
|
||||
const panel = new MethodsUsagePanel(modelingStore, mockCliServer);
|
||||
await panel.setState(methods, dbItem, hideModeledMethods);
|
||||
await panel.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
expect(mockTreeView.badge?.value).toBe(1);
|
||||
});
|
||||
@@ -41,6 +50,8 @@ describe("MethodsUsagePanel", () => {
|
||||
let modelingStore: ModelingStore;
|
||||
|
||||
const hideModeledMethods: boolean = false;
|
||||
const modeledMethods: Record<string, ModeledMethod> = {};
|
||||
const modifiedMethodSignatures: Set<string> = new Set();
|
||||
const usage = createUsage();
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -60,7 +71,13 @@ describe("MethodsUsagePanel", () => {
|
||||
];
|
||||
|
||||
const panel = new MethodsUsagePanel(modelingStore, mockCliServer);
|
||||
await panel.setState(methods, dbItem, hideModeledMethods);
|
||||
await panel.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
await panel.revealItem(usage);
|
||||
|
||||
@@ -70,7 +87,13 @@ describe("MethodsUsagePanel", () => {
|
||||
it("should do nothing if usage cannot be found", async () => {
|
||||
const methods = [createMethod({})];
|
||||
const panel = new MethodsUsagePanel(modelingStore, mockCliServer);
|
||||
await panel.setState(methods, dbItem, hideModeledMethods);
|
||||
await panel.setState(
|
||||
methods,
|
||||
dbItem,
|
||||
hideModeledMethods,
|
||||
modeledMethods,
|
||||
modifiedMethodSignatures,
|
||||
);
|
||||
|
||||
await panel.revealItem(usage);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user