Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/ql-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [UNRELEASED]

- 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)
- 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)
- 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.
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
Expand Down
29 changes: 24 additions & 5 deletions extensions/ql-vscode/src/view/common/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ export function DataGrid({ gridTemplateColumns, children }: DataGridProps) {
);
}

const StyledDataGridRow = styled.div<{ $focused?: boolean }>`
const StyledDataGridRow = styled.div<{
$focused?: boolean;
$selected?: boolean;
}>`
display: contents;

&:hover > * {
Expand All @@ -48,14 +51,18 @@ const StyledDataGridRow = styled.div<{ $focused?: boolean }>`
// Use !important to override the background color set by the hover state
background-color: ${(props) =>
props.$focused
? "var(--vscode-editor-selectionBackground) !important"
: "inherit"};
? "var(--vscode-editor-findMatchHighlightBackground) !important"
: props.$selected
? "var(--vscode-editor-selectionBackground) !important"
: "inherit"};
}
`;

interface DataGridRowProps {
focused?: boolean;
selected?: boolean;
children: ReactNode;
onClick?: () => void;
"data-testid"?: string;
}

Expand All @@ -69,10 +76,22 @@ interface DataGridRowProps {
*/
export const DataGridRow = forwardRef(
(
{ focused, children, "data-testid": testId }: DataGridRowProps,
{
focused,
selected,
children,
"data-testid": testId,
onClick,
}: DataGridRowProps,
ref?: React.Ref<HTMLElement | undefined>,
) => (
<StyledDataGridRow $focused={focused} ref={ref} data-testid={testId}>
<StyledDataGridRow
$focused={focused}
$selected={selected}
ref={ref}
data-testid={testId}
onClick={onClick}
>
{children}
</StyledDataGridRow>
),
Expand Down
4 changes: 4 additions & 0 deletions extensions/ql-vscode/src/view/common/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ type Props = {
"aria-label"?: string;
};

const stopClickPropagation = (e: React.MouseEvent) => {
e.stopPropagation();
};
/**
* A dropdown implementation styled to look like `VSCodeDropdown`.
*
Expand All @@ -50,6 +53,7 @@ export function Dropdown({
value={disabled ? disabledValue : value}
disabled={disabled}
onChange={onChange}
onClick={stopClickPropagation}
className={className}
{...props}
>
Expand Down
8 changes: 7 additions & 1 deletion extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@ export type LibraryRowProps = {
methods: Method[];
modeledMethodsMap: Record<string, ModeledMethod[]>;
modifiedSignatures: Set<string>;
selectedSignatures: Set<string>;
inProgressMethods: Set<string>;
viewState: ModelEditorViewState;
hideModeledMethods: boolean;
revealedMethodSignature: string | null;
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
onMethodClick: (methodSignature: string) => void;
onSaveModelClick: (methodSignatures: string[]) => void;
onGenerateFromLlmClick: (
dependencyName: string,
Expand All @@ -92,11 +94,13 @@ export const LibraryRow = ({
methods,
modeledMethodsMap,
modifiedSignatures,
selectedSignatures,
inProgressMethods,
viewState,
hideModeledMethods,
revealedMethodSignature,
onChange,
onMethodClick,
onSaveModelClick,
onGenerateFromLlmClick,
onStopGenerateFromLlmClick,
Expand Down Expand Up @@ -228,16 +232,18 @@ export const LibraryRow = ({
methods={methods}
modeledMethodsMap={modeledMethodsMap}
modifiedSignatures={modifiedSignatures}
selectedSignatures={selectedSignatures}
inProgressMethods={inProgressMethods}
viewState={viewState}
hideModeledMethods={hideModeledMethods}
revealedMethodSignature={revealedMethodSignature}
onChange={onChange}
onMethodClick={onMethodClick}
/>
<SectionDivider />
<ButtonsContainer>
<VSCodeButton onClick={handleSave} disabled={!hasUnsavedChanges}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spotted one odd piece of UI behaviour. If you have unsaved changes then the button will be enabled, but it could be that you haven't selected methods with unsaved changes, so pressing the button does nothing. It could be better if the button only became enabled when there were unsaved changes in the selected methods.

However I suggest we can leave this as a followup, since I believe we're expecting to do a design pass on this at some point anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! That's a good point 🦅 👀

But I also agree that it can be a follow-up change instead, along with any other design/user feedback!

Save
{selectedSignatures.size === 0 ? "Save" : "Save selected"}
</VSCodeButton>
</ButtonsContainer>
</>
Expand Down
34 changes: 30 additions & 4 deletions extensions/ql-vscode/src/view/model-editor/MethodRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ export type MethodRowProps = {
methodCanBeModeled: boolean;
modeledMethods: ModeledMethod[];
methodIsUnsaved: boolean;
methodIsSelected: boolean;
modelingInProgress: boolean;
viewState: ModelEditorViewState;
revealedMethodSignature: string | null;
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
onMethodClick: (methodSignature: string) => void;
};

export const MethodRow = (props: MethodRowProps) => {
Expand Down Expand Up @@ -103,9 +105,11 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
method,
modeledMethods: modeledMethodsProp,
methodIsUnsaved,
methodIsSelected,
viewState,
revealedMethodSignature,
onChange,
onMethodClick,
} = props;

const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
Expand Down Expand Up @@ -186,6 +190,10 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
<DataGridRow
data-testid="modelable-method-row"
focused={revealedMethodSignature === method.signature}
selected={methodIsSelected}
onClick={() => {
onMethodClick(method.signature);
}}
>
<DataGridCell
gridRow={`span ${modeledMethods.length + validationErrors.length}`}
Expand All @@ -196,11 +204,23 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
<MethodClassifications method={method} />
<MethodName {...props.method} />
{viewState.mode === Mode.Application && (
<UsagesButton onClick={jumpToMethod}>
<UsagesButton
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
jumpToMethod();
}}
>
{method.usages.length}
</UsagesButton>
)}
<ViewLink onClick={jumpToMethod}>View</ViewLink>
<ViewLink
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
jumpToMethod();
}}
>
View
</ViewLink>
{props.modelingInProgress && <ProgressRing />}
</ApiOrMethodRow>
</DataGridCell>
Expand Down Expand Up @@ -269,7 +289,10 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
<CodiconRow
appearance="icon"
aria-label="Add new model"
onClick={handleAddModelClick}
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
handleAddModelClick();
}}
disabled={addModelButtonDisabled}
>
<Codicon name="add" />
Expand All @@ -278,7 +301,10 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
<CodiconRow
appearance="icon"
aria-label="Remove model"
onClick={removeModelClickedHandlers[index]}
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
removeModelClickedHandlers[index]();
}}
>
<Codicon name="trash" />
</CodiconRow>
Expand Down
56 changes: 49 additions & 7 deletions extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ export function ModelEditor({
new Set(),
);

const [selectedSignatures, setSelectedSignatures] = useState<Set<string>>(
new Set(),
);

const [inProgressMethods, setInProgressMethods] = useState<Set<string>>(
new Set(),
);
Expand Down Expand Up @@ -189,6 +193,19 @@ export function ModelEditor({
[],
);

const onMethodClick = useCallback(
(methodSignature: string) => {
const newSelectedSignatures = new Set(selectedSignatures);
if (selectedSignatures.has(methodSignature)) {
newSelectedSignatures.delete(methodSignature);
} else {
newSelectedSignatures.add(methodSignature);
}
setSelectedSignatures(newSelectedSignatures);
},
[selectedSignatures],
);

const onRefreshClick = useCallback(() => {
vscode.postMessage({
t: "refreshMethods",
Expand All @@ -198,16 +215,32 @@ export function ModelEditor({
const onSaveAllClick = useCallback(() => {
vscode.postMessage({
t: "saveModeledMethods",
methodSignatures:
selectedSignatures.size === 0
? undefined
: Array.from(selectedSignatures),
});
}, []);
}, [selectedSignatures]);

const onSaveModelClick = useCallback((methodSignatures: string[]) => {
vscode.postMessage({
t: "saveModeledMethods",
methodSignatures,
});
const onDeselectAllClick = useCallback(() => {
setSelectedSignatures(new Set());
}, []);

const onSaveModelClick = useCallback(
(methodSignatures: string[]) => {
vscode.postMessage({
t: "saveModeledMethods",
methodSignatures:
selectedSignatures.size === 0
? methodSignatures
: methodSignatures.filter((signature) =>
selectedSignatures.has(signature),
),
});
},
[selectedSignatures],
);

const onGenerateFromSourceClick = useCallback(() => {
vscode.postMessage({
t: "generateMethod",
Expand Down Expand Up @@ -309,7 +342,14 @@ export function ModelEditor({
onClick={onSaveAllClick}
disabled={modifiedSignatures.size === 0}
>
Save all
{selectedSignatures.size === 0 ? "Save all" : "Save selected"}
</VSCodeButton>
<VSCodeButton
appearance="secondary"
onClick={onDeselectAllClick}
disabled={selectedSignatures.size === 0}
>
Deselect all
</VSCodeButton>
<VSCodeButton appearance="secondary" onClick={onRefreshClick}>
Refresh
Expand Down Expand Up @@ -339,11 +379,13 @@ export function ModelEditor({
methods={methods}
modeledMethodsMap={modeledMethods}
modifiedSignatures={modifiedSignatures}
selectedSignatures={selectedSignatures}
inProgressMethods={inProgressMethods}
viewState={viewState}
hideModeledMethods={hideModeledMethods}
revealedMethodSignature={revealedMethodSignature}
onChange={onChange}
onMethodClick={onMethodClick}
onSaveModelClick={onSaveModelClick}
onGenerateFromLlmClick={onGenerateFromLlmClick}
onStopGenerateFromLlmClick={onStopGenerateFromLlmClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,26 @@ export type ModeledMethodDataGridProps = {
methods: Method[];
modeledMethodsMap: Record<string, ModeledMethod[]>;
modifiedSignatures: Set<string>;
selectedSignatures: Set<string>;
inProgressMethods: Set<string>;
viewState: ModelEditorViewState;
hideModeledMethods: boolean;
revealedMethodSignature: string | null;
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
onMethodClick: (methodSignature: string) => void;
};

export const ModeledMethodDataGrid = ({
methods,
modeledMethodsMap,
modifiedSignatures,
selectedSignatures,
inProgressMethods,
viewState,
hideModeledMethods,
revealedMethodSignature,
onChange,
onMethodClick,
}: ModeledMethodDataGridProps) => {
const [methodsWithModelability, numHiddenMethods]: [
Array<{ method: Method; methodCanBeModeled: boolean }>,
Expand Down Expand Up @@ -80,10 +84,12 @@ export const ModeledMethodDataGrid = ({
methodCanBeModeled={methodCanBeModeled}
modeledMethods={modeledMethods}
methodIsUnsaved={modifiedSignatures.has(method.signature)}
methodIsSelected={selectedSignatures.has(method.signature)}
modelingInProgress={inProgressMethods.has(method.signature)}
viewState={viewState}
revealedMethodSignature={revealedMethodSignature}
onChange={onChange}
onMethodClick={onMethodClick}
/>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ export type ModeledMethodsListProps = {
methods: Method[];
modeledMethodsMap: Record<string, ModeledMethod[]>;
modifiedSignatures: Set<string>;
selectedSignatures: Set<string>;
inProgressMethods: Set<string>;
revealedMethodSignature: string | null;
viewState: ModelEditorViewState;
hideModeledMethods: boolean;
onChange: (methodSignature: string, modeledMethods: ModeledMethod[]) => void;
onMethodClick: (methodSignature: string) => void;
onSaveModelClick: (methodSignatures: string[]) => void;
onGenerateFromLlmClick: (
packageName: string,
Expand All @@ -36,11 +38,13 @@ export const ModeledMethodsList = ({
methods,
modeledMethodsMap,
modifiedSignatures,
selectedSignatures,
inProgressMethods,
viewState,
hideModeledMethods,
revealedMethodSignature,
onChange,
onMethodClick,
onSaveModelClick,
onGenerateFromLlmClick,
onStopGenerateFromLlmClick,
Expand Down Expand Up @@ -82,11 +86,13 @@ export const ModeledMethodsList = ({
methods={grouped[libraryName]}
modeledMethodsMap={modeledMethodsMap}
modifiedSignatures={modifiedSignatures}
selectedSignatures={selectedSignatures}
inProgressMethods={inProgressMethods}
viewState={viewState}
hideModeledMethods={hideModeledMethods}
revealedMethodSignature={revealedMethodSignature}
onChange={onChange}
onMethodClick={onMethodClick}
onSaveModelClick={onSaveModelClick}
onGenerateFromLlmClick={onGenerateFromLlmClick}
onStopGenerateFromLlmClick={onStopGenerateFromLlmClick}
Expand Down
Loading