Skip to content

Commit 73ff057

Browse files
committed
Merge remote-tracking branch 'origin/main' into koesie10/filter-export-copy
2 parents bb4307e + 3451259 commit 73ff057

File tree

15 files changed

+233
-23
lines changed

15 files changed

+233
-23
lines changed

extensions/ql-vscode/src/common/app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ import { AppEventEmitter } from './events';
22

33
export interface App {
44
createEventEmitter<T>(): AppEventEmitter<T>;
5+
extensionPath: string;
6+
globalStoragePath: string;
7+
workspaceStoragePath?: string;
58
}

extensions/ql-vscode/src/common/vscode/vscode-app.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
1+
import * as vscode from 'vscode';
12
import { App } from '../app';
23
import { AppEventEmitter } from '../events';
34
import { VSCodeAppEventEmitter } from './events';
45

56
export class ExtensionApp implements App {
7+
public constructor(
8+
public readonly extensionContext: vscode.ExtensionContext
9+
) {
10+
}
11+
12+
public get extensionPath(): string {
13+
return this.extensionContext.extensionPath;
14+
}
15+
16+
public get globalStoragePath(): string {
17+
return this.extensionContext.globalStorageUri.fsPath;
18+
}
19+
20+
public get workspaceStoragePath(): string | undefined {
21+
return this.extensionContext.storageUri?.fsPath;
22+
}
23+
624
public createEventEmitter<T>(): AppEventEmitter<T> {
725
return new VSCodeAppEventEmitter<T>();
826
}

extensions/ql-vscode/src/databases/db-config-store.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as chokidar from 'chokidar';
55
import { DisposableObject } from '../pure/disposable-object';
66
import { DbConfigValidator } from './db-config-validator';
77
import { ValueResult } from '../common/value-result';
8+
import { App } from '../common/app';
89

910
export class DbConfigStore extends DisposableObject {
1011
private readonly configPath: string;
@@ -14,17 +15,16 @@ export class DbConfigStore extends DisposableObject {
1415
private configErrors: string[];
1516
private configWatcher: chokidar.FSWatcher | undefined;
1617

17-
public constructor(
18-
workspaceStoragePath: string,
19-
extensionPath: string) {
18+
public constructor(app: App) {
2019
super();
2120

22-
this.configPath = path.join(workspaceStoragePath, 'workspace-databases.json');
21+
const storagePath = app.workspaceStoragePath || app.globalStoragePath;
22+
this.configPath = path.join(storagePath, 'workspace-databases.json');
2323

2424
this.config = this.createEmptyConfig();
2525
this.configErrors = [];
2626
this.configWatcher = undefined;
27-
this.configValidator = new DbConfigValidator(extensionPath);
27+
this.configValidator = new DbConfigValidator(app.extensionPath);
2828
}
2929

3030
public async initialize(): Promise<void> {

extensions/ql-vscode/src/databases/db-module.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as vscode from 'vscode';
2+
import { ExtensionApp } from '../common/vscode/vscode-app';
23
import { isCanary, isNewQueryRunExperienceEnabled } from '../config';
34
import { logger } from '../logging';
45
import { DisposableObject } from '../pure/disposable-object';
@@ -21,9 +22,9 @@ export class DbModule extends DisposableObject {
2122

2223
void logger.log('Initializing database module');
2324

24-
const storagePath = extensionContext.storageUri?.fsPath || extensionContext.globalStorageUri.fsPath;
25-
const extensionPath = extensionContext.extensionPath;
26-
const dbConfigStore = new DbConfigStore(storagePath, extensionPath);
25+
const app = new ExtensionApp(extensionContext);
26+
27+
const dbConfigStore = new DbConfigStore(app);
2728
await dbConfigStore.initialize();
2829

2930
const dbManager = new DbManager(dbConfigStore);

extensions/ql-vscode/src/databases/ui/db-tree-view-item.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ import {
1515
*/
1616
export class DbTreeViewItem extends vscode.TreeItem {
1717
constructor(
18+
// iconPath and tooltip must have those names because
19+
// they are part of the vscode.TreeItem interface
20+
1821
public readonly dbItem: DbItem | undefined,
19-
public readonly icon: vscode.ThemeIcon | undefined,
22+
public readonly iconPath: vscode.ThemeIcon | undefined,
2023
public readonly label: string,
2124
public readonly tooltip: string | undefined,
2225
public readonly collapsibleState: vscode.TreeItemCollapsibleState,

extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { useCallback, useEffect, useState } from 'react';
2+
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
33
import styled from 'styled-components';
44
import { VSCodeBadge, VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
55
import {
@@ -80,6 +80,9 @@ export type RepoRowProps = {
8080

8181
interpretedResults?: AnalysisAlert[];
8282
rawResults?: AnalysisRawResults;
83+
84+
selected?: boolean;
85+
onSelectedChange?: (repositoryId: number, selected: boolean) => void;
8386
}
8487

8588
const canExpand = (
@@ -101,6 +104,11 @@ const canExpand = (
101104
return downloadStatus === VariantAnalysisScannedRepositoryDownloadStatus.Succeeded || downloadStatus === VariantAnalysisScannedRepositoryDownloadStatus.Failed;
102105
};
103106

107+
const canSelect = (
108+
status: VariantAnalysisRepoStatus | undefined,
109+
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus | undefined,
110+
) => status == VariantAnalysisRepoStatus.Succeeded && downloadStatus === VariantAnalysisScannedRepositoryDownloadStatus.Succeeded;
111+
104112
const isExpandableContentLoaded = (
105113
status: VariantAnalysisRepoStatus | undefined,
106114
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus | undefined,
@@ -133,6 +141,8 @@ export const RepoRow = ({
133141
resultCount,
134142
interpretedResults,
135143
rawResults,
144+
selected,
145+
onSelectedChange,
136146
}: RepoRowProps) => {
137147
const [isExpanded, setExpanded] = useState(false);
138148
const resultsLoaded = !!interpretedResults || !!rawResults;
@@ -163,13 +173,35 @@ export const RepoRow = ({
163173
}
164174
}, [resultsLoaded, resultsLoading]);
165175

176+
const onClickCheckbox = useCallback((e: React.MouseEvent) => {
177+
// Prevent calling the onClick event of the container, which would toggle the expanded state
178+
e.stopPropagation();
179+
}, []);
180+
const onChangeCheckbox = useCallback((e: ChangeEvent<HTMLInputElement>) => {
181+
// This is called on first render, but we don't really care about this value
182+
if (e.target.checked === undefined) {
183+
return;
184+
}
185+
186+
if (!repository.id) {
187+
return;
188+
}
189+
190+
onSelectedChange?.(repository.id, e.target.checked);
191+
}, [onSelectedChange, repository]);
192+
166193
const disabled = !canExpand(status, downloadStatus);
167194
const expandableContentLoaded = isExpandableContentLoaded(status, downloadStatus, resultsLoaded);
168195

169196
return (
170197
<div>
171198
<TitleContainer onClick={toggleExpanded} disabled={disabled} aria-expanded={isExpanded}>
172-
<VSCodeCheckbox disabled />
199+
<VSCodeCheckbox
200+
onChange={onChangeCheckbox}
201+
onClick={onClickCheckbox}
202+
checked={selected}
203+
disabled={!repository.id || !canSelect(status, downloadStatus)}
204+
/>
173205
{isExpanded ? <ExpandCollapseCodicon name="chevron-down" label="Collapse" /> :
174206
<ExpandCollapseCodicon name="chevron-right" label="Expand" />}
175207
<VSCodeBadge>{resultCount === undefined ? '-' : formatDecimal(resultCount)}</VSCodeBadge>

extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export function VariantAnalysis({
5252
const [repoStates, setRepoStates] = useState<VariantAnalysisScannedRepositoryState[]>(initialRepoStates);
5353
const [repoResults, setRepoResults] = useState<VariantAnalysisScannedRepositoryResult[]>(initialRepoResults);
5454

55+
const [selectedRepositoryIds, setSelectedRepositoryIds] = useState<number[]>([]);
5556
const [filterSortState, setFilterSortState] = useState<RepositoriesFilterSortState>(defaultFilterSortState);
5657

5758
useEffect(() => {
@@ -113,6 +114,8 @@ export function VariantAnalysis({
113114
variantAnalysis={variantAnalysis}
114115
repositoryStates={repoStates}
115116
repositoryResults={repoResults}
117+
selectedRepositoryIds={selectedRepositoryIds}
118+
setSelectedRepositoryIds={setSelectedRepositoryIds}
116119
filterSortState={filterSortState}
117120
setFilterSortState={setFilterSortState}
118121
/>

extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisAnalyzedRepos.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { useMemo } from 'react';
2+
import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
33
import styled from 'styled-components';
44
import { RepoRow } from './RepoRow';
55
import {
@@ -25,13 +25,18 @@ export type VariantAnalysisAnalyzedReposProps = {
2525
repositoryResults?: VariantAnalysisScannedRepositoryResult[];
2626

2727
filterSortState?: RepositoriesFilterSortState;
28+
29+
selectedRepositoryIds?: number[];
30+
setSelectedRepositoryIds?: Dispatch<SetStateAction<number[]>>;
2831
}
2932

3033
export const VariantAnalysisAnalyzedRepos = ({
3134
variantAnalysis,
3235
repositoryStates,
3336
repositoryResults,
3437
filterSortState,
38+
selectedRepositoryIds,
39+
setSelectedRepositoryIds,
3540
}: VariantAnalysisAnalyzedReposProps) => {
3641
const repositoryStateById = useMemo(() => {
3742
const map = new Map<number, VariantAnalysisScannedRepositoryState>();
@@ -53,6 +58,20 @@ export const VariantAnalysisAnalyzedRepos = ({
5358
return filterAndSortRepositoriesWithResults(variantAnalysis.scannedRepos, filterSortState);
5459
}, [filterSortState, variantAnalysis.scannedRepos]);
5560

61+
const onSelectedChange = useCallback((repositoryId: number, selected: boolean) => {
62+
setSelectedRepositoryIds?.((prevSelectedRepositoryIds) => {
63+
if (selected) {
64+
if (prevSelectedRepositoryIds.includes(repositoryId)) {
65+
return prevSelectedRepositoryIds;
66+
}
67+
68+
return [...prevSelectedRepositoryIds, repositoryId];
69+
} else {
70+
return prevSelectedRepositoryIds.filter((id) => id !== repositoryId);
71+
}
72+
});
73+
}, [setSelectedRepositoryIds]);
74+
5675
return (
5776
<Container>
5877
{repositories?.map(repository => {
@@ -68,6 +87,8 @@ export const VariantAnalysisAnalyzedRepos = ({
6887
resultCount={repository.resultCount}
6988
interpretedResults={results?.interpretedResults}
7089
rawResults={results?.rawResults}
90+
selected={selectedRepositoryIds?.includes(repository.repository.id)}
91+
onSelectedChange={onSelectedChange}
7192
/>
7293
);
7394
})}

extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisOutcomePanels.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export type VariantAnalysisOutcomePanelProps = {
2121
repositoryStates?: VariantAnalysisScannedRepositoryState[];
2222
repositoryResults?: VariantAnalysisScannedRepositoryResult[];
2323

24+
selectedRepositoryIds?: number[];
25+
setSelectedRepositoryIds?: Dispatch<SetStateAction<number[]>>;
26+
2427
filterSortState: RepositoriesFilterSortState;
2528
setFilterSortState: Dispatch<SetStateAction<RepositoriesFilterSortState>>;
2629
};
@@ -49,6 +52,8 @@ export const VariantAnalysisOutcomePanels = ({
4952
variantAnalysis,
5053
repositoryStates,
5154
repositoryResults,
55+
selectedRepositoryIds,
56+
setSelectedRepositoryIds,
5257
filterSortState,
5358
setFilterSortState,
5459
}: VariantAnalysisOutcomePanelProps) => {
@@ -97,6 +102,8 @@ export const VariantAnalysisOutcomePanels = ({
97102
repositoryStates={repositoryStates}
98103
repositoryResults={repositoryResults}
99104
filterSortState={filterSortState}
105+
selectedRepositoryIds={selectedRepositoryIds}
106+
setSelectedRepositoryIds={setSelectedRepositoryIds}
100107
/>
101108
</>
102109
);
@@ -129,6 +136,8 @@ export const VariantAnalysisOutcomePanels = ({
129136
repositoryStates={repositoryStates}
130137
repositoryResults={repositoryResults}
131138
filterSortState={filterSortState}
139+
selectedRepositoryIds={selectedRepositoryIds}
140+
setSelectedRepositoryIds={setSelectedRepositoryIds}
132141
/>
133142
</VSCodePanelView>
134143
{notFoundRepos?.repositoryCount &&

extensions/ql-vscode/src/view/variant-analysis/__tests__/RepoRow.spec.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { render as reactRender, screen } from '@testing-library/react';
2+
import { render as reactRender, screen, waitFor } from '@testing-library/react';
33
import {
44
VariantAnalysisRepoStatus,
55
VariantAnalysisScannedRepositoryDownloadStatus
@@ -330,4 +330,42 @@ describe(RepoRow.name, () => {
330330
expanded: false
331331
})).toBeDisabled();
332332
});
333+
334+
it('does not allow selecting the item if the item has not succeeded', async () => {
335+
render({
336+
status: VariantAnalysisRepoStatus.InProgress,
337+
});
338+
339+
expect(screen.getByRole('checkbox')).toBeDisabled();
340+
});
341+
342+
it('does not allow selecting the item if the item has not been downloaded', async () => {
343+
render({
344+
status: VariantAnalysisRepoStatus.Succeeded,
345+
});
346+
347+
expect(screen.getByRole('checkbox')).toBeDisabled();
348+
});
349+
350+
it('does not allow selecting the item if the item has not been downloaded successfully', async () => {
351+
render({
352+
status: VariantAnalysisRepoStatus.Succeeded,
353+
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Failed,
354+
});
355+
356+
// It seems like sometimes the first render doesn't have the checkbox disabled
357+
// Might be related to https://github.com/microsoft/vscode-webview-ui-toolkit/issues/404
358+
await waitFor(() => {
359+
expect(screen.getByRole('checkbox')).toBeDisabled();
360+
});
361+
});
362+
363+
it('allows selecting the item if the item has been downloaded', async () => {
364+
render({
365+
status: VariantAnalysisRepoStatus.Succeeded,
366+
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
367+
});
368+
369+
expect(screen.getByRole('checkbox')).toBeEnabled();
370+
});
333371
});

0 commit comments

Comments
 (0)