Merge remote-tracking branch 'origin/main' into koesie10/add-database-source
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
- When adding a CodeQL database, we no longer add the database source folder to the workspace by default (since this caused bugs in single-folder workspaces). [#3047](https://github.com/github/vscode-codeql/pull/3047)
|
||||
- You can manually add individual database source folders to the workspace with the "Add Database Source to Workspace" right-click command in the databases view.
|
||||
- To restore the old behavior of adding all database source folders by default, set the `codeQL.addingDatabases.addDatabaseSourceToWorkspace` setting to `true`.
|
||||
- Rename the `codeQL.databaseDownload.allowHttp` setting to `codeQL.addingDatabases.allowHttp`, so that database-related settings are grouped together in the Settings UI. [#3047](https://github.com/github/vscode-codeql/pull/3047)
|
||||
- The "Sort by Language" action in the databases view now sorts by name within each language. [#3055](https://github.com/github/vscode-codeql/pull/3055)
|
||||
|
||||
## 1.9.4 - 6 November 2023
|
||||
|
||||
@@ -372,13 +372,23 @@
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "Downloading databases",
|
||||
"title": "Adding databases",
|
||||
"order": 6,
|
||||
"properties": {
|
||||
"codeQL.databaseDownload.allowHttp": {
|
||||
"codeQL.addingDatabases.allowHttp": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Allow database to be downloaded via HTTP. Warning: enabling this option will allow downloading from insecure servers."
|
||||
"description": "Allow databases to be downloaded via HTTP. Warning: enabling this option will allow downloading from insecure servers."
|
||||
},
|
||||
"codeQL.databaseDownload.allowHttp": {
|
||||
"type": "boolean",
|
||||
"markdownDeprecationMessage": "**Deprecated**: Please use `#codeQL.addingDatabases.allowHttp#` instead.",
|
||||
"deprecationMessage": "Deprecated: Please use codeQL.addingDatabases.allowHttp instead."
|
||||
},
|
||||
"codeQL.addingDatabases.addDatabaseSourceToWorkspace": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"markdownDescription": "When adding a CodeQL database, automatically add the database's source folder as a workspace folder. Warning: enabling this option in a single-folder workspace will cause the workspace to reload as a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces). This may cause query history and database lists to be reset."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -43,7 +43,9 @@ export function getFirstWorkspaceFolder() {
|
||||
const workspaceFolders = getOnDiskWorkspaceFolders();
|
||||
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||
throw new Error("No workspace folders found");
|
||||
throw new Error(
|
||||
"No workspace folders found. Please open a folder or workspace in VS Code.",
|
||||
);
|
||||
}
|
||||
|
||||
const firstFolderFsPath = workspaceFolders[0];
|
||||
|
||||
@@ -641,14 +641,23 @@ export function isCodespacesTemplate() {
|
||||
return !!CODESPACES_TEMPLATE.getValue<boolean>();
|
||||
}
|
||||
|
||||
const DATABASE_DOWNLOAD_SETTING = new Setting("databaseDownload", ROOT_SETTING);
|
||||
const ADDING_DATABASES_SETTING = new Setting("addingDatabases", ROOT_SETTING);
|
||||
|
||||
const ALLOW_HTTP_SETTING = new Setting("allowHttp", DATABASE_DOWNLOAD_SETTING);
|
||||
const ALLOW_HTTP_SETTING = new Setting("allowHttp", ADDING_DATABASES_SETTING);
|
||||
|
||||
export function allowHttp(): boolean {
|
||||
return ALLOW_HTTP_SETTING.getValue<boolean>() || false;
|
||||
}
|
||||
|
||||
const ADD_DATABASE_SOURCE_TO_WORKSPACE_SETTING = new Setting(
|
||||
"addDatabaseSourceToWorkspace",
|
||||
ADDING_DATABASES_SETTING,
|
||||
);
|
||||
|
||||
export function addDatabaseSourceToWorkspace(): boolean {
|
||||
return ADD_DATABASE_SOURCE_TO_WORKSPACE_SETTING.getValue<boolean>() || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent setting for all settings related to the "Create Query" command.
|
||||
*/
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
} from "../common/github-url-identifier-helper";
|
||||
import { Credentials } from "../common/authentication";
|
||||
import { AppCommandManager } from "../common/commands";
|
||||
import { allowHttp } from "../config";
|
||||
import { addDatabaseSourceToWorkspace, allowHttp } from "../config";
|
||||
import { showAndLogInformationMessage } from "../common/logging";
|
||||
import { AppOctokit } from "../common/octokit";
|
||||
import { getLanguageDisplayName } from "../common/query-language";
|
||||
@@ -104,7 +104,7 @@ export async function promptImportGithubDatabase(
|
||||
cli?: CodeQLCliServer,
|
||||
language?: string,
|
||||
makeSelected = true,
|
||||
addSourceArchiveFolder = true,
|
||||
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const githubRepo = await askForGitHubRepo(progress);
|
||||
if (!githubRepo) {
|
||||
@@ -183,7 +183,7 @@ export async function downloadGitHubDatabase(
|
||||
cli?: CodeQLCliServer,
|
||||
language?: string,
|
||||
makeSelected = true,
|
||||
addSourceArchiveFolder = true,
|
||||
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const nwo = getNwoFromGitHubUrl(githubRepo) || githubRepo;
|
||||
if (!isValidGitHubNwo(nwo)) {
|
||||
@@ -311,7 +311,7 @@ async function databaseArchiveFetcher(
|
||||
progress: ProgressCallback,
|
||||
cli?: CodeQLCliServer,
|
||||
makeSelected = true,
|
||||
addSourceArchiveFolder = true,
|
||||
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
|
||||
): Promise<DatabaseItem> {
|
||||
progress({
|
||||
message: "Getting database",
|
||||
@@ -493,7 +493,7 @@ async function checkForFailingResponse(
|
||||
return response;
|
||||
}
|
||||
|
||||
// An error downloading the database. Attempt to extract the resaon behind it.
|
||||
// An error downloading the database. Attempt to extract the reason behind it.
|
||||
const text = await response.text();
|
||||
let msg: string;
|
||||
try {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { QueryRunner } from "../../query-server";
|
||||
import * as cli from "../../codeql-cli/cli";
|
||||
import { ProgressCallback, withProgress } from "../../common/vscode/progress";
|
||||
import {
|
||||
addDatabaseSourceToWorkspace,
|
||||
getAutogenerateQlPacks,
|
||||
isCodespacesTemplate,
|
||||
setAutogenerateQlPacks,
|
||||
@@ -137,7 +138,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
displayName?: string,
|
||||
{
|
||||
isTutorialDatabase = false,
|
||||
addSourceArchiveFolder = true,
|
||||
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
|
||||
}: OpenDatabaseOptions = {},
|
||||
): Promise<DatabaseItem> {
|
||||
const databaseItem = await this.createDatabaseItem(
|
||||
@@ -164,7 +165,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
databaseItem: DatabaseItemImpl,
|
||||
makeSelected: boolean,
|
||||
isTutorialDatabase?: boolean,
|
||||
addSourceArchiveFolder = true,
|
||||
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
|
||||
): Promise<DatabaseItem> {
|
||||
const existingItem = this.findDatabaseItem(databaseItem.databaseUri);
|
||||
if (existingItem !== undefined) {
|
||||
@@ -274,7 +275,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
`We've noticed you don't have a CodeQL pack available to analyze this database. Can we set up a query pack for you?`,
|
||||
);
|
||||
|
||||
if (answer === "No") {
|
||||
if (answer === "No" || answer === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -126,3 +126,38 @@ export function isModelAccepted(
|
||||
modeledMethod.provenance !== "ai-generated"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the new provenance for a modeled method based on the current provenance.
|
||||
* @param modeledMethod The modeled method if there is one.
|
||||
* @returns The new provenance.
|
||||
*/
|
||||
export function calculateNewProvenance(
|
||||
modeledMethod: ModeledMethod | undefined,
|
||||
) {
|
||||
if (!modeledMethod || !modeledMethodSupportsProvenance(modeledMethod)) {
|
||||
// If nothing has been modeled or the modeled method does not support
|
||||
// provenance, we assume that the user has entered it manually.
|
||||
return "manual";
|
||||
}
|
||||
|
||||
switch (modeledMethod.provenance) {
|
||||
case "df-generated":
|
||||
// If the method has been generated and there has been a change, we assume
|
||||
// that the user has manually edited it.
|
||||
return "df-manual";
|
||||
case "df-manual":
|
||||
// If the method has had manual edits, we want the provenance to stay the same.
|
||||
return "df-manual";
|
||||
case "ai-generated":
|
||||
// If the method has been generated and there has been a change, we assume
|
||||
// that the user has manually edited it.
|
||||
return "ai-manual";
|
||||
case "ai-manual":
|
||||
// If the method has had manual edits, we want the provenance to stay the same.
|
||||
return "ai-manual";
|
||||
default:
|
||||
// The method has been modeled manually.
|
||||
return "manual";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { styled } from "styled-components";
|
||||
|
||||
type Props = {
|
||||
percent: number;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
const Circle = styled.div`
|
||||
@@ -33,10 +32,7 @@ const progressSegments = 44;
|
||||
// See https://github.com/microsoft/fast/blob/21c210f2164c5cf285cade1a328460c67e4b97e6/packages/web-components/fast-foundation/src/progress-ring/progress-ring.template.ts
|
||||
// Once the determinate progress ring is available in the VSCode webview UI toolkit, we should use that instead
|
||||
|
||||
export const DeterminateProgressRing = ({
|
||||
percent,
|
||||
label = "Loading...",
|
||||
}: Props) => (
|
||||
export const DeterminateProgressRing = ({ percent }: Props) => (
|
||||
<Circle
|
||||
role="progressbar"
|
||||
aria-valuemin={0}
|
||||
|
||||
@@ -226,7 +226,6 @@ export const LibraryRow = ({
|
||||
<>
|
||||
<SectionDivider />
|
||||
<ModeledMethodDataGrid
|
||||
packageName={title}
|
||||
methods={methods}
|
||||
modeledMethodsMap={modeledMethodsMap}
|
||||
modifiedSignatures={modifiedSignatures}
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from "react";
|
||||
import { ChangeEvent, useCallback, useMemo } from "react";
|
||||
import {
|
||||
ModeledMethod,
|
||||
calculateNewProvenance,
|
||||
isModelAccepted,
|
||||
modeledMethodSupportsInput,
|
||||
} from "../../model-editor/modeled-method";
|
||||
@@ -53,6 +54,7 @@ export const ModelInputDropdown = ({
|
||||
|
||||
onChange({
|
||||
...modeledMethod,
|
||||
provenance: calculateNewProvenance(modeledMethod),
|
||||
input: target.value,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ModeledMethodKind,
|
||||
modeledMethodSupportsKind,
|
||||
isModelAccepted,
|
||||
calculateNewProvenance,
|
||||
} from "../../model-editor/modeled-method";
|
||||
import { getModelsAsDataLanguage } from "../../model-editor/languages";
|
||||
import { QueryLanguage } from "../../common/query-language";
|
||||
@@ -52,6 +53,7 @@ export const ModelKindDropdown = ({
|
||||
|
||||
onChange({
|
||||
...modeledMethod,
|
||||
provenance: calculateNewProvenance(modeledMethod),
|
||||
kind,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from "react";
|
||||
import { ChangeEvent, useCallback, useMemo } from "react";
|
||||
import {
|
||||
ModeledMethod,
|
||||
calculateNewProvenance,
|
||||
isModelAccepted,
|
||||
modeledMethodSupportsOutput,
|
||||
} from "../../model-editor/modeled-method";
|
||||
@@ -54,6 +55,7 @@ export const ModelOutputDropdown = ({
|
||||
|
||||
onChange({
|
||||
...modeledMethod,
|
||||
provenance: calculateNewProvenance(modeledMethod),
|
||||
output: target.value,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { ChangeEvent, useCallback } from "react";
|
||||
import {
|
||||
calculateNewProvenance,
|
||||
isModelAccepted,
|
||||
ModeledMethod,
|
||||
modeledMethodSupportsProvenance,
|
||||
ModeledMethodType,
|
||||
Provenance,
|
||||
} from "../../model-editor/modeled-method";
|
||||
import { Method } from "../../model-editor/method";
|
||||
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
|
||||
@@ -43,15 +42,6 @@ export const ModelTypeDropdown = ({
|
||||
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const modelsAsDataLanguage = getModelsAsDataLanguage(language);
|
||||
|
||||
let newProvenance: Provenance = "manual";
|
||||
if (modeledMethod && modeledMethodSupportsProvenance(modeledMethod)) {
|
||||
if (modeledMethod.provenance === "df-generated") {
|
||||
newProvenance = "df-manual";
|
||||
} else if (modeledMethod.provenance === "ai-generated") {
|
||||
newProvenance = "ai-manual";
|
||||
}
|
||||
}
|
||||
|
||||
const emptyModeledMethod = createEmptyModeledMethod(
|
||||
e.target.value as ModeledMethodType,
|
||||
method,
|
||||
@@ -67,7 +57,7 @@ export const ModelTypeDropdown = ({
|
||||
updatedModeledMethod.output = "ReturnValue";
|
||||
}
|
||||
if ("provenance" in updatedModeledMethod) {
|
||||
updatedModeledMethod.provenance = newProvenance;
|
||||
updatedModeledMethod.provenance = calculateNewProvenance(modeledMethod);
|
||||
}
|
||||
if ("kind" in updatedModeledMethod) {
|
||||
updatedModeledMethod.kind = "value";
|
||||
|
||||
@@ -15,7 +15,6 @@ export const MULTIPLE_MODELS_GRID_TEMPLATE_COLUMNS =
|
||||
"0.5fr 0.125fr 0.125fr 0.125fr 0.125fr max-content";
|
||||
|
||||
export type ModeledMethodDataGridProps = {
|
||||
packageName: string;
|
||||
methods: Method[];
|
||||
modeledMethodsMap: Record<string, ModeledMethod[]>;
|
||||
modifiedSignatures: Set<string>;
|
||||
@@ -27,7 +26,6 @@ export type ModeledMethodDataGridProps = {
|
||||
};
|
||||
|
||||
export const ModeledMethodDataGrid = ({
|
||||
packageName,
|
||||
methods,
|
||||
modeledMethodsMap,
|
||||
modifiedSignatures,
|
||||
@@ -81,24 +79,22 @@ export const ModeledMethodDataGrid = ({
|
||||
<ScreenReaderOnly>Add or remove models</ScreenReaderOnly>
|
||||
</DataGridCell>
|
||||
)}
|
||||
{methodsWithModelability.map(
|
||||
({ method, methodCanBeModeled }, index) => {
|
||||
const modeledMethods = modeledMethodsMap[method.signature] ?? [];
|
||||
return (
|
||||
<MethodRow
|
||||
key={method.signature}
|
||||
method={method}
|
||||
methodCanBeModeled={methodCanBeModeled}
|
||||
modeledMethods={modeledMethods}
|
||||
methodIsUnsaved={modifiedSignatures.has(method.signature)}
|
||||
modelingInProgress={inProgressMethods.has(method.signature)}
|
||||
viewState={viewState}
|
||||
revealedMethodSignature={revealedMethodSignature}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
},
|
||||
)}
|
||||
{methodsWithModelability.map(({ method, methodCanBeModeled }) => {
|
||||
const modeledMethods = modeledMethodsMap[method.signature] ?? [];
|
||||
return (
|
||||
<MethodRow
|
||||
key={method.signature}
|
||||
method={method}
|
||||
methodCanBeModeled={methodCanBeModeled}
|
||||
modeledMethods={modeledMethods}
|
||||
methodIsUnsaved={modifiedSignatures.has(method.signature)}
|
||||
modelingInProgress={inProgressMethods.has(method.signature)}
|
||||
viewState={viewState}
|
||||
revealedMethodSignature={revealedMethodSignature}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
<HiddenMethodsRow
|
||||
|
||||
@@ -27,7 +27,7 @@ describe(MethodRow.name, () => {
|
||||
input: "Argument[0]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "df-generated",
|
||||
provenance: "manual",
|
||||
};
|
||||
const onChange = jest.fn();
|
||||
|
||||
@@ -111,6 +111,32 @@ describe(MethodRow.name, () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("changes the provenance when the kind is changed", async () => {
|
||||
const modeledMethodWithGeneratedProvenance: ModeledMethod = {
|
||||
...modeledMethod,
|
||||
provenance: "df-generated",
|
||||
};
|
||||
render({ modeledMethods: [modeledMethodWithGeneratedProvenance] });
|
||||
|
||||
onChange.mockReset();
|
||||
|
||||
expect(screen.getByRole("combobox", { name: "Kind" })).toHaveValue("taint");
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole("combobox", { name: "Kind" }),
|
||||
"value",
|
||||
);
|
||||
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith(method.signature, [
|
||||
{
|
||||
...modeledMethod,
|
||||
kind: "value",
|
||||
provenance: "df-manual",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("has the correct input options", () => {
|
||||
render();
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ describe(ModeledMethodDataGrid.name, () => {
|
||||
const render = (props: Partial<ModeledMethodDataGridProps> = {}) =>
|
||||
reactRender(
|
||||
<ModeledMethodDataGrid
|
||||
packageName="sql2o"
|
||||
methods={[method1, method2, method3]}
|
||||
modeledMethodsMap={{
|
||||
[method1.signature]: [
|
||||
|
||||
@@ -47,9 +47,7 @@ export function Graph({ graphData, databaseUri }: GraphProps) {
|
||||
d.attributes["xlink:href"] = "#";
|
||||
d.attributes["href"] = "#";
|
||||
loc.uri = `file://${loc.uri}`;
|
||||
select(this).on("click", function (e) {
|
||||
jumpToLocation(loc, databaseUri);
|
||||
});
|
||||
select(this).on("click", () => jumpToLocation(loc, databaseUri));
|
||||
}
|
||||
}
|
||||
if ("fill" in d.attributes) {
|
||||
|
||||
@@ -9,7 +9,6 @@ interface Props {
|
||||
loc: ResolvableLocationValue;
|
||||
label: string;
|
||||
databaseUri: string;
|
||||
title?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
@@ -24,7 +23,6 @@ export function ClickableLocation({
|
||||
loc,
|
||||
label,
|
||||
databaseUri,
|
||||
title,
|
||||
onClick: onClick,
|
||||
}: Props): JSX.Element {
|
||||
const handleClick = useCallback(
|
||||
|
||||
@@ -48,7 +48,6 @@ export function Location({
|
||||
loc={resolvableLoc}
|
||||
label={displayLabel}
|
||||
databaseUri={databaseUri}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"rootDir": "../..",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"experimentalDecorators": true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[
|
||||
"v2.15.1",
|
||||
"v2.15.2",
|
||||
"v2.14.6",
|
||||
"v2.13.5",
|
||||
"v2.12.7",
|
||||
|
||||
@@ -625,6 +625,16 @@ describe("local databases", () => {
|
||||
expect(generateSpy).not.toBeCalled();
|
||||
});
|
||||
|
||||
it("should return early if the user escapes out of the dialog", async () => {
|
||||
showNeverAskAgainDialogSpy = jest
|
||||
.spyOn(dialog, "showNeverAskAgainDialog")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
await (databaseManager as any).createSkeletonPacks(mockDbItem);
|
||||
|
||||
expect(generateSpy).not.toBeCalled();
|
||||
});
|
||||
|
||||
it("should return early and write choice to settings if user wants to never be asked again", async () => {
|
||||
showNeverAskAgainDialogSpy = jest
|
||||
.spyOn(dialog, "showNeverAskAgainDialog")
|
||||
@@ -738,7 +748,20 @@ describe("local databases", () => {
|
||||
expect(setCurrentDatabaseItemSpy).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should add database source archive folder", async () => {
|
||||
it("should not add database source archive folder when `codeQL.addingDatabases.addDatabaseSourceToWorkspace` is `false`", async () => {
|
||||
jest.spyOn(config, "addDatabaseSourceToWorkspace").mockReturnValue(false);
|
||||
|
||||
await databaseManager.openDatabase(
|
||||
mockDbItem.databaseUri,
|
||||
mockDbItem.origin,
|
||||
);
|
||||
|
||||
expect(addDatabaseSourceArchiveFolderSpy).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it("should add database source archive folder when `codeQL.addingDatabases.addDatabaseSourceToWorkspace` is `true`", async () => {
|
||||
jest.spyOn(config, "addDatabaseSourceToWorkspace").mockReturnValue(true);
|
||||
|
||||
await databaseManager.openDatabase(
|
||||
mockDbItem.databaseUri,
|
||||
mockDbItem.origin,
|
||||
|
||||
Reference in New Issue
Block a user