diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index edfdd080ca2..236d8426bbb 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -871,6 +871,7 @@ async function activateWithInstalledDistribution( ctx, queryHistoryConfigurationListener, labelProvider, + languageContext, async ( from: CompletedLocalQueryInfo, to: CompletedLocalQueryInfo, diff --git a/extensions/ql-vscode/src/query-history/history-tree-data-provider.ts b/extensions/ql-vscode/src/query-history/history-tree-data-provider.ts index 4e7586d4408..c7a02d7c9ff 100644 --- a/extensions/ql-vscode/src/query-history/history-tree-data-provider.ts +++ b/extensions/ql-vscode/src/query-history/history-tree-data-provider.ts @@ -10,9 +10,10 @@ import { } from "vscode"; import { DisposableObject } from "../common/disposable-object"; import { assertNever } from "../common/helpers-pure"; -import { QueryHistoryInfo } from "./query-history-info"; +import { getLanguage, QueryHistoryInfo } from "./query-history-info"; import { QueryStatus } from "./query-status"; import { HistoryItemLabelProvider } from "./history-item-label-provider"; +import { LanguageContextStore } from "../language-context-store"; export enum SortOrder { NameAsc = "NameAsc", @@ -50,7 +51,10 @@ export class HistoryTreeDataProvider private current: QueryHistoryInfo | undefined; - constructor(private readonly labelProvider: HistoryItemLabelProvider) { + constructor( + private readonly labelProvider: HistoryItemLabelProvider, + private readonly languageContext: LanguageContextStore, + ) { super(); } @@ -127,51 +131,55 @@ export class HistoryTreeDataProvider getChildren(element?: QueryHistoryInfo): ProviderResult { return element ? [] - : this.history.sort((h1, h2) => { - const h1Label = this.labelProvider.getLabel(h1).toLowerCase(); - const h2Label = this.labelProvider.getLabel(h2).toLowerCase(); - - const h1Date = this.getItemDate(h1); - - const h2Date = this.getItemDate(h2); - - const resultCount1 = - h1.t === "local" - ? h1.completedQuery?.resultCount ?? -1 - : h1.resultCount ?? -1; - const resultCount2 = - h2.t === "local" - ? h2.completedQuery?.resultCount ?? -1 - : h2.resultCount ?? -1; - - switch (this.sortOrder) { - case SortOrder.NameAsc: - return h1Label.localeCompare(h2Label, env.language); - - case SortOrder.NameDesc: - return h2Label.localeCompare(h1Label, env.language); - - case SortOrder.DateAsc: - return h1Date - h2Date; - - case SortOrder.DateDesc: - return h2Date - h1Date; - - case SortOrder.CountAsc: - // If the result counts are equal, sort by name. - return resultCount1 - resultCount2 === 0 - ? h1Label.localeCompare(h2Label, env.language) - : resultCount1 - resultCount2; - - case SortOrder.CountDesc: - // If the result counts are equal, sort by name. - return resultCount2 - resultCount1 === 0 - ? h2Label.localeCompare(h1Label, env.language) - : resultCount2 - resultCount1; - default: - assertNever(this.sortOrder); - } - }); + : this.history + .filter((h) => { + return this.languageContext.shouldInclude(getLanguage(h)); + }) + .sort((h1, h2) => { + const h1Label = this.labelProvider.getLabel(h1).toLowerCase(); + const h2Label = this.labelProvider.getLabel(h2).toLowerCase(); + + const h1Date = this.getItemDate(h1); + + const h2Date = this.getItemDate(h2); + + const resultCount1 = + h1.t === "local" + ? h1.completedQuery?.resultCount ?? -1 + : h1.resultCount ?? -1; + const resultCount2 = + h2.t === "local" + ? h2.completedQuery?.resultCount ?? -1 + : h2.resultCount ?? -1; + + switch (this.sortOrder) { + case SortOrder.NameAsc: + return h1Label.localeCompare(h2Label, env.language); + + case SortOrder.NameDesc: + return h2Label.localeCompare(h1Label, env.language); + + case SortOrder.DateAsc: + return h1Date - h2Date; + + case SortOrder.DateDesc: + return h2Date - h1Date; + + case SortOrder.CountAsc: + // If the result counts are equal, sort by name. + return resultCount1 - resultCount2 === 0 + ? h1Label.localeCompare(h2Label, env.language) + : resultCount1 - resultCount2; + + case SortOrder.CountDesc: + // If the result counts are equal, sort by name. + return resultCount2 - resultCount1 === 0 + ? h2Label.localeCompare(h1Label, env.language) + : resultCount2 - resultCount1; + default: + assertNever(this.sortOrder); + } + }); } getParent(_element: QueryHistoryInfo): ProviderResult { diff --git a/extensions/ql-vscode/src/query-history/query-history-manager.ts b/extensions/ql-vscode/src/query-history/query-history-manager.ts index f5670591bf9..d1ed293f801 100644 --- a/extensions/ql-vscode/src/query-history/query-history-manager.ts +++ b/extensions/ql-vscode/src/query-history/query-history-manager.ts @@ -62,6 +62,7 @@ import { showAndLogInformationMessage, showAndLogWarningMessage, } from "../common/logging"; +import { LanguageContextStore } from "../language-context-store"; /** * query-history-manager.ts @@ -141,6 +142,7 @@ export class QueryHistoryManager extends DisposableObject { ctx: ExtensionContext, private readonly queryHistoryConfigListener: QueryHistoryConfig, private readonly labelProvider: HistoryItemLabelProvider, + private readonly languageContext: LanguageContextStore, private readonly doCompareCallback: ( from: CompletedLocalQueryInfo, to: CompletedLocalQueryInfo, @@ -158,7 +160,7 @@ export class QueryHistoryManager extends DisposableObject { ); this.treeDataProvider = this.push( - new HistoryTreeDataProvider(this.labelProvider), + new HistoryTreeDataProvider(this.labelProvider, this.languageContext), ); this.treeView = this.push( window.createTreeView("codeQLQueryHistory", { @@ -230,6 +232,12 @@ export class QueryHistoryManager extends DisposableObject { this.registerQueryHistoryScrubber(queryHistoryConfigListener, this, ctx); this.registerToVariantAnalysisEvents(); + + this.push( + this.languageContext.onLanguageContextChanged(async () => { + this.treeDataProvider.refresh(); + }), + ); } public getCommands(): QueryHistoryCommands { diff --git a/extensions/ql-vscode/test/factories/query-history/local-query-history-item.ts b/extensions/ql-vscode/test/factories/query-history/local-query-history-item.ts index b99db5bd365..edb8f94070a 100644 --- a/extensions/ql-vscode/test/factories/query-history/local-query-history-item.ts +++ b/extensions/ql-vscode/test/factories/query-history/local-query-history-item.ts @@ -7,6 +7,7 @@ import { import { CancellationTokenSource } from "vscode"; import { QueryResultType } from "../../../src/query-server/legacy-messages"; import { QueryMetadata } from "../../../src/common/interface-types"; +import { QueryLanguage } from "../../../src/common/query-language"; export function createMockLocalQueryInfo({ startTime = new Date(), @@ -16,6 +17,7 @@ export function createMockLocalQueryInfo({ dbName = "db-name", hasMetadata = false, queryWithResults = undefined, + language = undefined, }: { startTime?: Date; resultCount?: number; @@ -24,6 +26,7 @@ export function createMockLocalQueryInfo({ dbName?: string; hasMetadata?: boolean; queryWithResults?: QueryWithResults | undefined; + language?: QueryLanguage; }): LocalQueryInfo { const cancellationToken = { dispose: () => { @@ -40,6 +43,7 @@ export function createMockLocalQueryInfo({ databaseInfo: { databaseUri: "databaseUri", name: dbName, + language, }, start: startTime, id: faker.number.int().toString(), diff --git a/extensions/ql-vscode/test/factories/query-history/variant-analysis-history-item.ts b/extensions/ql-vscode/test/factories/query-history/variant-analysis-history-item.ts index 8675dd78acc..706cd1a9665 100644 --- a/extensions/ql-vscode/test/factories/query-history/variant-analysis-history-item.ts +++ b/extensions/ql-vscode/test/factories/query-history/variant-analysis-history-item.ts @@ -5,6 +5,7 @@ import { VariantAnalysisStatus, } from "../../../src/variant-analysis/shared/variant-analysis"; import { createMockVariantAnalysis } from "../variant-analysis/shared/variant-analysis"; +import { QueryLanguage } from "../../../src/common/query-language"; export function createMockVariantAnalysisHistoryItem({ historyItemStatus = QueryStatus.InProgress, @@ -14,6 +15,7 @@ export function createMockVariantAnalysisHistoryItem({ userSpecifiedLabel = undefined, executionStartTime = undefined, variantAnalysis = undefined, + language = QueryLanguage.Javascript, }: { historyItemStatus?: QueryStatus; variantAnalysisStatus?: VariantAnalysisStatus; @@ -22,6 +24,7 @@ export function createMockVariantAnalysisHistoryItem({ userSpecifiedLabel?: string; executionStartTime?: number; variantAnalysis?: VariantAnalysis; + language?: QueryLanguage; }): VariantAnalysisHistoryItem { return { t: "variant-analysis", @@ -34,6 +37,7 @@ export function createMockVariantAnalysisHistoryItem({ createMockVariantAnalysis({ status: variantAnalysisStatus, executionStartTime, + language, }), userSpecifiedLabel, }; diff --git a/extensions/ql-vscode/test/factories/variant-analysis/shared/variant-analysis.ts b/extensions/ql-vscode/test/factories/variant-analysis/shared/variant-analysis.ts index b149e29529e..2ff0aaa867e 100644 --- a/extensions/ql-vscode/test/factories/variant-analysis/shared/variant-analysis.ts +++ b/extensions/ql-vscode/test/factories/variant-analysis/shared/variant-analysis.ts @@ -15,11 +15,13 @@ export function createMockVariantAnalysis({ scannedRepos = createMockScannedRepos(), skippedRepos = createMockSkippedRepos(), executionStartTime = faker.number.int(), + language = QueryLanguage.Javascript, }: { status?: VariantAnalysisStatus; scannedRepos?: VariantAnalysisScannedRepository[]; skippedRepos?: VariantAnalysisSkippedRepositories; executionStartTime?: number | undefined; + language?: QueryLanguage; }): VariantAnalysis { return { id: faker.number.int(), @@ -32,7 +34,7 @@ export function createMockVariantAnalysis({ query: { name: "a-query-name", filePath: "a-query-file-path", - language: QueryLanguage.Javascript, + language, text: "a-query-text", }, databases: { diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/history-tree-data-provider.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/history-tree-data-provider.test.ts index 1969afe0a87..5468fa829f2 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/history-tree-data-provider.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/history-tree-data-provider.test.ts @@ -28,9 +28,13 @@ import { import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager"; import { createMockQueryHistoryDirs } from "../../../factories/query-history/query-history-dirs"; import { createMockApp } from "../../../__mocks__/appMock"; +import { LanguageContextStore } from "../../../../src/language-context-store"; +import { App } from "../../../../src/common/app"; +import { QueryLanguage } from "../../../../src/common/query-language"; describe("HistoryTreeDataProvider", () => { const mockExtensionLocation = join(tmpDir.name, "mock-extension-location"); + let app: App; let configListener: QueryHistoryConfigListener; const doCompareCallback = jest.fn(); @@ -45,10 +49,12 @@ describe("HistoryTreeDataProvider", () => { let historyTreeDataProvider: HistoryTreeDataProvider; let labelProvider: HistoryItemLabelProvider; + let languageContext: LanguageContextStore; beforeEach(() => { jest.spyOn(extLogger, "log").mockResolvedValue(undefined); + app = createMockApp({}); configListener = new QueryHistoryConfigListener(); localQueriesResultsViewStub = { showResults: jest.fn(), @@ -124,7 +130,11 @@ describe("HistoryTreeDataProvider", () => { ttlInMillis: 0, onDidChangeConfiguration: jest.fn(), }); - historyTreeDataProvider = new HistoryTreeDataProvider(labelProvider); + languageContext = new LanguageContextStore(app); + historyTreeDataProvider = new HistoryTreeDataProvider( + labelProvider, + languageContext, + ); }); afterEach(async () => { @@ -418,11 +428,68 @@ describe("HistoryTreeDataProvider", () => { expect(children).toEqual(expected); }); }); + + describe("filtering", () => { + const history = [ + createMockLocalQueryInfo({ + userSpecifiedLabel: "a", + // No language at all => unknown + }), + createMockVariantAnalysisHistoryItem({ + userSpecifiedLabel: "b", + // No specified language => javascript + }), + createMockLocalQueryInfo({ + userSpecifiedLabel: "c", + language: QueryLanguage.Python, + }), + createMockVariantAnalysisHistoryItem({ + userSpecifiedLabel: "d", + language: QueryLanguage.Java, + }), + ]; + + let treeDataProvider: HistoryTreeDataProvider; + + beforeEach(async () => { + queryHistoryManager = await createMockQueryHistory(allHistory); + (queryHistoryManager.treeDataProvider as any).history = [...history]; + treeDataProvider = queryHistoryManager.treeDataProvider; + }); + + it("should get all if no filter is provided", async () => { + const expected = [history[0], history[1], history[2], history[3]]; + treeDataProvider.sortOrder = SortOrder.NameAsc; + + const children = await treeDataProvider.getChildren(); + expect(children).toEqual(expected); + }); + + it("should filter local runs by language", async () => { + const expected = [history[3]]; + treeDataProvider.sortOrder = SortOrder.NameAsc; + + await languageContext.setLanguageContext(QueryLanguage.Java); + + const children = await treeDataProvider.getChildren(); + expect(children).toEqual(expected); + }); + + it("should filter variant analysis runs by language", async () => { + const expected = [history[2]]; + treeDataProvider.sortOrder = SortOrder.NameAsc; + + await languageContext.setLanguageContext(QueryLanguage.Python); + + const children = await treeDataProvider.getChildren(); + expect(children).toEqual(expected); + }); + }); }); async function createMockQueryHistory(allHistory: QueryHistoryInfo[]) { const qhm = new QueryHistoryManager( - createMockApp({}), + app, {} as QueryRunner, {} as DatabaseManager, localQueriesResultsViewStub, @@ -439,6 +506,7 @@ describe("HistoryTreeDataProvider", () => { ttlInMillis: 0, onDidChangeConfiguration: jest.fn(), }), + languageContext, doCompareCallback, ); (qhm.treeDataProvider as any).history = [...allHistory]; diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/query-history-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/query-history-manager.test.ts index 57068a774a2..8ab91b72677 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/query-history-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/query-history-manager.test.ts @@ -28,6 +28,7 @@ import { createMockQueryHistoryDirs } from "../../../factories/query-history/que import { createMockApp } from "../../../__mocks__/appMock"; import { App } from "../../../../src/common/app"; import { createMockCommandManager } from "../../../__mocks__/commandsMock"; +import { LanguageContextStore } from "../../../../src/language-context-store"; describe("QueryHistoryManager", () => { const mockExtensionLocation = join(tmpDir.name, "mock-extension-location"); @@ -937,6 +938,7 @@ describe("QueryHistoryManager", () => { ttlInMillis: 0, onDidChangeConfiguration: jest.fn(), }), + new LanguageContextStore(mockApp), doCompareCallback, ); (qhm.treeDataProvider as any).history = [...allHistory]; diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/variant-analysis-history.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/variant-analysis-history.test.ts index f371fff787e..c11b34e1c2c 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/variant-analysis-history.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/variant-analysis-history.test.ts @@ -23,6 +23,7 @@ import { QueryHistoryManager } from "../../../../src/query-history/query-history import { mockedObject } from "../../utils/mocking.helpers"; import { createMockQueryHistoryDirs } from "../../../factories/query-history/query-history-dirs"; import { createMockApp } from "../../../__mocks__/appMock"; +import { LanguageContextStore } from "../../../../src/language-context-store"; // set a higher timeout since recursive delete may take a while, expecially on Windows. jest.setTimeout(120000); @@ -74,8 +75,10 @@ describe("Variant Analyses and QueryHistoryManager", () => { join(STORAGE_DIR, "workspace-query-history.json"), ).queries; + const app = createMockApp({}); + qhm = new QueryHistoryManager( - createMockApp({}), + app, {} as QueryRunner, {} as DatabaseManager, localQueriesResultsViewStub, @@ -97,6 +100,7 @@ describe("Variant Analyses and QueryHistoryManager", () => { ttlInMillis: 0, onDidChangeConfiguration: jest.fn(), }), + new LanguageContextStore(app), asyncNoop, ); disposables.push(qhm);