Skip to content

Commit e90f3d0

Browse files
committed
Keep track of checkbox state in view
This will add a new `useState` call on the top-level to keep track of the checkbox state. It will allow all downloaded repositories to be selected. This will allow us to make the copy repository list and export results button dependent on the selected repositories.
1 parent 7e8fa5c commit e90f3d0

File tree

5 files changed

+119
-5
lines changed

5 files changed

+119
-5
lines changed

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

Lines changed: 44 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,21 @@ const canExpand = (
101104
return downloadStatus === VariantAnalysisScannedRepositoryDownloadStatus.Succeeded || downloadStatus === VariantAnalysisScannedRepositoryDownloadStatus.Failed;
102105
};
103106

107+
const canSelect = (
108+
status: VariantAnalysisRepoStatus | undefined,
109+
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus | undefined,
110+
): boolean => {
111+
if (!status) {
112+
return false;
113+
}
114+
115+
if (status !== VariantAnalysisRepoStatus.Succeeded) {
116+
return false;
117+
}
118+
119+
return downloadStatus === VariantAnalysisScannedRepositoryDownloadStatus.Succeeded;
120+
};
121+
104122
const isExpandableContentLoaded = (
105123
status: VariantAnalysisRepoStatus | undefined,
106124
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus | undefined,
@@ -133,6 +151,8 @@ export const RepoRow = ({
133151
resultCount,
134152
interpretedResults,
135153
rawResults,
154+
selected,
155+
onSelectedChange,
136156
}: RepoRowProps) => {
137157
const [isExpanded, setExpanded] = useState(false);
138158
const resultsLoaded = !!interpretedResults || !!rawResults;
@@ -163,13 +183,35 @@ export const RepoRow = ({
163183
}
164184
}, [resultsLoaded, resultsLoading]);
165185

186+
const onClickCheckbox = useCallback((e: React.MouseEvent) => {
187+
// Prevent calling the onClick event of the container, which would toggle the expanded state
188+
e.stopPropagation();
189+
}, []);
190+
const onChangeCheckbox = useCallback((e: ChangeEvent<HTMLInputElement>) => {
191+
// This is called on first render, but we don't really care about this value
192+
if (e.target.checked === undefined) {
193+
return;
194+
}
195+
196+
if (!repository.id) {
197+
return;
198+
}
199+
200+
onSelectedChange?.(repository.id, e.target.checked);
201+
}, [onSelectedChange, repository]);
202+
166203
const disabled = !canExpand(status, downloadStatus);
167204
const expandableContentLoaded = isExpandableContentLoaded(status, downloadStatus, resultsLoaded);
168205

169206
return (
170207
<div>
171208
<TitleContainer onClick={toggleExpanded} disabled={disabled} aria-expanded={isExpanded}>
172-
<VSCodeCheckbox disabled />
209+
<VSCodeCheckbox
210+
onChange={onChangeCheckbox}
211+
onClick={onClickCheckbox}
212+
checked={selected}
213+
disabled={!repository.id || !canSelect(status, downloadStatus)}
214+
/>
173215
{isExpanded ? <ExpandCollapseCodicon name="chevron-down" label="Collapse" /> :
174216
<ExpandCollapseCodicon name="chevron-right" label="Expand" />}
175217
<VSCodeBadge>{resultCount === undefined ? '-' : formatDecimal(resultCount)}</VSCodeBadge>

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

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

54+
const [selectedRepositoryIds, setSelectedRepositoryIds] = useState<number[]>([]);
55+
5456
useEffect(() => {
5557
const listener = (evt: MessageEvent) => {
5658
if (evt.origin === window.origin) {
@@ -103,6 +105,8 @@ export function VariantAnalysis({
103105
variantAnalysis={variantAnalysis}
104106
repositoryStates={repoStates}
105107
repositoryResults={repoResults}
108+
selectedRepositoryIds={selectedRepositoryIds}
109+
setSelectedRepositoryIds={setSelectedRepositoryIds}
106110
/>
107111
</>
108112
);

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 {
@@ -22,13 +22,18 @@ export type VariantAnalysisAnalyzedReposProps = {
2222
repositoryResults?: VariantAnalysisScannedRepositoryResult[];
2323

2424
filterSortState?: RepositoriesFilterSortState;
25+
26+
selectedRepositoryIds?: number[];
27+
setSelectedRepositoryIds?: Dispatch<SetStateAction<number[]>>;
2528
}
2629

2730
export const VariantAnalysisAnalyzedRepos = ({
2831
variantAnalysis,
2932
repositoryStates,
3033
repositoryResults,
3134
filterSortState,
35+
selectedRepositoryIds,
36+
setSelectedRepositoryIds,
3237
}: VariantAnalysisAnalyzedReposProps) => {
3338
const repositoryStateById = useMemo(() => {
3439
const map = new Map<number, VariantAnalysisScannedRepositoryState>();
@@ -52,6 +57,20 @@ export const VariantAnalysisAnalyzedRepos = ({
5257
})?.sort(compareWithResults(filterSortState));
5358
}, [filterSortState, variantAnalysis.scannedRepos]);
5459

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

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { useState } from 'react';
2+
import { Dispatch, SetStateAction, useState } from 'react';
33
import styled from 'styled-components';
44
import { VSCodeBadge, VSCodePanels, VSCodePanelTab, VSCodePanelView } from '@vscode/webview-ui-toolkit/react';
55
import { formatDecimal } from '../../pure/number';
@@ -20,6 +20,9 @@ export type VariantAnalysisOutcomePanelProps = {
2020
variantAnalysis: VariantAnalysis;
2121
repositoryStates?: VariantAnalysisScannedRepositoryState[];
2222
repositoryResults?: VariantAnalysisScannedRepositoryResult[];
23+
24+
selectedRepositoryIds?: number[];
25+
setSelectedRepositoryIds?: Dispatch<SetStateAction<number[]>>;
2326
};
2427

2528
const Tab = styled(VSCodePanelTab)`
@@ -46,6 +49,8 @@ export const VariantAnalysisOutcomePanels = ({
4649
variantAnalysis,
4750
repositoryStates,
4851
repositoryResults,
52+
selectedRepositoryIds,
53+
setSelectedRepositoryIds,
4954
}: VariantAnalysisOutcomePanelProps) => {
5055
const [filterSortState, setFilterSortState] = useState<RepositoriesFilterSortState>(defaultFilterSortState);
5156

@@ -94,6 +99,8 @@ export const VariantAnalysisOutcomePanels = ({
9499
repositoryStates={repositoryStates}
95100
repositoryResults={repositoryResults}
96101
filterSortState={filterSortState}
102+
selectedRepositoryIds={selectedRepositoryIds}
103+
setSelectedRepositoryIds={setSelectedRepositoryIds}
97104
/>
98105
</>
99106
);
@@ -126,6 +133,8 @@ export const VariantAnalysisOutcomePanels = ({
126133
repositoryStates={repositoryStates}
127134
repositoryResults={repositoryResults}
128135
filterSortState={filterSortState}
136+
selectedRepositoryIds={selectedRepositoryIds}
137+
setSelectedRepositoryIds={setSelectedRepositoryIds}
129138
/>
130139
</VSCodePanelView>
131140
{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)