Skip to content

Commit 281f8ee

Browse files
authored
CodeQL model editor: support saving single/selected models (#3156)
1 parent ea1419a commit 281f8ee

13 files changed

Lines changed: 208 additions & 17 deletions

extensions/ql-vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [UNRELEASED]
44

5+
- In the CodeQL model editor, you can now select individual method rows and save changes to only the selected rows, instead of having to save the entire library model. [#3156](https://github.com/github/vscode-codeql/pull/3156)
56
- If you run a query without having selected a database, we show a more intuitive prompt to help you select a database. [#3214](https://github.com/github/vscode-codeql/pull/3214)
67
- The UI for browsing and running CodeQL tests has moved to use VS Code's built-in test UI. This makes the CodeQL test UI more consistent with the test UIs for other languages.
78
This change means that this extension no longer depends on the "Test Explorer UI" and "Test Adapter Converter" extensions. You can uninstall those two extensions if they are

extensions/ql-vscode/src/view/common/DataGrid.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ export function DataGrid({ gridTemplateColumns, children }: DataGridProps) {
3737
);
3838
}
3939

40-
const StyledDataGridRow = styled.div<{ $focused?: boolean }>`
40+
const StyledDataGridRow = styled.div<{
41+
$focused?: boolean;
42+
$selected?: boolean;
43+
}>`
4144
display: contents;
4245

4346
&:hover > * {
@@ -48,14 +51,18 @@ const StyledDataGridRow = styled.div<{ $focused?: boolean }>`
4851
// Use !important to override the background color set by the hover state
4952
background-color: ${(props) =>
5053
props.$focused
51-
? "var(--vscode-editor-selectionBackground) !important"
52-
: "inherit"};
54+
? "var(--vscode-editor-findMatchHighlightBackground) !important"
55+
: props.$selected
56+
? "var(--vscode-editor-selectionBackground) !important"
57+
: "inherit"};
5358
}
5459
`;
5560

5661
interface DataGridRowProps {
5762
focused?: boolean;
63+
selected?: boolean;
5864
children: ReactNode;
65+
onClick?: () => void;
5966
"data-testid"?: string;
6067
}
6168

@@ -69,10 +76,22 @@ interface DataGridRowProps {
6976
*/
7077
export const DataGridRow = forwardRef(
7178
(
72-
{ focused, children, "data-testid": testId }: DataGridRowProps,
79+
{
80+
focused,
81+
selected,
82+
children,
83+
"data-testid": testId,
84+
onClick,
85+
}: DataGridRowProps,
7386
ref?: React.Ref<HTMLElement | undefined>,
7487
) => (
75-
<StyledDataGridRow $focused={focused} ref={ref} data-testid={testId}>
88+
<StyledDataGridRow
89+
$focused={focused}
90+
$selected={selected}
91+
ref={ref}
92+
data-testid={testId}
93+
onClick={onClick}
94+
>
7695
{children}
7796
</StyledDataGridRow>
7897
),

extensions/ql-vscode/src/view/common/Dropdown.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ type Props = {
2525
"aria-label"?: string;
2626
};
2727

28+
const stopClickPropagation = (e: React.MouseEvent) => {
29+
e.stopPropagation();
30+
};
2831
/**
2932
* A dropdown implementation styled to look like `VSCodeDropdown`.
3033
*
@@ -50,6 +53,7 @@ export function Dropdown({
5053
value={disabled ? disabledValue : value}
5154
disabled={disabled}
5255
onChange={onChange}
56+
onClick={stopClickPropagation}
5357
className={className}
5458
{...props}
5559
>

extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@ export type LibraryRowProps = {
7171
methods: Method[];
7272
modeledMethodsMap: Record<string, ModeledMethod[]>;
7373
modifiedSignatures: Set<string>;
74+
selectedSignatures: Set<string>;
7475
inProgressMethods: Set<string>;
7576
viewState: ModelEditorViewState;
7677
hideModeledMethods: boolean;
7778
revealedMethodSignature: string | null;
7879
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
80+
onMethodClick: (methodSignature: string) => void;
7981
onSaveModelClick: (methodSignatures: string[]) => void;
8082
onGenerateFromLlmClick: (
8183
dependencyName: string,
@@ -92,11 +94,13 @@ export const LibraryRow = ({
9294
methods,
9395
modeledMethodsMap,
9496
modifiedSignatures,
97+
selectedSignatures,
9598
inProgressMethods,
9699
viewState,
97100
hideModeledMethods,
98101
revealedMethodSignature,
99102
onChange,
103+
onMethodClick,
100104
onSaveModelClick,
101105
onGenerateFromLlmClick,
102106
onStopGenerateFromLlmClick,
@@ -228,16 +232,18 @@ export const LibraryRow = ({
228232
methods={methods}
229233
modeledMethodsMap={modeledMethodsMap}
230234
modifiedSignatures={modifiedSignatures}
235+
selectedSignatures={selectedSignatures}
231236
inProgressMethods={inProgressMethods}
232237
viewState={viewState}
233238
hideModeledMethods={hideModeledMethods}
234239
revealedMethodSignature={revealedMethodSignature}
235240
onChange={onChange}
241+
onMethodClick={onMethodClick}
236242
/>
237243
<SectionDivider />
238244
<ButtonsContainer>
239245
<VSCodeButton onClick={handleSave} disabled={!hasUnsavedChanges}>
240-
Save
246+
{selectedSignatures.size === 0 ? "Save" : "Save selected"}
241247
</VSCodeButton>
242248
</ButtonsContainer>
243249
</>

extensions/ql-vscode/src/view/model-editor/MethodRow.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,12 @@ export type MethodRowProps = {
7070
methodCanBeModeled: boolean;
7171
modeledMethods: ModeledMethod[];
7272
methodIsUnsaved: boolean;
73+
methodIsSelected: boolean;
7374
modelingInProgress: boolean;
7475
viewState: ModelEditorViewState;
7576
revealedMethodSignature: string | null;
7677
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
78+
onMethodClick: (methodSignature: string) => void;
7779
};
7880

7981
export const MethodRow = (props: MethodRowProps) => {
@@ -103,9 +105,11 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
103105
method,
104106
modeledMethods: modeledMethodsProp,
105107
methodIsUnsaved,
108+
methodIsSelected,
106109
viewState,
107110
revealedMethodSignature,
108111
onChange,
112+
onMethodClick,
109113
} = props;
110114

111115
const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
@@ -186,6 +190,10 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
186190
<DataGridRow
187191
data-testid="modelable-method-row"
188192
focused={revealedMethodSignature === method.signature}
193+
selected={methodIsSelected}
194+
onClick={() => {
195+
onMethodClick(method.signature);
196+
}}
189197
>
190198
<DataGridCell
191199
gridRow={`span ${modeledMethods.length + validationErrors.length}`}
@@ -196,11 +204,23 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
196204
<MethodClassifications method={method} />
197205
<MethodName {...props.method} />
198206
{viewState.mode === Mode.Application && (
199-
<UsagesButton onClick={jumpToMethod}>
207+
<UsagesButton
208+
onClick={(event: React.MouseEvent) => {
209+
event.stopPropagation();
210+
jumpToMethod();
211+
}}
212+
>
200213
{method.usages.length}
201214
</UsagesButton>
202215
)}
203-
<ViewLink onClick={jumpToMethod}>View</ViewLink>
216+
<ViewLink
217+
onClick={(event: React.MouseEvent) => {
218+
event.stopPropagation();
219+
jumpToMethod();
220+
}}
221+
>
222+
View
223+
</ViewLink>
204224
{props.modelingInProgress && <ProgressRing />}
205225
</ApiOrMethodRow>
206226
</DataGridCell>
@@ -269,7 +289,10 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
269289
<CodiconRow
270290
appearance="icon"
271291
aria-label="Add new model"
272-
onClick={handleAddModelClick}
292+
onClick={(event: React.MouseEvent) => {
293+
event.stopPropagation();
294+
handleAddModelClick();
295+
}}
273296
disabled={addModelButtonDisabled}
274297
>
275298
<Codicon name="add" />
@@ -278,7 +301,10 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
278301
<CodiconRow
279302
appearance="icon"
280303
aria-label="Remove model"
281-
onClick={removeModelClickedHandlers[index]}
304+
onClick={(event: React.MouseEvent) => {
305+
event.stopPropagation();
306+
removeModelClickedHandlers[index]();
307+
}}
282308
>
283309
<Codicon name="trash" />
284310
</CodiconRow>

extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ export function ModelEditor({
9595
new Set(),
9696
);
9797

98+
const [selectedSignatures, setSelectedSignatures] = useState<Set<string>>(
99+
new Set(),
100+
);
101+
98102
const [inProgressMethods, setInProgressMethods] = useState<Set<string>>(
99103
new Set(),
100104
);
@@ -189,6 +193,19 @@ export function ModelEditor({
189193
[],
190194
);
191195

196+
const onMethodClick = useCallback(
197+
(methodSignature: string) => {
198+
const newSelectedSignatures = new Set(selectedSignatures);
199+
if (selectedSignatures.has(methodSignature)) {
200+
newSelectedSignatures.delete(methodSignature);
201+
} else {
202+
newSelectedSignatures.add(methodSignature);
203+
}
204+
setSelectedSignatures(newSelectedSignatures);
205+
},
206+
[selectedSignatures],
207+
);
208+
192209
const onRefreshClick = useCallback(() => {
193210
vscode.postMessage({
194211
t: "refreshMethods",
@@ -198,16 +215,32 @@ export function ModelEditor({
198215
const onSaveAllClick = useCallback(() => {
199216
vscode.postMessage({
200217
t: "saveModeledMethods",
218+
methodSignatures:
219+
selectedSignatures.size === 0
220+
? undefined
221+
: Array.from(selectedSignatures),
201222
});
202-
}, []);
223+
}, [selectedSignatures]);
203224

204-
const onSaveModelClick = useCallback((methodSignatures: string[]) => {
205-
vscode.postMessage({
206-
t: "saveModeledMethods",
207-
methodSignatures,
208-
});
225+
const onDeselectAllClick = useCallback(() => {
226+
setSelectedSignatures(new Set());
209227
}, []);
210228

229+
const onSaveModelClick = useCallback(
230+
(methodSignatures: string[]) => {
231+
vscode.postMessage({
232+
t: "saveModeledMethods",
233+
methodSignatures:
234+
selectedSignatures.size === 0
235+
? methodSignatures
236+
: methodSignatures.filter((signature) =>
237+
selectedSignatures.has(signature),
238+
),
239+
});
240+
},
241+
[selectedSignatures],
242+
);
243+
211244
const onGenerateFromSourceClick = useCallback(() => {
212245
vscode.postMessage({
213246
t: "generateMethod",
@@ -309,7 +342,14 @@ export function ModelEditor({
309342
onClick={onSaveAllClick}
310343
disabled={modifiedSignatures.size === 0}
311344
>
312-
Save all
345+
{selectedSignatures.size === 0 ? "Save all" : "Save selected"}
346+
</VSCodeButton>
347+
<VSCodeButton
348+
appearance="secondary"
349+
onClick={onDeselectAllClick}
350+
disabled={selectedSignatures.size === 0}
351+
>
352+
Deselect all
313353
</VSCodeButton>
314354
<VSCodeButton appearance="secondary" onClick={onRefreshClick}>
315355
Refresh
@@ -339,11 +379,13 @@ export function ModelEditor({
339379
methods={methods}
340380
modeledMethodsMap={modeledMethods}
341381
modifiedSignatures={modifiedSignatures}
382+
selectedSignatures={selectedSignatures}
342383
inProgressMethods={inProgressMethods}
343384
viewState={viewState}
344385
hideModeledMethods={hideModeledMethods}
345386
revealedMethodSignature={revealedMethodSignature}
346387
onChange={onChange}
388+
onMethodClick={onMethodClick}
347389
onSaveModelClick={onSaveModelClick}
348390
onGenerateFromLlmClick={onGenerateFromLlmClick}
349391
onStopGenerateFromLlmClick={onStopGenerateFromLlmClick}

extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,26 @@ export type ModeledMethodDataGridProps = {
1616
methods: Method[];
1717
modeledMethodsMap: Record<string, ModeledMethod[]>;
1818
modifiedSignatures: Set<string>;
19+
selectedSignatures: Set<string>;
1920
inProgressMethods: Set<string>;
2021
viewState: ModelEditorViewState;
2122
hideModeledMethods: boolean;
2223
revealedMethodSignature: string | null;
2324
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
25+
onMethodClick: (methodSignature: string) => void;
2426
};
2527

2628
export const ModeledMethodDataGrid = ({
2729
methods,
2830
modeledMethodsMap,
2931
modifiedSignatures,
32+
selectedSignatures,
3033
inProgressMethods,
3134
viewState,
3235
hideModeledMethods,
3336
revealedMethodSignature,
3437
onChange,
38+
onMethodClick,
3539
}: ModeledMethodDataGridProps) => {
3640
const [methodsWithModelability, numHiddenMethods]: [
3741
Array<{ method: Method; methodCanBeModeled: boolean }>,
@@ -80,10 +84,12 @@ export const ModeledMethodDataGrid = ({
8084
methodCanBeModeled={methodCanBeModeled}
8185
modeledMethods={modeledMethods}
8286
methodIsUnsaved={modifiedSignatures.has(method.signature)}
87+
methodIsSelected={selectedSignatures.has(method.signature)}
8388
modelingInProgress={inProgressMethods.has(method.signature)}
8489
viewState={viewState}
8590
revealedMethodSignature={revealedMethodSignature}
8691
onChange={onChange}
92+
onMethodClick={onMethodClick}
8793
/>
8894
);
8995
})}

extensions/ql-vscode/src/view/model-editor/ModeledMethodsList.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ export type ModeledMethodsListProps = {
1313
methods: Method[];
1414
modeledMethodsMap: Record<string, ModeledMethod[]>;
1515
modifiedSignatures: Set<string>;
16+
selectedSignatures: Set<string>;
1617
inProgressMethods: Set<string>;
1718
revealedMethodSignature: string | null;
1819
viewState: ModelEditorViewState;
1920
hideModeledMethods: boolean;
2021
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
22+
onMethodClick: (methodSignature: string) => void;
2123
onSaveModelClick: (methodSignatures: string[]) => void;
2224
onGenerateFromLlmClick: (
2325
packageName: string,
@@ -36,11 +38,13 @@ export const ModeledMethodsList = ({
3638
methods,
3739
modeledMethodsMap,
3840
modifiedSignatures,
41+
selectedSignatures,
3942
inProgressMethods,
4043
viewState,
4144
hideModeledMethods,
4245
revealedMethodSignature,
4346
onChange,
47+
onMethodClick,
4448
onSaveModelClick,
4549
onGenerateFromLlmClick,
4650
onStopGenerateFromLlmClick,
@@ -82,11 +86,13 @@ export const ModeledMethodsList = ({
8286
methods={grouped[libraryName]}
8387
modeledMethodsMap={modeledMethodsMap}
8488
modifiedSignatures={modifiedSignatures}
89+
selectedSignatures={selectedSignatures}
8590
inProgressMethods={inProgressMethods}
8691
viewState={viewState}
8792
hideModeledMethods={hideModeledMethods}
8893
revealedMethodSignature={revealedMethodSignature}
8994
onChange={onChange}
95+
onMethodClick={onMethodClick}
9096
onSaveModelClick={onSaveModelClick}
9197
onGenerateFromLlmClick={onGenerateFromLlmClick}
9298
onStopGenerateFromLlmClick={onStopGenerateFromLlmClick}

0 commit comments

Comments
 (0)