Merge branch 'main' into robertbrignull/logged_error_telemetry
This commit is contained in:
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -122,16 +122,15 @@ jobs:
|
||||
perl -i -pe 's/^/## \[UNRELEASED\]\n\n/ if($.==3)' CHANGELOG.md
|
||||
|
||||
- name: Create version bump PR
|
||||
uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 # v4.2.3
|
||||
uses: ./.github/actions/create-pr
|
||||
if: success()
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Bump version to ${{ steps.bump-patch-version.outputs.next_version }}
|
||||
title: Bump version to ${{ steps.bump-patch-version.outputs.next_version }}
|
||||
body: This PR was automatically generated by the GitHub Actions release workflow in this repository.
|
||||
branch: ${{ format('version/bump-to-{0}', steps.bump-patch-version.outputs.next_version) }}
|
||||
base: main
|
||||
draft: true
|
||||
head-branch: ${{ format('version/bump-to-{0}', steps.bump-patch-version.outputs.next_version) }}
|
||||
base-branch: main
|
||||
|
||||
vscode-publish:
|
||||
name: Publish to VS Code Marketplace
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.vscode-test/
|
||||
node_modules/
|
||||
out/
|
||||
build/
|
||||
|
||||
# Include the Storybook config
|
||||
!.storybook
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
module.exports = {
|
||||
const { resolve } = require("path");
|
||||
|
||||
const baseConfig = {
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: "module",
|
||||
project: ["tsconfig.json", "./src/**/tsconfig.json", "./test/**/tsconfig.json", "./gulpfile.ts/tsconfig.json", "./scripts/tsconfig.json", "./.storybook/tsconfig.json"],
|
||||
project: [
|
||||
resolve(__dirname, "tsconfig.lint.json"),
|
||||
resolve(__dirname, "src/**/tsconfig.json"),
|
||||
resolve(__dirname, "test/**/tsconfig.json"),
|
||||
resolve(__dirname, "gulpfile.ts/tsconfig.json"),
|
||||
resolve(__dirname, "scripts/tsconfig.json"),
|
||||
resolve(__dirname, ".storybook/tsconfig.json"),
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
"github",
|
||||
"@typescript-eslint",
|
||||
"etc"
|
||||
],
|
||||
plugins: ["github", "@typescript-eslint", "etc"],
|
||||
env: {
|
||||
node: true,
|
||||
es6: true,
|
||||
@@ -21,7 +26,7 @@ module.exports = {
|
||||
"plugin:github/typescript",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/no-use-before-define": 0,
|
||||
@@ -37,14 +42,14 @@ module.exports = {
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-floating-promises": [ "error", { ignoreVoid: true } ],
|
||||
"@typescript-eslint/no-floating-promises": ["error", { ignoreVoid: true }],
|
||||
"@typescript-eslint/no-invalid-this": "off",
|
||||
"@typescript-eslint/no-shadow": "off",
|
||||
"prefer-const": ["warn", { destructuring: "all" }],
|
||||
"@typescript-eslint/no-throw-literal": "error",
|
||||
"no-useless-escape": 0,
|
||||
"camelcase": "off",
|
||||
"eqeqeq": "off",
|
||||
camelcase: "off",
|
||||
eqeqeq: "off",
|
||||
"escompat/no-regexp-lookbehind": "off",
|
||||
"etc/no-implicit-any-catch": "error",
|
||||
"filenames/match-regex": "off",
|
||||
@@ -72,3 +77,102 @@ module.exports = {
|
||||
"github/no-then": "off",
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
...baseConfig,
|
||||
overrides: [
|
||||
{
|
||||
files: ["src/stories/**/*"],
|
||||
parserOptions: {
|
||||
project: resolve(__dirname, "src/stories/tsconfig.json"),
|
||||
},
|
||||
extends: [
|
||||
...baseConfig.extends,
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
rules: {
|
||||
...baseConfig.rules,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["src/view/**/*"],
|
||||
parserOptions: {
|
||||
project: resolve(__dirname, "src/view/tsconfig.json"),
|
||||
},
|
||||
extends: [
|
||||
...baseConfig.extends,
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
],
|
||||
rules: {
|
||||
...baseConfig.rules,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["test/**/*"],
|
||||
parserOptions: {
|
||||
project: resolve(__dirname, "test/tsconfig.json"),
|
||||
},
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["test/vscode-tests/**/*"],
|
||||
parserOptions: {
|
||||
project: resolve(__dirname, "test/tsconfig.json"),
|
||||
},
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
...baseConfig.rules,
|
||||
"@typescript-eslint/ban-types": [
|
||||
"error",
|
||||
{
|
||||
// For a full list of the default banned types, see:
|
||||
// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md
|
||||
extendDefaults: true,
|
||||
types: {
|
||||
// Don't complain about the `Function` type in test files. (Default is `true`.)
|
||||
Function: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
".eslintrc.js",
|
||||
"test/**/jest-runner-vscode.config.js",
|
||||
"test/**/jest-runner-vscode.config.base.js",
|
||||
],
|
||||
parser: undefined,
|
||||
plugins: ["github"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:github/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
],
|
||||
rules: {
|
||||
"import/no-commonjs": "off",
|
||||
"prefer-template": "off",
|
||||
"filenames/match-regex": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -395,7 +395,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.removeItemContextMenu",
|
||||
"title": "Remove"
|
||||
"title": "Delete"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.chooseDatabaseFolder",
|
||||
@@ -523,7 +523,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQuery",
|
||||
"title": "Open Query",
|
||||
"title": "View Query",
|
||||
"icon": "$(edit)"
|
||||
},
|
||||
{
|
||||
@@ -533,7 +533,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItem",
|
||||
"title": "Remove from History",
|
||||
"title": "Delete",
|
||||
"icon": "$(trash)"
|
||||
},
|
||||
{
|
||||
@@ -550,11 +550,11 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryLog",
|
||||
"title": "Show Query Log"
|
||||
"title": "View Query Log"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryDirectory",
|
||||
"title": "Open Query Directory"
|
||||
"title": "Open Results Directory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLog",
|
||||
@@ -574,7 +574,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryText",
|
||||
"title": "Show Query Text"
|
||||
"title": "View Query Text"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.exportResults",
|
||||
@@ -597,8 +597,8 @@
|
||||
"title": "View DIL"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.setLabel",
|
||||
"title": "Set Label"
|
||||
"command": "codeQLQueryHistory.renameItem",
|
||||
"title": "Rename"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.compareWith",
|
||||
@@ -606,7 +606,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openOnGithub",
|
||||
"title": "Open Variant Analysis on GitHub"
|
||||
"title": "View Logs"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.copyRepoList",
|
||||
@@ -827,57 +827,62 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQuery",
|
||||
"group": "9_qlCommands",
|
||||
"group": "2_queryHistory@0",
|
||||
"when": "view == codeQLQueryHistory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItem",
|
||||
"group": "9_qlCommands",
|
||||
"group": "7_queryHistory@0",
|
||||
"when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.setLabel",
|
||||
"group": "9_qlCommands",
|
||||
"command": "codeQLQueryHistory.removeHistoryItem",
|
||||
"group": "inline",
|
||||
"when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.renameItem",
|
||||
"group": "6_queryHistory@0",
|
||||
"when": "view == codeQLQueryHistory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.compareWith",
|
||||
"group": "9_qlCommands",
|
||||
"group": "3_queryHistory@0",
|
||||
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryLog",
|
||||
"group": "9_qlCommands",
|
||||
"group": "4_queryHistory@4",
|
||||
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryDirectory",
|
||||
"group": "9_qlCommands",
|
||||
"group": "2_queryHistory@4",
|
||||
"when": "view == codeQLQueryHistory && !hasRemoteServer"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLog",
|
||||
"group": "9_qlCommands",
|
||||
"group": "4_queryHistory@1",
|
||||
"when": "codeql.supportsEvalLog && viewItem == rawResultsItem || codeql.supportsEvalLog && viewItem == interpretedResultsItem || codeql.supportsEvalLog && viewItem == cancelledResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLogSummary",
|
||||
"group": "9_qlCommands",
|
||||
"group": "4_queryHistory@2",
|
||||
"when": "codeql.supportsEvalLog && viewItem == rawResultsItem || codeql.supportsEvalLog && viewItem == interpretedResultsItem || codeql.supportsEvalLog && viewItem == cancelledResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLogViewer",
|
||||
"group": "9_qlCommands",
|
||||
"group": "4_queryHistory@3",
|
||||
"when": "config.codeQL.canary && codeql.supportsEvalLog && viewItem == rawResultsItem || config.codeQL.canary && codeql.supportsEvalLog && viewItem == interpretedResultsItem || config.codeQL.canary && codeql.supportsEvalLog && viewItem == cancelledResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryText",
|
||||
"group": "9_qlCommands",
|
||||
"group": "2_queryHistory@2",
|
||||
"when": "view == codeQLQueryHistory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.exportResults",
|
||||
"group": "9_qlCommands",
|
||||
"group": "1_queryHistory@0",
|
||||
"when": "view == codeQLQueryHistory && viewItem == remoteResultsItem"
|
||||
},
|
||||
{
|
||||
@@ -887,17 +892,17 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewCsvAlerts",
|
||||
"group": "9_qlCommands",
|
||||
"group": "5_queryHistory@0",
|
||||
"when": "viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarifAlerts",
|
||||
"group": "9_qlCommands",
|
||||
"group": "5_queryHistory@1",
|
||||
"when": "viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewDil",
|
||||
"group": "9_qlCommands",
|
||||
"group": "5_queryHistory@2",
|
||||
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
@@ -907,12 +912,12 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openOnGithub",
|
||||
"group": "9_qlCommands",
|
||||
"group": "2_queryHistory@3",
|
||||
"when": "viewItem == remoteResultsItem || viewItem == inProgressRemoteResultsItem || viewItem == cancelledRemoteResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.copyRepoList",
|
||||
"group": "9_qlCommands",
|
||||
"group": "1_queryHistory@1",
|
||||
"when": "viewItem == remoteResultsItem"
|
||||
},
|
||||
{
|
||||
@@ -1160,7 +1165,7 @@
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.setLabel",
|
||||
"command": "codeQLQueryHistory.renameItem",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
@@ -1310,6 +1315,11 @@
|
||||
{
|
||||
"view": "codeQLEvalLogViewer",
|
||||
"contents": "Run the 'Show Evaluator Log (UI)' command on a CodeQL query run in the Query History view."
|
||||
},
|
||||
{
|
||||
"view": "codeQLVariantAnalysisRepositories",
|
||||
"contents": "Set up a controller repository to start using variant analysis.\n[Set up controller repository](command:codeQLVariantAnalysisRepositories.setupControllerRepository)",
|
||||
"when": "!config.codeQL.variantAnalysis.controllerRepo"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1325,7 +1335,7 @@
|
||||
"cli-integration": "jest --projects test/vscode-tests/cli-integration",
|
||||
"update-vscode": "node ./node_modules/vscode/bin/install",
|
||||
"format": "prettier --write **/*.{ts,tsx} && eslint . --ext .ts,.tsx --fix",
|
||||
"lint": "eslint . --ext .ts,.tsx --max-warnings=0",
|
||||
"lint": "eslint . --ext .js,.ts,.tsx --max-warnings=0",
|
||||
"format-staged": "lint-staged",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook",
|
||||
|
||||
@@ -50,14 +50,20 @@ function getNwoOrOwnerFromGitHubUrl(
|
||||
kind: "owner" | "nwo",
|
||||
): string | undefined {
|
||||
try {
|
||||
const uri = new URL(githubUrl);
|
||||
if (uri.protocol !== "https:") {
|
||||
return;
|
||||
let paths: string[];
|
||||
const urlElements = githubUrl.split("/");
|
||||
if (
|
||||
urlElements[0] === "github.com" ||
|
||||
urlElements[0] === "www.github.com"
|
||||
) {
|
||||
paths = githubUrl.split("/").slice(1);
|
||||
} else {
|
||||
const uri = new URL(githubUrl);
|
||||
if (uri.hostname !== "github.com" && uri.hostname !== "www.github.com") {
|
||||
return;
|
||||
}
|
||||
paths = uri.pathname.split("/").filter((segment: string) => segment);
|
||||
}
|
||||
if (uri.hostname !== "github.com" && uri.hostname !== "www.github.com") {
|
||||
return;
|
||||
}
|
||||
const paths = uri.pathname.split("/").filter((segment: string) => segment);
|
||||
const owner = `${paths[0]}`;
|
||||
if (kind === "owner") {
|
||||
return owner ? owner : undefined;
|
||||
|
||||
@@ -92,6 +92,15 @@ export const GLOBAL_ENABLE_TELEMETRY = new Setting(
|
||||
GLOBAL_TELEMETRY_SETTING,
|
||||
);
|
||||
|
||||
const ENABLE_NEW_TELEMETRY = new Setting(
|
||||
"enableNewTelemetry",
|
||||
TELEMETRY_SETTING,
|
||||
);
|
||||
|
||||
export function newTelemetryEnabled(): boolean {
|
||||
return ENABLE_NEW_TELEMETRY.getValue<boolean>();
|
||||
}
|
||||
|
||||
// Distribution configuration
|
||||
const DISTRIBUTION_SETTING = new Setting("cli", ROOT_SETTING);
|
||||
export const CUSTOM_CODEQL_PATH_SETTING = new Setting(
|
||||
@@ -539,6 +548,27 @@ export async function setRemoteControllerRepo(repo: string | undefined) {
|
||||
await REMOTE_CONTROLLER_REPO.updateValue(repo, ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
export interface VariantAnalysisConfig {
|
||||
controllerRepo: string | undefined;
|
||||
onDidChangeConfiguration?: Event<void>;
|
||||
}
|
||||
|
||||
export class VariantAnalysisConfigListener
|
||||
extends ConfigListener
|
||||
implements VariantAnalysisConfig
|
||||
{
|
||||
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
|
||||
this.handleDidChangeConfigurationForRelevantSettings(
|
||||
[VARIANT_ANALYSIS_SETTING],
|
||||
e,
|
||||
);
|
||||
}
|
||||
|
||||
public get controllerRepo(): string | undefined {
|
||||
return getRemoteControllerRepo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The branch of "github/codeql-variant-analysis-action" to use with the "Run Variant Analysis" command.
|
||||
* Default value is "main".
|
||||
|
||||
@@ -24,7 +24,7 @@ export class DbModule extends DisposableObject {
|
||||
const dbModule = new DbModule(app);
|
||||
app.subscriptions.push(dbModule);
|
||||
|
||||
await dbModule.initialize();
|
||||
await dbModule.initialize(app);
|
||||
return dbModule;
|
||||
}
|
||||
|
||||
@@ -39,12 +39,12 @@ export class DbModule extends DisposableObject {
|
||||
return isCanary() && isVariantAnalysisReposPanelEnabled();
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
private async initialize(app: App): Promise<void> {
|
||||
void extLogger.log("Initializing database module");
|
||||
|
||||
await this.dbConfigStore.initialize();
|
||||
|
||||
const dbPanel = new DbPanel(this.dbManager);
|
||||
const dbPanel = new DbPanel(this.dbManager, app.credentials);
|
||||
await dbPanel.initialize();
|
||||
|
||||
this.push(dbPanel);
|
||||
|
||||
@@ -29,6 +29,9 @@ import { DbManager } from "../db-manager";
|
||||
import { DbTreeDataProvider } from "./db-tree-data-provider";
|
||||
import { DbTreeViewItem } from "./db-tree-view-item";
|
||||
import { getGitHubUrl } from "./db-tree-view-item-action";
|
||||
import { getControllerRepo } from "../../remote-queries/run-remote-query";
|
||||
import { getErrorMessage } from "../../pure/helpers-pure";
|
||||
import { Credentials } from "../../common/authentication";
|
||||
|
||||
export interface RemoteDatabaseQuickPickItem extends QuickPickItem {
|
||||
kind: string;
|
||||
@@ -42,7 +45,10 @@ export class DbPanel extends DisposableObject {
|
||||
private readonly dataProvider: DbTreeDataProvider;
|
||||
private readonly treeView: TreeView<DbTreeViewItem>;
|
||||
|
||||
public constructor(private readonly dbManager: DbManager) {
|
||||
public constructor(
|
||||
private readonly dbManager: DbManager,
|
||||
private readonly credentials: Credentials,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.dataProvider = new DbTreeDataProvider(dbManager);
|
||||
@@ -112,6 +118,12 @@ export class DbPanel extends DisposableObject {
|
||||
(treeViewItem: DbTreeViewItem) => this.removeItem(treeViewItem),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLVariantAnalysisRepositories.setupControllerRepository",
|
||||
() => this.setupControllerRepository(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private async openConfigFile(): Promise<void> {
|
||||
@@ -172,7 +184,7 @@ export class DbPanel extends DisposableObject {
|
||||
const repoName = await window.showInputBox({
|
||||
title: "Add a repository",
|
||||
prompt: "Insert a GitHub repository URL or name with owner",
|
||||
placeHolder: "github.com/<owner>/<repo> or <owner>/<repo>",
|
||||
placeHolder: "<owner>/<repo> or https://github.com/<owner>/<repo>",
|
||||
});
|
||||
if (!repoName) {
|
||||
return;
|
||||
@@ -196,7 +208,7 @@ export class DbPanel extends DisposableObject {
|
||||
const ownerName = await window.showInputBox({
|
||||
title: "Add all repositories of a GitHub org or owner",
|
||||
prompt: "Insert a GitHub organization or owner name",
|
||||
placeHolder: "github.com/<owner> or <owner>",
|
||||
placeHolder: "<owner> or https://github.com/<owner>",
|
||||
});
|
||||
|
||||
if (!ownerName) {
|
||||
@@ -383,4 +395,21 @@ export class DbPanel extends DisposableObject {
|
||||
|
||||
await commands.executeCommand("vscode.open", Uri.parse(githubUrl));
|
||||
}
|
||||
|
||||
private async setupControllerRepository(): Promise<void> {
|
||||
try {
|
||||
// This will also validate that the controller repository is valid
|
||||
await getControllerRepo(this.credentials);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof UserCancellationException) {
|
||||
return;
|
||||
}
|
||||
|
||||
void showAndLogErrorMessage(
|
||||
`An error occurred while setting up the controller repository: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
DbConfigValidationError,
|
||||
DbConfigValidationErrorKind,
|
||||
} from "../db-validation-errors";
|
||||
import { VariantAnalysisConfigListener } from "../../config";
|
||||
|
||||
export class DbTreeDataProvider
|
||||
extends DisposableObject
|
||||
@@ -27,8 +28,17 @@ export class DbTreeDataProvider
|
||||
);
|
||||
private dbTreeItems: DbTreeViewItem[];
|
||||
|
||||
private variantAnalysisConfig: VariantAnalysisConfigListener;
|
||||
|
||||
public constructor(private readonly dbManager: DbManager) {
|
||||
super();
|
||||
|
||||
this.variantAnalysisConfig = this.push(new VariantAnalysisConfigListener());
|
||||
this.variantAnalysisConfig.onDidChangeConfiguration(() => {
|
||||
this.dbTreeItems = this.createTree();
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
});
|
||||
|
||||
this.dbTreeItems = this.createTree();
|
||||
this.onDidChangeTreeData = this._onDidChangeTreeData.event;
|
||||
|
||||
@@ -62,6 +72,11 @@ export class DbTreeDataProvider
|
||||
}
|
||||
|
||||
private createTree(): DbTreeViewItem[] {
|
||||
// Returning an empty tree here will show the welcome view
|
||||
if (!this.variantAnalysisConfig.controllerRepo) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dbItemsResult = this.dbManager.getDbItems();
|
||||
|
||||
if (dbItemsResult.isFailure) {
|
||||
|
||||
@@ -635,7 +635,6 @@ async function activateWithInstalledDistribution(
|
||||
);
|
||||
await ensureDir(variantAnalysisStorageDir);
|
||||
const variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
app.credentials,
|
||||
cliServer,
|
||||
extLogger,
|
||||
);
|
||||
|
||||
@@ -239,8 +239,8 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLQueryHistory.setLabel",
|
||||
this.handleSetLabel.bind(this),
|
||||
"codeQLQueryHistory.renameItem",
|
||||
this.handleRenameItem.bind(this),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
@@ -752,7 +752,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
async handleSetLabel(
|
||||
async handleRenameItem(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[],
|
||||
): Promise<void> {
|
||||
|
||||
@@ -81,16 +81,6 @@ export async function getVariantAnalysisRepo(
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getVariantAnalysisRepoResult(
|
||||
credentials: Credentials,
|
||||
downloadUrl: string,
|
||||
): Promise<ArrayBuffer> {
|
||||
const octokit = await credentials.getOctokit();
|
||||
const response = await octokit.request(`GET ${downloadUrl}`);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getRepositoryFromNwo(
|
||||
credentials: Credentials,
|
||||
owner: string,
|
||||
|
||||
@@ -376,10 +376,10 @@ export async function getControllerRepo(
|
||||
);
|
||||
controllerRepoNwo = await window.showInputBox({
|
||||
title:
|
||||
"Controller repository in which to run the GitHub Actions workflow for this variant analysis",
|
||||
"Controller repository in which to run GitHub Actions workflows for variant analyses",
|
||||
placeHolder: "<owner>/<repo>",
|
||||
prompt:
|
||||
"Enter the name of a GitHub repository in the format <owner>/<repo>",
|
||||
"Enter the name of a GitHub repository in the format <owner>/<repo>. You can change this in the extension settings.",
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
if (!controllerRepoNwo) {
|
||||
|
||||
@@ -127,6 +127,7 @@ export enum VariantAnalysisScannedRepositoryDownloadStatus {
|
||||
export interface VariantAnalysisScannedRepositoryState {
|
||||
repositoryId: number;
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus;
|
||||
downloadPercentage?: number;
|
||||
}
|
||||
|
||||
export interface VariantAnalysisScannedRepositoryResult {
|
||||
|
||||
@@ -69,6 +69,7 @@ export class VariantAnalysisManager
|
||||
implements VariantAnalysisViewManager<VariantAnalysisView>
|
||||
{
|
||||
private static readonly REPO_STATES_FILENAME = "repo_states.json";
|
||||
private static readonly DOWNLOAD_PERCENTAGE_UPDATE_DELAY_MS = 500;
|
||||
|
||||
private readonly _onVariantAnalysisAdded = this.push(
|
||||
new EventEmitter<VariantAnalysis>(),
|
||||
@@ -515,10 +516,27 @@ export class VariantAnalysisManager
|
||||
await this.onRepoStateUpdated(variantAnalysis.id, repoState);
|
||||
|
||||
try {
|
||||
let lastRepoStateUpdate = 0;
|
||||
const updateRepoStateCallback = async (downloadPercentage: number) => {
|
||||
const now = new Date().getTime();
|
||||
if (
|
||||
lastRepoStateUpdate <
|
||||
now - VariantAnalysisManager.DOWNLOAD_PERCENTAGE_UPDATE_DELAY_MS
|
||||
) {
|
||||
lastRepoStateUpdate = now;
|
||||
await this.onRepoStateUpdated(variantAnalysis.id, {
|
||||
repositoryId: scannedRepo.repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
downloadPercentage,
|
||||
});
|
||||
}
|
||||
};
|
||||
await this.variantAnalysisResultsManager.download(
|
||||
variantAnalysis.id,
|
||||
repoTask,
|
||||
this.getVariantAnalysisStorageLocation(variantAnalysis.id),
|
||||
updateRepoStateCallback,
|
||||
);
|
||||
} catch (e) {
|
||||
repoState.downloadStatus =
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
pathExists,
|
||||
mkdir,
|
||||
outputJson,
|
||||
writeFileSync,
|
||||
readJson,
|
||||
} from "fs-extra";
|
||||
import { appendFile, pathExists, mkdir, outputJson, readJson } from "fs-extra";
|
||||
import fetch from "node-fetch";
|
||||
import { EOL } from "os";
|
||||
import { join } from "path";
|
||||
|
||||
import { Credentials } from "../common/authentication";
|
||||
import { Logger } from "../common";
|
||||
import { AnalysisAlert, AnalysisRawResults } from "./shared/analysis-result";
|
||||
import { sarifParser } from "../sarif-parser";
|
||||
@@ -21,7 +15,6 @@ import {
|
||||
VariantAnalysisScannedRepositoryResult,
|
||||
} from "./shared/variant-analysis";
|
||||
import { DisposableObject, DisposeHandler } from "../pure/disposable-object";
|
||||
import { getVariantAnalysisRepoResult } from "./gh-api/gh-api-client";
|
||||
import { EventEmitter } from "vscode";
|
||||
import { unzipFile } from "../pure/zip";
|
||||
|
||||
@@ -63,7 +56,6 @@ export class VariantAnalysisResultsManager extends DisposableObject {
|
||||
readonly onResultLoaded = this._onResultLoaded.event;
|
||||
|
||||
constructor(
|
||||
private readonly credentials: Credentials,
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
private readonly logger: Logger,
|
||||
) {
|
||||
@@ -75,6 +67,7 @@ export class VariantAnalysisResultsManager extends DisposableObject {
|
||||
variantAnalysisId: number,
|
||||
repoTask: VariantAnalysisRepositoryTask,
|
||||
variantAnalysisStoragePath: string,
|
||||
onDownloadPercentageChanged: (downloadPercentage: number) => Promise<void>,
|
||||
): Promise<void> {
|
||||
if (!repoTask.artifactUrl) {
|
||||
throw new Error("Missing artifact URL");
|
||||
@@ -85,11 +78,6 @@ export class VariantAnalysisResultsManager extends DisposableObject {
|
||||
repoTask.repository.fullName,
|
||||
);
|
||||
|
||||
const result = await getVariantAnalysisRepoResult(
|
||||
this.credentials,
|
||||
repoTask.artifactUrl,
|
||||
);
|
||||
|
||||
if (!(await pathExists(resultDirectory))) {
|
||||
await mkdir(resultDirectory, { recursive: true });
|
||||
}
|
||||
@@ -100,12 +88,28 @@ export class VariantAnalysisResultsManager extends DisposableObject {
|
||||
);
|
||||
|
||||
const zipFilePath = join(resultDirectory, "results.zip");
|
||||
|
||||
const response = await fetch(repoTask.artifactUrl);
|
||||
|
||||
let responseSize = parseInt(response.headers.get("content-length") || "0");
|
||||
if (responseSize === 0 && response.size > 0) {
|
||||
responseSize = response.size;
|
||||
}
|
||||
|
||||
let amountDownloaded = 0;
|
||||
for await (const chunk of response.body) {
|
||||
await appendFile(zipFilePath, Buffer.from(chunk));
|
||||
amountDownloaded += chunk.length;
|
||||
await onDownloadPercentageChanged(
|
||||
Math.floor((amountDownloaded / responseSize) * 100),
|
||||
);
|
||||
}
|
||||
|
||||
const unzippedFilesDirectory = join(
|
||||
resultDirectory,
|
||||
VariantAnalysisResultsManager.RESULTS_DIRECTORY,
|
||||
);
|
||||
|
||||
writeFileSync(zipFilePath, Buffer.from(result));
|
||||
await unzipFile(zipFilePath, unzippedFilesDirectory);
|
||||
|
||||
this._onResultDownloaded.fire({
|
||||
|
||||
@@ -52,7 +52,7 @@ export class VariantAnalysisView
|
||||
});
|
||||
|
||||
const panel = await this.getPanel();
|
||||
panel.title = `${variantAnalysis.query.name} - CodeQL Query Results`;
|
||||
panel.title = this.getTitle(variantAnalysis);
|
||||
}
|
||||
|
||||
public async updateRepoState(
|
||||
@@ -88,9 +88,7 @@ export class VariantAnalysisView
|
||||
|
||||
return {
|
||||
viewId: VariantAnalysisView.viewType,
|
||||
title: variantAnalysis
|
||||
? `${variantAnalysis.query.name} - CodeQL Query Results`
|
||||
: `Variant analysis ${this.variantAnalysisId} - CodeQL Query Results`,
|
||||
title: this.getTitle(variantAnalysis),
|
||||
viewColumn: ViewColumn.Active,
|
||||
preserveFocus: true,
|
||||
view: "variant-analysis",
|
||||
@@ -189,4 +187,10 @@ export class VariantAnalysisView
|
||||
repoStates,
|
||||
});
|
||||
}
|
||||
|
||||
private getTitle(variantAnalysis: VariantAnalysis | undefined): string {
|
||||
return variantAnalysis
|
||||
? `${variantAnalysis.query.name} - Variant Analysis Results`
|
||||
: `Variant Analysis ${this.variantAnalysisId} - Results`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
plugins: ["github"],
|
||||
extends: [
|
||||
"plugin:github/react",
|
||||
"plugin:github/recommended",
|
||||
"plugin:github/typescript",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
rules: {
|
||||
"filenames/match-regex": "off",
|
||||
"import/named": "off",
|
||||
"import/no-namespace": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"no-unused-vars": "off",
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -15,7 +15,7 @@ import { createMockRepositoryWithMetadata } from "../../../test/factories/remote
|
||||
|
||||
import * as analysesResults from "../remote-queries/data/analysesResultsMessage.json";
|
||||
import * as rawResults from "../remote-queries/data/rawResults.json";
|
||||
import { RepoRow } from "../../view/variant-analysis/RepoRow";
|
||||
import { RepoRow, RepoRowProps } from "../../view/variant-analysis/RepoRow";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Repo Row",
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
],
|
||||
} as ComponentMeta<typeof RepoRow>;
|
||||
|
||||
const Template: ComponentStory<typeof RepoRow> = (args) => (
|
||||
const Template: ComponentStory<typeof RepoRow> = (args: RepoRowProps) => (
|
||||
<RepoRow {...args} />
|
||||
);
|
||||
|
||||
@@ -77,7 +77,22 @@ SucceededDownloading.args = {
|
||||
...Pending.args,
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 198,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
downloadState: {
|
||||
repositoryId: 63537249,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
},
|
||||
};
|
||||
|
||||
export const SucceededDownloadingWithPercentage = Template.bind({});
|
||||
SucceededDownloadingWithPercentage.args = {
|
||||
...Pending.args,
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 198,
|
||||
downloadState: {
|
||||
repositoryId: 63537249,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
downloadPercentage: 42,
|
||||
},
|
||||
};
|
||||
|
||||
export const SucceededSuccessfulDownload = Template.bind({});
|
||||
@@ -85,7 +100,10 @@ SucceededSuccessfulDownload.args = {
|
||||
...Pending.args,
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 198,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
downloadState: {
|
||||
repositoryId: 63537249,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
};
|
||||
|
||||
export const SucceededFailedDownload = Template.bind({});
|
||||
@@ -93,7 +111,10 @@ SucceededFailedDownload.args = {
|
||||
...Pending.args,
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 198,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||
downloadState: {
|
||||
repositoryId: 63537249,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||
},
|
||||
};
|
||||
|
||||
export const InterpretedResults = Template.bind({});
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
LOG_TELEMETRY,
|
||||
isIntegrationTestMode,
|
||||
isCanary,
|
||||
newTelemetryEnabled,
|
||||
} from "./config";
|
||||
import * as appInsights from "applicationinsights";
|
||||
import { extLogger } from "./common";
|
||||
@@ -173,6 +174,10 @@ export class TelemetryListener extends ConfigListener {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newTelemetryEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.reporter.sendTelemetryEvent(
|
||||
"ui-interaction",
|
||||
{
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true
|
||||
},
|
||||
plugins: [
|
||||
"github",
|
||||
],
|
||||
extends: [
|
||||
"plugin:github/react",
|
||||
"plugin:github/recommended",
|
||||
"plugin:github/typescript",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-invalid-this": "off",
|
||||
"@typescript-eslint/no-shadow": "off",
|
||||
"camelcase": "off",
|
||||
"eqeqeq": "off",
|
||||
"filenames/match-regex": "off",
|
||||
"i18n-text/no-en": "off",
|
||||
"import/named": "off",
|
||||
"import/no-dynamic-require": "off",
|
||||
"import/no-dynamic-required": "off",
|
||||
"import/no-namespace": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"jsx-a11y/anchor-is-valid": "off",
|
||||
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||
"jsx-a11y/no-static-element-interactions": "off",
|
||||
"jsx-a11y/click-events-have-key-events": "off",
|
||||
"no-console": "off",
|
||||
"no-invalid-this": "off",
|
||||
"no-undef": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-shadow": "off",
|
||||
"github/array-foreach": "off",
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
percent: number;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
const Circle = styled.div`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
`;
|
||||
|
||||
const Background = styled.circle`
|
||||
stroke: var(--vscode-editorWidget-background);
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
`;
|
||||
|
||||
const Determinate = styled.circle`
|
||||
stroke: var(--vscode-progressBar-background);
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(-90deg);
|
||||
transition: all 0.2s ease-in-out 0s;
|
||||
`;
|
||||
|
||||
const progressSegments = 44;
|
||||
|
||||
// This is a re-implementation of the FAST component progress ring
|
||||
// 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) => (
|
||||
<Circle
|
||||
role="progressbar"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={percent}
|
||||
>
|
||||
<svg className="progress" viewBox="0 0 16 16">
|
||||
<Background cx="8px" cy="8px" r="7px" />
|
||||
<Determinate
|
||||
style={{
|
||||
strokeDasharray: `${
|
||||
(progressSegments * percent) / 100
|
||||
}px ${progressSegments}px`,
|
||||
}}
|
||||
cx="8px"
|
||||
cy="8px"
|
||||
r="7px"
|
||||
></Determinate>
|
||||
</svg>
|
||||
</Circle>
|
||||
);
|
||||
@@ -17,6 +17,7 @@ export const Codicon = ({ name, label, className, slot }: Props) => (
|
||||
<CodiconIcon
|
||||
role="img"
|
||||
aria-label={label}
|
||||
title={label}
|
||||
className={classNames("codicon", `codicon-${name}`, className)}
|
||||
slot={slot}
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
isCompletedAnalysisRepoStatus,
|
||||
VariantAnalysisRepoStatus,
|
||||
VariantAnalysisScannedRepositoryDownloadStatus,
|
||||
VariantAnalysisScannedRepositoryState,
|
||||
} from "../../remote-queries/shared/variant-analysis";
|
||||
import { formatDecimal } from "../../pure/number";
|
||||
import {
|
||||
@@ -25,6 +26,7 @@ import { AnalyzedRepoItemContent } from "./AnalyzedRepoItemContent";
|
||||
import StarCount from "../common/StarCount";
|
||||
import { LastUpdated } from "../common/LastUpdated";
|
||||
import { useTelemetryOnChange } from "../common/telemetry";
|
||||
import { DeterminateProgressRing } from "../common/DeterminateProgressRing";
|
||||
|
||||
// This will ensure that these icons have a className which we can use in the TitleContainer
|
||||
const ExpandCollapseCodicon = styled(Codicon)``;
|
||||
@@ -91,7 +93,7 @@ export type RepoRowProps = {
|
||||
repository: Partial<RepositoryWithMetadata> &
|
||||
Pick<RepositoryWithMetadata, "fullName">;
|
||||
status?: VariantAnalysisRepoStatus;
|
||||
downloadStatus?: VariantAnalysisScannedRepositoryDownloadStatus;
|
||||
downloadState?: VariantAnalysisScannedRepositoryState;
|
||||
resultCount?: number;
|
||||
|
||||
interpretedResults?: AnalysisAlert[];
|
||||
@@ -163,7 +165,7 @@ const filterRepoRowExpandedTelemetry = (v: boolean) => v;
|
||||
export const RepoRow = ({
|
||||
repository,
|
||||
status,
|
||||
downloadStatus,
|
||||
downloadState,
|
||||
resultCount,
|
||||
interpretedResults,
|
||||
rawResults,
|
||||
@@ -185,7 +187,7 @@ export const RepoRow = ({
|
||||
if (
|
||||
resultsLoaded ||
|
||||
status !== VariantAnalysisRepoStatus.Succeeded ||
|
||||
downloadStatus !==
|
||||
downloadState?.downloadStatus !==
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded
|
||||
) {
|
||||
setExpanded((oldIsExpanded) => !oldIsExpanded);
|
||||
@@ -203,7 +205,7 @@ export const RepoRow = ({
|
||||
resultsLoaded,
|
||||
repository.fullName,
|
||||
status,
|
||||
downloadStatus,
|
||||
downloadState,
|
||||
setExpanded,
|
||||
]);
|
||||
|
||||
@@ -234,10 +236,11 @@ export const RepoRow = ({
|
||||
[onSelectedChange, repository],
|
||||
);
|
||||
|
||||
const disabled = !canExpand(status, downloadStatus) || resultsLoading;
|
||||
const disabled =
|
||||
!canExpand(status, downloadState?.downloadStatus) || resultsLoading;
|
||||
const expandableContentLoaded = isExpandableContentLoaded(
|
||||
status,
|
||||
downloadStatus,
|
||||
downloadState?.downloadStatus,
|
||||
resultsLoaded,
|
||||
);
|
||||
|
||||
@@ -252,7 +255,9 @@ export const RepoRow = ({
|
||||
onChange={onChangeCheckbox}
|
||||
onClick={onClickCheckbox}
|
||||
checked={selected}
|
||||
disabled={!repository.id || !canSelect(status, downloadStatus)}
|
||||
disabled={
|
||||
!repository.id || !canSelect(status, downloadState?.downloadStatus)
|
||||
}
|
||||
/>
|
||||
{isExpanded && (
|
||||
<ExpandCollapseCodicon name="chevron-down" label="Collapse" />
|
||||
@@ -278,11 +283,13 @@ export const RepoRow = ({
|
||||
)}
|
||||
{!status && <WarningIcon />}
|
||||
</span>
|
||||
{downloadStatus ===
|
||||
{downloadState?.downloadStatus ===
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress && (
|
||||
<LoadingIcon label="Downloading" />
|
||||
<DeterminateProgressRing
|
||||
percent={downloadState.downloadPercentage ?? 0}
|
||||
/>
|
||||
)}
|
||||
{downloadStatus ===
|
||||
{downloadState?.downloadStatus ===
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Failed && (
|
||||
<WarningIcon label="Failed to download the results" />
|
||||
)}
|
||||
@@ -296,7 +303,7 @@ export const RepoRow = ({
|
||||
{isExpanded && expandableContentLoaded && (
|
||||
<AnalyzedRepoItemContent
|
||||
status={status}
|
||||
downloadStatus={downloadStatus}
|
||||
downloadStatus={downloadState?.downloadStatus}
|
||||
interpretedResults={interpretedResults}
|
||||
rawResults={rawResults}
|
||||
/>
|
||||
|
||||
@@ -89,7 +89,7 @@ export const VariantAnalysisAnalyzedRepos = ({
|
||||
key={repository.repository.id}
|
||||
repository={repository.repository}
|
||||
status={repository.analysisStatus}
|
||||
downloadStatus={state?.downloadStatus}
|
||||
downloadState={state}
|
||||
resultCount={repository.resultCount}
|
||||
interpretedResults={results?.interpretedResults}
|
||||
rawResults={results?.rawResults}
|
||||
|
||||
@@ -87,7 +87,10 @@ describe(RepoRow.name, () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 178,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Pending,
|
||||
downloadState: {
|
||||
repositoryId: 1,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Pending,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -101,7 +104,11 @@ describe(RepoRow.name, () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 178,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
downloadState: {
|
||||
repositoryId: 1,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -115,7 +122,11 @@ describe(RepoRow.name, () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 178,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
downloadState: {
|
||||
repositoryId: 1,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -129,7 +140,10 @@ describe(RepoRow.name, () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 178,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||
downloadState: {
|
||||
repositoryId: 1,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -320,7 +334,11 @@ describe(RepoRow.name, () => {
|
||||
it("can expand the repo item when succeeded and loaded", async () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
downloadState: {
|
||||
repositoryId: 1,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
interpretedResults: [],
|
||||
});
|
||||
|
||||
@@ -340,7 +358,11 @@ describe(RepoRow.name, () => {
|
||||
it("can expand the repo item when succeeded and not loaded", async () => {
|
||||
const { rerender } = render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
downloadState: {
|
||||
repositoryId: 1,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
|
||||
await userEvent.click(
|
||||
@@ -411,7 +433,10 @@ describe(RepoRow.name, () => {
|
||||
it("does not allow selecting the item if the item has not been downloaded successfully", async () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||
downloadState: {
|
||||
repositoryId: 1,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||
},
|
||||
});
|
||||
|
||||
// It seems like sometimes the first render doesn't have the checkbox disabled
|
||||
@@ -424,7 +449,11 @@ describe(RepoRow.name, () => {
|
||||
it("allows selecting the item if the item has been downloaded", async () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
downloadState: {
|
||||
repositoryId: 1,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.getByRole("checkbox")).toBeEnabled();
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
parserOptions: {
|
||||
project: "./test/tsconfig.json",
|
||||
},
|
||||
plugins: [
|
||||
"github",
|
||||
],
|
||||
extends: [
|
||||
"plugin:github/react",
|
||||
"plugin:github/recommended",
|
||||
"plugin:github/typescript",
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-shadow": "off",
|
||||
"camelcase": "off",
|
||||
"filenames/match-regex": "off",
|
||||
"i18n-text/no-en": "off",
|
||||
"import/no-namespace": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"no-console": "off",
|
||||
"no-shadow": "off",
|
||||
"no-undef": "off",
|
||||
"github/array-foreach": "off",
|
||||
}
|
||||
};
|
||||
@@ -2,7 +2,6 @@ import { expect } from "@jest/globals";
|
||||
import type { MatcherFunction } from "expect";
|
||||
import { pathsEqual } from "../../src/pure/files";
|
||||
|
||||
// eslint-disable-next-line func-style -- We need to have access to this and specify the type of the function
|
||||
const toEqualPath: MatcherFunction<[expectedPath: unknown]> = function (
|
||||
actual,
|
||||
expectedPath,
|
||||
@@ -15,20 +14,16 @@ const toEqualPath: MatcherFunction<[expectedPath: unknown]> = function (
|
||||
if (pass) {
|
||||
return {
|
||||
message: () =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-invalid-this
|
||||
`expected ${this.utils.printReceived(
|
||||
actual,
|
||||
// eslint-disable-next-line @typescript-eslint/no-invalid-this
|
||||
)} to equal path ${this.utils.printExpected(expectedPath)}`,
|
||||
pass: true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
message: () =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-invalid-this
|
||||
`expected ${this.utils.printReceived(
|
||||
actual,
|
||||
// eslint-disable-next-line @typescript-eslint/no-invalid-this
|
||||
)} to equal path ${this.utils.printExpected(expectedPath)}`,
|
||||
pass: false,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"include": ["**/*.ts", "../src/**/*.ts"],
|
||||
"include": ["**/*.ts", "../src/**/*.ts", "**/.eslintrc.js", "**/*.config.js"],
|
||||
"exclude": [],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
|
||||
@@ -24,7 +24,6 @@ describe("github url identifier helper", () => {
|
||||
describe("getNwoFromGitHubUrl method", () => {
|
||||
it("should handle invalid urls", () => {
|
||||
expect(getNwoFromGitHubUrl("")).toBe(undefined);
|
||||
expect(getNwoFromGitHubUrl("http://github.com/foo/bar")).toBe(undefined);
|
||||
expect(getNwoFromGitHubUrl("https://ww.github.com/foo/bar")).toBe(
|
||||
undefined,
|
||||
);
|
||||
@@ -34,7 +33,10 @@ describe("github url identifier helper", () => {
|
||||
});
|
||||
|
||||
it("should handle valid urls", () => {
|
||||
expect(getNwoFromGitHubUrl("github.com/foo/bar")).toBe("foo/bar");
|
||||
expect(getNwoFromGitHubUrl("www.github.com/foo/bar")).toBe("foo/bar");
|
||||
expect(getNwoFromGitHubUrl("https://github.com/foo/bar")).toBe("foo/bar");
|
||||
expect(getNwoFromGitHubUrl("http://github.com/foo/bar")).toBe("foo/bar");
|
||||
expect(getNwoFromGitHubUrl("https://www.github.com/foo/bar")).toBe(
|
||||
"foo/bar",
|
||||
);
|
||||
@@ -47,9 +49,6 @@ describe("github url identifier helper", () => {
|
||||
describe("getOwnerFromGitHubUrl method", () => {
|
||||
it("should handle invalid urls", () => {
|
||||
expect(getOwnerFromGitHubUrl("")).toBe(undefined);
|
||||
expect(getOwnerFromGitHubUrl("http://github.com/foo/bar")).toBe(
|
||||
undefined,
|
||||
);
|
||||
expect(getOwnerFromGitHubUrl("https://ww.github.com/foo/bar")).toBe(
|
||||
undefined,
|
||||
);
|
||||
@@ -58,6 +57,7 @@ describe("github url identifier helper", () => {
|
||||
});
|
||||
|
||||
it("should handle valid urls", () => {
|
||||
expect(getOwnerFromGitHubUrl("http://github.com/foo/bar")).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("https://github.com/foo/bar")).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("https://www.github.com/foo/bar")).toBe(
|
||||
"foo",
|
||||
@@ -66,6 +66,8 @@ describe("github url identifier helper", () => {
|
||||
getOwnerFromGitHubUrl("https://github.com/foo/bar/sub/pages"),
|
||||
).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("https://www.github.com/foo")).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("github.com/foo")).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("www.github.com/foo")).toBe("foo");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
import {
|
||||
getRepositoryFromNwo,
|
||||
getVariantAnalysis,
|
||||
getVariantAnalysisRepo,
|
||||
getVariantAnalysisRepoResult,
|
||||
submitVariantAnalysis,
|
||||
} from "../../../../src/remote-queries/gh-api/gh-api-client";
|
||||
import { createMockSubmission } from "../../../factories/remote-queries/shared/variant-analysis-submission";
|
||||
@@ -69,23 +66,6 @@ describe("getVariantAnalysisRepo", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getVariantAnalysisRepoResult", () => {
|
||||
it("returns the variant analysis repo result", async () => {
|
||||
await mockServer.loadScenario("problem-query-success");
|
||||
|
||||
const result = await getVariantAnalysisRepoResult(
|
||||
testCredentialsWithRealOctokit(),
|
||||
`https://objects-origin.githubusercontent.com/codeql-query-console/codeql-variant-analysis-repo-tasks/${variantAnalysisId}/${repoTaskId}/${faker.datatype.uuid()}`,
|
||||
);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toBeInstanceOf(ArrayBuffer);
|
||||
expect(result.byteLength).toBe(
|
||||
variantAnalysisRepoJson_response.body.artifact_size_in_bytes,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRepositoryFromNwo", () => {
|
||||
it("returns the repository", async () => {
|
||||
await mockServer.loadScenario("problem-query-success");
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
module.exports = {
|
||||
parserOptions: {
|
||||
project: ["../../tsconfig.json"],
|
||||
},
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
plugins: [
|
||||
"github",
|
||||
],
|
||||
extends: [
|
||||
"plugin:github/react",
|
||||
"plugin:github/recommended",
|
||||
"plugin:github/typescript",
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/ban-types": [
|
||||
"error",
|
||||
{
|
||||
// For a full list of the default banned types, see:
|
||||
// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md
|
||||
extendDefaults: true,
|
||||
types: {
|
||||
// Don't complain about the `Function` type in test files. (Default is `true`.)
|
||||
Function: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-shadow": "off",
|
||||
"@typescript-eslint/no-invalid-this": "off",
|
||||
"eqeqeq": "off",
|
||||
"filenames/match-regex": "off",
|
||||
"filenames/match-regexp": "off",
|
||||
"i18n-text/no-en": "off",
|
||||
"import/no-anonymous-default-export": "off",
|
||||
"import/no-dynamic-require": "off",
|
||||
"import/no-mutable-exports": "off",
|
||||
"import/no-namespace": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"no-console": "off",
|
||||
"github/array-foreach": "off",
|
||||
"github/no-then": "off"
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@ import * as ghApiClient from "../../../../src/remote-queries/gh-api/gh-api-clien
|
||||
import * as ghActionsApiClient from "../../../../src/remote-queries/gh-api/gh-actions-api-client";
|
||||
import * as fs from "fs-extra";
|
||||
import { join } from "path";
|
||||
import { Readable } from "stream";
|
||||
import { Response } from "node-fetch";
|
||||
import * as fetchModule from "node-fetch";
|
||||
|
||||
import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-analysis-manager";
|
||||
import { CodeQLCliServer } from "../../../../src/cli";
|
||||
@@ -95,7 +98,6 @@ describe("Variant Analysis Manager", () => {
|
||||
cli = extension.cliServer;
|
||||
app = createMockApp({});
|
||||
variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
app.credentials,
|
||||
cli,
|
||||
extLogger,
|
||||
);
|
||||
@@ -334,32 +336,21 @@ describe("Variant Analysis Manager", () => {
|
||||
});
|
||||
|
||||
describe("autoDownloadVariantAnalysisResult", () => {
|
||||
let arrayBuffer: ArrayBuffer;
|
||||
|
||||
let getVariantAnalysisRepoStub: jest.SpiedFunction<
|
||||
typeof ghApiClient.getVariantAnalysisRepo
|
||||
>;
|
||||
let getVariantAnalysisRepoResultStub: jest.SpiedFunction<
|
||||
typeof ghApiClient.getVariantAnalysisRepoResult
|
||||
typeof fetchModule.default
|
||||
>;
|
||||
|
||||
let repoStatesPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
const sourceFilePath = join(
|
||||
__dirname,
|
||||
"../data/variant-analysis-results.zip",
|
||||
);
|
||||
arrayBuffer = fs.readFileSync(sourceFilePath).buffer;
|
||||
|
||||
getVariantAnalysisRepoStub = jest.spyOn(
|
||||
ghApiClient,
|
||||
"getVariantAnalysisRepo",
|
||||
);
|
||||
getVariantAnalysisRepoResultStub = jest.spyOn(
|
||||
ghApiClient,
|
||||
"getVariantAnalysisRepoResult",
|
||||
);
|
||||
getVariantAnalysisRepoResultStub = jest.spyOn(fetchModule, "default");
|
||||
|
||||
repoStatesPath = join(
|
||||
storagePath,
|
||||
@@ -374,7 +365,6 @@ describe("Variant Analysis Manager", () => {
|
||||
delete dummyRepoTask.artifact_url;
|
||||
|
||||
getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask);
|
||||
getVariantAnalysisRepoResultStub.mockResolvedValue(arrayBuffer);
|
||||
});
|
||||
|
||||
it("should not try to download the result", async () => {
|
||||
@@ -395,7 +385,15 @@ describe("Variant Analysis Manager", () => {
|
||||
dummyRepoTask = createMockVariantAnalysisRepoTask();
|
||||
|
||||
getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask);
|
||||
getVariantAnalysisRepoResultStub.mockResolvedValue(arrayBuffer);
|
||||
|
||||
const sourceFilePath = join(
|
||||
__dirname,
|
||||
"../data/variant-analysis-results.zip",
|
||||
);
|
||||
const fileContents = fs.readFileSync(sourceFilePath);
|
||||
const response = new Response(Readable.from(fileContents));
|
||||
response.size = fileContents.length;
|
||||
getVariantAnalysisRepoResultStub.mockResolvedValue(response);
|
||||
});
|
||||
|
||||
it("should return early if variant analysis is cancelled", async () => {
|
||||
|
||||
@@ -3,19 +3,19 @@ import { CodeQLExtensionInterface } from "../../../../src/extension";
|
||||
import { extLogger } from "../../../../src/common";
|
||||
import * as fs from "fs-extra";
|
||||
import { join, resolve } from "path";
|
||||
import { Readable } from "stream";
|
||||
import { Response, RequestInfo, RequestInit } from "node-fetch";
|
||||
import * as fetchModule from "node-fetch";
|
||||
|
||||
import { VariantAnalysisResultsManager } from "../../../../src/remote-queries/variant-analysis-results-manager";
|
||||
import { CodeQLCliServer } from "../../../../src/cli";
|
||||
import { storagePath } from "../global.helper";
|
||||
import { faker } from "@faker-js/faker";
|
||||
import * as ghApiClient from "../../../../src/remote-queries/gh-api/gh-api-client";
|
||||
import { createMockVariantAnalysisRepositoryTask } from "../../../factories/remote-queries/shared/variant-analysis-repo-tasks";
|
||||
import {
|
||||
VariantAnalysisRepositoryTask,
|
||||
VariantAnalysisScannedRepositoryResult,
|
||||
} from "../../../../src/remote-queries/shared/variant-analysis";
|
||||
import { testCredentialsWithStub } from "../../../factories/authentication";
|
||||
import { Credentials } from "../../../../src/common/authentication";
|
||||
|
||||
jest.setTimeout(10_000);
|
||||
|
||||
@@ -44,7 +44,6 @@ describe(VariantAnalysisResultsManager.name, () => {
|
||||
jest.spyOn(extLogger, "log").mockResolvedValue(undefined);
|
||||
|
||||
variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
testCredentialsWithStub(),
|
||||
cli,
|
||||
extLogger,
|
||||
);
|
||||
@@ -89,35 +88,33 @@ describe(VariantAnalysisResultsManager.name, () => {
|
||||
variantAnalysisId,
|
||||
dummyRepoTask,
|
||||
variantAnalysisStoragePath,
|
||||
() => Promise.resolve(),
|
||||
),
|
||||
).rejects.toThrow("Missing artifact URL");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the artifact_url is present", () => {
|
||||
let arrayBuffer: ArrayBuffer;
|
||||
|
||||
let getVariantAnalysisRepoResultStub: jest.SpiedFunction<
|
||||
typeof ghApiClient.getVariantAnalysisRepoResult
|
||||
typeof fetchModule.default
|
||||
>;
|
||||
let fileContents: Buffer;
|
||||
|
||||
beforeEach(async () => {
|
||||
const sourceFilePath = join(
|
||||
__dirname,
|
||||
"../data/variant-analysis-results.zip",
|
||||
);
|
||||
arrayBuffer = fs.readFileSync(sourceFilePath).buffer;
|
||||
fileContents = fs.readFileSync(sourceFilePath);
|
||||
|
||||
getVariantAnalysisRepoResultStub = jest
|
||||
.spyOn(ghApiClient, "getVariantAnalysisRepoResult")
|
||||
.mockImplementation(
|
||||
(_credentials: Credentials, downloadUrl: string) => {
|
||||
if (downloadUrl === dummyRepoTask.artifactUrl) {
|
||||
return Promise.resolve(arrayBuffer);
|
||||
}
|
||||
return Promise.reject(new Error("Unexpected artifact URL"));
|
||||
},
|
||||
);
|
||||
.spyOn(fetchModule, "default")
|
||||
.mockImplementation((url: RequestInfo, _init?: RequestInit) => {
|
||||
if (url === dummyRepoTask.artifactUrl) {
|
||||
return Promise.resolve(new Response(Readable.from(fileContents)));
|
||||
}
|
||||
return Promise.reject(new Error("Unexpected artifact URL"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should call the API to download the results", async () => {
|
||||
@@ -125,6 +122,7 @@ describe(VariantAnalysisResultsManager.name, () => {
|
||||
variantAnalysisId,
|
||||
dummyRepoTask,
|
||||
variantAnalysisStoragePath,
|
||||
() => Promise.resolve(),
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoResultStub).toHaveBeenCalledTimes(1);
|
||||
@@ -135,6 +133,7 @@ describe(VariantAnalysisResultsManager.name, () => {
|
||||
variantAnalysisId,
|
||||
dummyRepoTask,
|
||||
variantAnalysisStoragePath,
|
||||
() => Promise.resolve(),
|
||||
);
|
||||
|
||||
expect(fs.existsSync(`${repoTaskStorageDirectory}/results.zip`)).toBe(
|
||||
@@ -147,6 +146,7 @@ describe(VariantAnalysisResultsManager.name, () => {
|
||||
variantAnalysisId,
|
||||
dummyRepoTask,
|
||||
variantAnalysisStoragePath,
|
||||
() => Promise.resolve(),
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -154,12 +154,57 @@ describe(VariantAnalysisResultsManager.name, () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should report download progress", async () => {
|
||||
// This generates a "fake" stream which "downloads" the file in 5 chunks,
|
||||
// rather than in 1 chunk. This is used for testing that we actually get
|
||||
// multiple progress reports.
|
||||
async function* generateInParts() {
|
||||
const partLength = fileContents.length / 5;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
yield fileContents.slice(i * partLength, (i + 1) * partLength);
|
||||
}
|
||||
}
|
||||
|
||||
getVariantAnalysisRepoResultStub.mockImplementation(
|
||||
(url: RequestInfo, _init?: RequestInit) => {
|
||||
if (url === dummyRepoTask.artifactUrl) {
|
||||
const response = new Response(Readable.from(generateInParts()));
|
||||
response.headers.set(
|
||||
"Content-Length",
|
||||
fileContents.length.toString(),
|
||||
);
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
return Promise.reject(new Error("Unexpected artifact URL"));
|
||||
},
|
||||
);
|
||||
|
||||
const downloadPercentageChanged = jest
|
||||
.fn()
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
await variantAnalysisResultsManager.download(
|
||||
variantAnalysisId,
|
||||
dummyRepoTask,
|
||||
variantAnalysisStoragePath,
|
||||
downloadPercentageChanged,
|
||||
);
|
||||
|
||||
expect(downloadPercentageChanged).toHaveBeenCalledTimes(5);
|
||||
expect(downloadPercentageChanged).toHaveBeenCalledWith(20);
|
||||
expect(downloadPercentageChanged).toHaveBeenCalledWith(40);
|
||||
expect(downloadPercentageChanged).toHaveBeenCalledWith(60);
|
||||
expect(downloadPercentageChanged).toHaveBeenCalledWith(80);
|
||||
expect(downloadPercentageChanged).toHaveBeenCalledWith(100);
|
||||
});
|
||||
|
||||
describe("isVariantAnalysisRepoDownloaded", () => {
|
||||
it("should return true once results are downloaded", async () => {
|
||||
await variantAnalysisResultsManager.download(
|
||||
variantAnalysisId,
|
||||
dummyRepoTask,
|
||||
variantAnalysisStoragePath,
|
||||
() => Promise.resolve(),
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -185,7 +230,6 @@ describe(VariantAnalysisResultsManager.name, () => {
|
||||
|
||||
beforeEach(() => {
|
||||
variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
testCredentialsWithStub(),
|
||||
cli,
|
||||
extLogger,
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
|
||||
import { CodeQLExtensionInterface } from "../../../../src/extension";
|
||||
import { MockGitHubApiServer } from "../../../../src/mocks/mock-gh-api-server";
|
||||
import { mockConfiguration } from "../../utils/configuration-helpers";
|
||||
|
||||
jest.setTimeout(30_000);
|
||||
|
||||
@@ -35,50 +36,17 @@ describe("Variant Analysis Submission Integration", () => {
|
||||
let showErrorMessageSpy: jest.SpiedFunction<typeof window.showErrorMessage>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const originalGetConfiguration = workspace.getConfiguration;
|
||||
|
||||
jest
|
||||
.spyOn(workspace, "getConfiguration")
|
||||
.mockImplementation((section, scope) => {
|
||||
const configuration = originalGetConfiguration(section, scope);
|
||||
|
||||
return {
|
||||
get(key: string, defaultValue?: unknown) {
|
||||
if (section === "codeQL.variantAnalysis" && key === "liveResults") {
|
||||
return true;
|
||||
}
|
||||
if (section === "codeQL" && key == "canary") {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
section === "codeQL.variantAnalysis" &&
|
||||
key === "controllerRepo"
|
||||
) {
|
||||
return "github/vscode-codeql";
|
||||
}
|
||||
return configuration.get(key, defaultValue);
|
||||
},
|
||||
has(key: string) {
|
||||
return configuration.has(key);
|
||||
},
|
||||
inspect(key: string) {
|
||||
return configuration.inspect(key);
|
||||
},
|
||||
update(
|
||||
key: string,
|
||||
value: unknown,
|
||||
configurationTarget?: boolean,
|
||||
overrideInLanguage?: boolean,
|
||||
) {
|
||||
return configuration.update(
|
||||
key,
|
||||
value,
|
||||
configurationTarget,
|
||||
overrideInLanguage,
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
mockConfiguration({
|
||||
values: {
|
||||
codeQL: {
|
||||
canary: true,
|
||||
},
|
||||
"codeQL.variantAnalysis": {
|
||||
liveResults: true,
|
||||
controllerRepo: "github/vscode-codeql",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
jest.spyOn(authentication, "getSession").mockResolvedValue({
|
||||
id: "test",
|
||||
|
||||
@@ -13,6 +13,7 @@ import { DbTreeViewItem } from "../../../../src/databases/ui/db-tree-view-item";
|
||||
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";
|
||||
import { createMockExtensionContext } from "../../../factories/extension-context";
|
||||
import { createDbConfig } from "../../../factories/db-config-factories";
|
||||
import { mockConfiguration } from "../../utils/configuration-helpers";
|
||||
|
||||
describe("db panel rendering nodes", () => {
|
||||
const workspaceStoragePath = join(__dirname, "test-workspace-storage");
|
||||
@@ -48,238 +49,281 @@ describe("db panel rendering nodes", () => {
|
||||
await remove(workspaceStoragePath);
|
||||
});
|
||||
|
||||
it("should render default remote nodes when the config is empty", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig();
|
||||
|
||||
await saveDbConfig(dbConfig);
|
||||
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
const items = dbTreeItems!;
|
||||
expect(items.length).toBe(3);
|
||||
|
||||
checkRemoteSystemDefinedListItem(items[0], 10);
|
||||
checkRemoteSystemDefinedListItem(items[1], 100);
|
||||
checkRemoteSystemDefinedListItem(items[2], 1000);
|
||||
});
|
||||
|
||||
it("should render remote repository list nodes", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
remoteLists: [
|
||||
{
|
||||
name: "my-list-1",
|
||||
repositories: ["owner1/repo1", "owner1/repo2"],
|
||||
describe("when controller repo is not set", () => {
|
||||
mockConfiguration({
|
||||
values: {
|
||||
"codeQL.variantAnalysis": {
|
||||
controllerRepo: undefined,
|
||||
},
|
||||
{
|
||||
name: "my-list-2",
|
||||
repositories: ["owner1/repo1", "owner2/repo1", "owner2/repo2"],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await saveDbConfig(dbConfig);
|
||||
it("should not have any items", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
remoteLists: [
|
||||
{
|
||||
name: "my-list-1",
|
||||
repositories: ["owner1/repo1", "owner1/repo2"],
|
||||
},
|
||||
{
|
||||
name: "my-list-2",
|
||||
repositories: ["owner2/repo1", "owner2/repo2"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
await saveDbConfig(dbConfig);
|
||||
|
||||
const systemDefinedListItems = dbTreeItems!.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.RemoteSystemDefinedList,
|
||||
);
|
||||
expect(systemDefinedListItems.length).toBe(3);
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
|
||||
const userDefinedListItems = dbTreeItems!.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.RemoteUserDefinedList,
|
||||
);
|
||||
expect(userDefinedListItems.length).toBe(2);
|
||||
checkUserDefinedListItem(userDefinedListItems[0], "my-list-1", [
|
||||
"owner1/repo1",
|
||||
"owner1/repo2",
|
||||
]);
|
||||
checkUserDefinedListItem(userDefinedListItems[1], "my-list-2", [
|
||||
"owner1/repo1",
|
||||
"owner2/repo1",
|
||||
"owner2/repo2",
|
||||
]);
|
||||
expect(dbTreeItems).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should render owner list nodes", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
remoteOwners: ["owner1", "owner2"],
|
||||
describe("when controller repo is set", () => {
|
||||
beforeEach(() => {
|
||||
mockConfiguration({
|
||||
values: {
|
||||
"codeQL.variantAnalysis": {
|
||||
controllerRepo: "github/codeql",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await saveDbConfig(dbConfig);
|
||||
it("should render default remote nodes when the config is empty", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig();
|
||||
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
expect(dbTreeItems?.length).toBe(5);
|
||||
await saveDbConfig(dbConfig);
|
||||
|
||||
const ownerListItems = dbTreeItems!.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.RemoteOwner,
|
||||
);
|
||||
expect(ownerListItems.length).toBe(2);
|
||||
checkOwnerItem(ownerListItems[0], "owner1");
|
||||
checkOwnerItem(ownerListItems[1], "owner2");
|
||||
});
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
|
||||
it("should render repository nodes", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
remoteRepos: ["owner1/repo1", "owner1/repo2"],
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
const items = dbTreeItems!;
|
||||
expect(items.length).toBe(3);
|
||||
|
||||
checkRemoteSystemDefinedListItem(items[0], 10);
|
||||
checkRemoteSystemDefinedListItem(items[1], 100);
|
||||
checkRemoteSystemDefinedListItem(items[2], 1000);
|
||||
});
|
||||
|
||||
await saveDbConfig(dbConfig);
|
||||
it("should render remote repository list nodes", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
remoteLists: [
|
||||
{
|
||||
name: "my-list-1",
|
||||
repositories: ["owner1/repo1", "owner1/repo2"],
|
||||
},
|
||||
{
|
||||
name: "my-list-2",
|
||||
repositories: ["owner1/repo1", "owner2/repo1", "owner2/repo2"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
expect(dbTreeItems!.length).toBe(5);
|
||||
await saveDbConfig(dbConfig);
|
||||
|
||||
const repoItems = dbTreeItems!.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.RemoteRepo,
|
||||
);
|
||||
expect(repoItems.length).toBe(2);
|
||||
checkRemoteRepoItem(repoItems[0], "owner1/repo1");
|
||||
checkRemoteRepoItem(repoItems[1], "owner1/repo2");
|
||||
});
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
|
||||
it.skip("should render local list nodes", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
localLists: [
|
||||
const systemDefinedListItems = dbTreeItems!.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.RemoteSystemDefinedList,
|
||||
);
|
||||
expect(systemDefinedListItems.length).toBe(3);
|
||||
|
||||
const userDefinedListItems = dbTreeItems!.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.RemoteUserDefinedList,
|
||||
);
|
||||
expect(userDefinedListItems.length).toBe(2);
|
||||
checkUserDefinedListItem(userDefinedListItems[0], "my-list-1", [
|
||||
"owner1/repo1",
|
||||
"owner1/repo2",
|
||||
]);
|
||||
checkUserDefinedListItem(userDefinedListItems[1], "my-list-2", [
|
||||
"owner1/repo1",
|
||||
"owner2/repo1",
|
||||
"owner2/repo2",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should render owner list nodes", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
remoteOwners: ["owner1", "owner2"],
|
||||
});
|
||||
|
||||
await saveDbConfig(dbConfig);
|
||||
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
expect(dbTreeItems?.length).toBe(5);
|
||||
|
||||
const ownerListItems = dbTreeItems!.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.RemoteOwner,
|
||||
);
|
||||
expect(ownerListItems.length).toBe(2);
|
||||
checkOwnerItem(ownerListItems[0], "owner1");
|
||||
checkOwnerItem(ownerListItems[1], "owner2");
|
||||
});
|
||||
|
||||
it("should render repository nodes", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
remoteRepos: ["owner1/repo1", "owner1/repo2"],
|
||||
});
|
||||
|
||||
await saveDbConfig(dbConfig);
|
||||
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
expect(dbTreeItems!.length).toBe(5);
|
||||
|
||||
const repoItems = dbTreeItems!.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.RemoteRepo,
|
||||
);
|
||||
expect(repoItems.length).toBe(2);
|
||||
checkRemoteRepoItem(repoItems[0], "owner1/repo1");
|
||||
checkRemoteRepoItem(repoItems[1], "owner1/repo2");
|
||||
});
|
||||
|
||||
it.skip("should render local list nodes", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
localLists: [
|
||||
{
|
||||
name: "my-list-1",
|
||||
databases: [
|
||||
{
|
||||
name: "db1",
|
||||
dateAdded: 1668428293677,
|
||||
language: "cpp",
|
||||
storagePath: "/path/to/db1/",
|
||||
},
|
||||
{
|
||||
name: "db2",
|
||||
dateAdded: 1668428472731,
|
||||
language: "cpp",
|
||||
storagePath: "/path/to/db2/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "my-list-2",
|
||||
databases: [
|
||||
{
|
||||
name: "db3",
|
||||
dateAdded: 1668428472731,
|
||||
language: "ruby",
|
||||
storagePath: "/path/to/db3/",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await saveDbConfig(dbConfig);
|
||||
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
|
||||
const localRootNode = dbTreeItems?.find(
|
||||
(i) => i.dbItem?.kind === DbItemKind.RootLocal,
|
||||
);
|
||||
expect(localRootNode).toBeTruthy();
|
||||
|
||||
expect(localRootNode!.dbItem).toBeTruthy();
|
||||
expect(localRootNode!.collapsibleState).toBe(
|
||||
TreeItemCollapsibleState.Collapsed,
|
||||
);
|
||||
expect(localRootNode!.children).toBeTruthy();
|
||||
expect(localRootNode!.children.length).toBe(2);
|
||||
|
||||
const localListItems = localRootNode!.children.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.LocalList,
|
||||
);
|
||||
expect(localListItems.length).toBe(2);
|
||||
checkLocalListItem(localListItems[0], "my-list-1", [
|
||||
{
|
||||
name: "my-list-1",
|
||||
databases: [
|
||||
{
|
||||
name: "db1",
|
||||
dateAdded: 1668428293677,
|
||||
language: "cpp",
|
||||
storagePath: "/path/to/db1/",
|
||||
},
|
||||
{
|
||||
name: "db2",
|
||||
dateAdded: 1668428472731,
|
||||
language: "cpp",
|
||||
storagePath: "/path/to/db2/",
|
||||
},
|
||||
],
|
||||
kind: DbItemKind.LocalDatabase,
|
||||
databaseName: "db1",
|
||||
dateAdded: 1668428293677,
|
||||
language: "cpp",
|
||||
storagePath: "/path/to/db1/",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "my-list-2",
|
||||
databases: [
|
||||
{
|
||||
name: "db3",
|
||||
dateAdded: 1668428472731,
|
||||
language: "ruby",
|
||||
storagePath: "/path/to/db3/",
|
||||
},
|
||||
],
|
||||
kind: DbItemKind.LocalDatabase,
|
||||
databaseName: "db2",
|
||||
dateAdded: 1668428472731,
|
||||
language: "cpp",
|
||||
storagePath: "/path/to/db2/",
|
||||
selected: false,
|
||||
},
|
||||
],
|
||||
]);
|
||||
checkLocalListItem(localListItems[1], "my-list-2", [
|
||||
{
|
||||
kind: DbItemKind.LocalDatabase,
|
||||
databaseName: "db3",
|
||||
dateAdded: 1668428472731,
|
||||
language: "ruby",
|
||||
storagePath: "/path/to/db3/",
|
||||
selected: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
await saveDbConfig(dbConfig);
|
||||
it.skip("should render local database nodes", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
localDbs: [
|
||||
{
|
||||
name: "db1",
|
||||
dateAdded: 1668428293677,
|
||||
language: "csharp",
|
||||
storagePath: "/path/to/db1/",
|
||||
},
|
||||
{
|
||||
name: "db2",
|
||||
dateAdded: 1668428472731,
|
||||
language: "go",
|
||||
storagePath: "/path/to/db2/",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
await saveDbConfig(dbConfig);
|
||||
|
||||
const localRootNode = dbTreeItems?.find(
|
||||
(i) => i.dbItem?.kind === DbItemKind.RootLocal,
|
||||
);
|
||||
expect(localRootNode).toBeTruthy();
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
|
||||
expect(localRootNode!.dbItem).toBeTruthy();
|
||||
expect(localRootNode!.collapsibleState).toBe(
|
||||
TreeItemCollapsibleState.Collapsed,
|
||||
);
|
||||
expect(localRootNode!.children).toBeTruthy();
|
||||
expect(localRootNode!.children.length).toBe(2);
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
const localRootNode = dbTreeItems?.find(
|
||||
(i) => i.dbItem?.kind === DbItemKind.RootLocal,
|
||||
);
|
||||
expect(localRootNode).toBeTruthy();
|
||||
|
||||
const localListItems = localRootNode!.children.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.LocalList,
|
||||
);
|
||||
expect(localListItems.length).toBe(2);
|
||||
checkLocalListItem(localListItems[0], "my-list-1", [
|
||||
{
|
||||
expect(localRootNode!.dbItem).toBeTruthy();
|
||||
expect(localRootNode!.collapsibleState).toBe(
|
||||
TreeItemCollapsibleState.Collapsed,
|
||||
);
|
||||
expect(localRootNode!.children).toBeTruthy();
|
||||
expect(localRootNode!.children.length).toBe(2);
|
||||
|
||||
const localDatabaseItems = localRootNode!.children.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.LocalDatabase,
|
||||
);
|
||||
expect(localDatabaseItems.length).toBe(2);
|
||||
checkLocalDatabaseItem(localDatabaseItems[0], {
|
||||
kind: DbItemKind.LocalDatabase,
|
||||
databaseName: "db1",
|
||||
dateAdded: 1668428293677,
|
||||
language: "cpp",
|
||||
language: "csharp",
|
||||
storagePath: "/path/to/db1/",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
});
|
||||
checkLocalDatabaseItem(localDatabaseItems[1], {
|
||||
kind: DbItemKind.LocalDatabase,
|
||||
databaseName: "db2",
|
||||
dateAdded: 1668428472731,
|
||||
language: "cpp",
|
||||
language: "go",
|
||||
storagePath: "/path/to/db2/",
|
||||
selected: false,
|
||||
},
|
||||
]);
|
||||
checkLocalListItem(localListItems[1], "my-list-2", [
|
||||
{
|
||||
kind: DbItemKind.LocalDatabase,
|
||||
databaseName: "db3",
|
||||
dateAdded: 1668428472731,
|
||||
language: "ruby",
|
||||
storagePath: "/path/to/db3/",
|
||||
selected: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip("should render local database nodes", async () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
localDbs: [
|
||||
{
|
||||
name: "db1",
|
||||
dateAdded: 1668428293677,
|
||||
language: "csharp",
|
||||
storagePath: "/path/to/db1/",
|
||||
},
|
||||
{
|
||||
name: "db2",
|
||||
dateAdded: 1668428472731,
|
||||
language: "go",
|
||||
storagePath: "/path/to/db2/",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await saveDbConfig(dbConfig);
|
||||
|
||||
const dbTreeItems = await dbTreeDataProvider.getChildren();
|
||||
|
||||
expect(dbTreeItems).toBeTruthy();
|
||||
const localRootNode = dbTreeItems?.find(
|
||||
(i) => i.dbItem?.kind === DbItemKind.RootLocal,
|
||||
);
|
||||
expect(localRootNode).toBeTruthy();
|
||||
|
||||
expect(localRootNode!.dbItem).toBeTruthy();
|
||||
expect(localRootNode!.collapsibleState).toBe(
|
||||
TreeItemCollapsibleState.Collapsed,
|
||||
);
|
||||
expect(localRootNode!.children).toBeTruthy();
|
||||
expect(localRootNode!.children.length).toBe(2);
|
||||
|
||||
const localDatabaseItems = localRootNode!.children.filter(
|
||||
(item) => item.dbItem?.kind === DbItemKind.LocalDatabase,
|
||||
);
|
||||
expect(localDatabaseItems.length).toBe(2);
|
||||
checkLocalDatabaseItem(localDatabaseItems[0], {
|
||||
kind: DbItemKind.LocalDatabase,
|
||||
databaseName: "db1",
|
||||
dateAdded: 1668428293677,
|
||||
language: "csharp",
|
||||
storagePath: "/path/to/db1/",
|
||||
selected: false,
|
||||
});
|
||||
checkLocalDatabaseItem(localDatabaseItems[1], {
|
||||
kind: DbItemKind.LocalDatabase,
|
||||
databaseName: "db2",
|
||||
dateAdded: 1668428472731,
|
||||
language: "go",
|
||||
storagePath: "/path/to/db2/",
|
||||
selected: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DbTreeViewItem } from "../../../../src/databases/ui/db-tree-view-item";
|
||||
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";
|
||||
import { createMockExtensionContext } from "../../../factories/extension-context";
|
||||
import { createDbConfig } from "../../../factories/db-config-factories";
|
||||
import { mockConfiguration } from "../../utils/configuration-helpers";
|
||||
|
||||
describe("db panel", () => {
|
||||
const workspaceStoragePath = join(__dirname, "test-workspace-storage");
|
||||
@@ -38,6 +39,14 @@ describe("db panel", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await ensureDir(workspaceStoragePath);
|
||||
|
||||
mockConfiguration({
|
||||
values: {
|
||||
"codeQL.variantAnalysis": {
|
||||
controllerRepo: "github/codeql",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";
|
||||
import { createMockExtensionContext } from "../../../factories/extension-context";
|
||||
import { createDbConfig } from "../../../factories/db-config-factories";
|
||||
import { mockConfiguration } from "../../utils/configuration-helpers";
|
||||
|
||||
describe("db panel selection", () => {
|
||||
const workspaceStoragePath = join(__dirname, "test-workspace-storage");
|
||||
@@ -44,6 +45,14 @@ describe("db panel selection", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await ensureDir(workspaceStoragePath);
|
||||
|
||||
mockConfiguration({
|
||||
values: {
|
||||
"codeQL.variantAnalysis": {
|
||||
controllerRepo: "github/codeql",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { readdirSync, mkdirSync, writeFileSync } from "fs-extra";
|
||||
import { join } from "path";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { extLogger } from "../../../../src/common";
|
||||
import { registerQueryHistoryScrubber } from "../../../../src/query-history/query-history-scrubber";
|
||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
||||
import {
|
||||
QueryHistoryConfig,
|
||||
@@ -11,13 +9,6 @@ import {
|
||||
} from "../../../../src/config";
|
||||
import { LocalQueryInfo } from "../../../../src/query-results";
|
||||
import { DatabaseManager } from "../../../../src/databases";
|
||||
import { dirSync } from "tmp-promise";
|
||||
import {
|
||||
ONE_DAY_IN_MS,
|
||||
ONE_HOUR_IN_MS,
|
||||
THREE_HOURS_IN_MS,
|
||||
TWO_HOURS_IN_MS,
|
||||
} from "../../../../src/pure/time";
|
||||
import { tmpDir } from "../../../../src/helpers";
|
||||
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
||||
import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager";
|
||||
@@ -1468,189 +1459,6 @@ describe("QueryHistoryManager", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("query history scrubber", () => {
|
||||
const now = Date.now();
|
||||
|
||||
let deregister: vscode.Disposable | undefined;
|
||||
let mockCtx: vscode.ExtensionContext;
|
||||
let runCount = 0;
|
||||
|
||||
// We don't want our times to align exactly with the hour,
|
||||
// so we can better mimic real life
|
||||
const LESS_THAN_ONE_DAY = ONE_DAY_IN_MS - 1000;
|
||||
const tmpDir = dirSync({
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers({
|
||||
doNotFake: ["setTimeout"],
|
||||
now,
|
||||
});
|
||||
|
||||
mockCtx = {
|
||||
globalState: {
|
||||
lastScrubTime: now,
|
||||
get(key: string) {
|
||||
if (key !== "lastScrubTime") {
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
}
|
||||
return this.lastScrubTime;
|
||||
},
|
||||
async update(key: string, value: any) {
|
||||
if (key !== "lastScrubTime") {
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
}
|
||||
this.lastScrubTime = value;
|
||||
},
|
||||
},
|
||||
} as any as vscode.ExtensionContext;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (deregister) {
|
||||
deregister.dispose();
|
||||
deregister = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
it("should not throw an error when the query directory does not exist", async () => {
|
||||
registerScrubber("idontexist");
|
||||
|
||||
jest.advanceTimersByTime(ONE_HOUR_IN_MS);
|
||||
await wait();
|
||||
// "Should not have called the scrubber"
|
||||
expect(runCount).toBe(0);
|
||||
|
||||
jest.advanceTimersByTime(ONE_HOUR_IN_MS - 1);
|
||||
await wait();
|
||||
// "Should not have called the scrubber"
|
||||
expect(runCount).toBe(0);
|
||||
|
||||
jest.advanceTimersByTime(1);
|
||||
await wait();
|
||||
// "Should have called the scrubber once"
|
||||
expect(runCount).toBe(1);
|
||||
|
||||
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
||||
await wait();
|
||||
// "Should have called the scrubber a second time"
|
||||
expect(runCount).toBe(2);
|
||||
|
||||
expect((mockCtx.globalState as any).lastScrubTime).toBe(
|
||||
now + TWO_HOURS_IN_MS * 2,
|
||||
);
|
||||
});
|
||||
|
||||
it("should scrub directories", async () => {
|
||||
// create two query directories that are right around the cut off time
|
||||
const queryDir = createMockQueryDir(
|
||||
ONE_HOUR_IN_MS,
|
||||
TWO_HOURS_IN_MS,
|
||||
THREE_HOURS_IN_MS,
|
||||
);
|
||||
registerScrubber(queryDir);
|
||||
|
||||
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
||||
await wait();
|
||||
|
||||
// should have deleted only the invalid locations
|
||||
expectDirectories(
|
||||
queryDir,
|
||||
toQueryDirName(ONE_HOUR_IN_MS),
|
||||
toQueryDirName(TWO_HOURS_IN_MS),
|
||||
toQueryDirName(THREE_HOURS_IN_MS),
|
||||
);
|
||||
|
||||
jest.advanceTimersByTime(LESS_THAN_ONE_DAY);
|
||||
await wait();
|
||||
|
||||
// nothing should have happened...yet
|
||||
expectDirectories(
|
||||
queryDir,
|
||||
toQueryDirName(ONE_HOUR_IN_MS),
|
||||
toQueryDirName(TWO_HOURS_IN_MS),
|
||||
toQueryDirName(THREE_HOURS_IN_MS),
|
||||
);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
await wait();
|
||||
|
||||
// should have deleted the two older directories
|
||||
// even though they have different time stamps,
|
||||
// they both expire during the same scrubbing period
|
||||
expectDirectories(queryDir, toQueryDirName(THREE_HOURS_IN_MS));
|
||||
|
||||
// Wait until the next scrub time and the final directory is deleted
|
||||
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
||||
await wait();
|
||||
|
||||
// should have deleted everything
|
||||
expectDirectories(queryDir);
|
||||
});
|
||||
|
||||
function expectDirectories(queryDir: string, ...dirNames: string[]) {
|
||||
const files = readdirSync(queryDir);
|
||||
expect(files.sort()).toEqual(dirNames.sort());
|
||||
}
|
||||
|
||||
function createMockQueryDir(...timestamps: number[]) {
|
||||
const dir = tmpDir.name;
|
||||
const queryDir = join(dir, "query");
|
||||
// create qyuery directory and fill it with some query directories
|
||||
mkdirSync(queryDir);
|
||||
|
||||
// create an invalid file
|
||||
const invalidFile = join(queryDir, "invalid.txt");
|
||||
writeFileSync(invalidFile, "invalid");
|
||||
|
||||
// create a directory without a timestamp file
|
||||
const noTimestampDir = join(queryDir, "noTimestampDir");
|
||||
mkdirSync(noTimestampDir);
|
||||
writeFileSync(join(noTimestampDir, "invalid.txt"), "invalid");
|
||||
|
||||
// create a directory with a timestamp file, but is invalid
|
||||
const invalidTimestampDir = join(queryDir, "invalidTimestampDir");
|
||||
mkdirSync(invalidTimestampDir);
|
||||
writeFileSync(join(invalidTimestampDir, "timestamp"), "invalid");
|
||||
|
||||
// create a directories with a valid timestamp files from the args
|
||||
timestamps.forEach((timestamp) => {
|
||||
const dir = join(queryDir, toQueryDirName(timestamp));
|
||||
mkdirSync(dir);
|
||||
writeFileSync(join(dir, "timestamp"), `${now + timestamp}`);
|
||||
});
|
||||
|
||||
return queryDir;
|
||||
}
|
||||
|
||||
function toQueryDirName(timestamp: number) {
|
||||
return `query-${timestamp}`;
|
||||
}
|
||||
|
||||
function registerScrubber(dir: string) {
|
||||
deregister = registerQueryHistoryScrubber(
|
||||
ONE_HOUR_IN_MS,
|
||||
TWO_HOURS_IN_MS,
|
||||
LESS_THAN_ONE_DAY,
|
||||
dir,
|
||||
{
|
||||
removeDeletedQueries: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
} as QueryHistoryManager,
|
||||
mockCtx,
|
||||
{
|
||||
increment: () => runCount++,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function wait(ms = 500) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
});
|
||||
|
||||
async function createMockQueryHistory(
|
||||
allHistory: QueryHistoryInfo[],
|
||||
credentials?: Credentials,
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import { readdirSync, mkdirSync, writeFileSync } from "fs-extra";
|
||||
import { join } from "path";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { extLogger } from "../../../../src/common";
|
||||
import { registerQueryHistoryScrubber } from "../../../../src/query-history/query-history-scrubber";
|
||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
||||
import { dirSync } from "tmp-promise";
|
||||
import {
|
||||
ONE_DAY_IN_MS,
|
||||
ONE_HOUR_IN_MS,
|
||||
THREE_HOURS_IN_MS,
|
||||
TWO_HOURS_IN_MS,
|
||||
} from "../../../../src/pure/time";
|
||||
|
||||
describe("query history scrubber", () => {
|
||||
const now = Date.now();
|
||||
|
||||
let deregister: vscode.Disposable | undefined;
|
||||
let mockCtx: vscode.ExtensionContext;
|
||||
let runCount = 0;
|
||||
|
||||
// We don't want our times to align exactly with the hour,
|
||||
// so we can better mimic real life
|
||||
const LESS_THAN_ONE_DAY = ONE_DAY_IN_MS - 1000;
|
||||
const tmpDir = dirSync({
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(extLogger, "log").mockResolvedValue(undefined);
|
||||
|
||||
jest.useFakeTimers({
|
||||
doNotFake: ["setTimeout"],
|
||||
now,
|
||||
});
|
||||
|
||||
mockCtx = {
|
||||
globalState: {
|
||||
lastScrubTime: now,
|
||||
get(key: string) {
|
||||
if (key !== "lastScrubTime") {
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
}
|
||||
return this.lastScrubTime;
|
||||
},
|
||||
async update(key: string, value: any) {
|
||||
if (key !== "lastScrubTime") {
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
}
|
||||
this.lastScrubTime = value;
|
||||
},
|
||||
},
|
||||
} as any as vscode.ExtensionContext;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (deregister) {
|
||||
deregister.dispose();
|
||||
deregister = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
it("should not throw an error when the query directory does not exist", async () => {
|
||||
registerScrubber("idontexist");
|
||||
|
||||
jest.advanceTimersByTime(ONE_HOUR_IN_MS);
|
||||
await wait();
|
||||
// "Should not have called the scrubber"
|
||||
expect(runCount).toBe(0);
|
||||
|
||||
jest.advanceTimersByTime(ONE_HOUR_IN_MS - 1);
|
||||
await wait();
|
||||
// "Should not have called the scrubber"
|
||||
expect(runCount).toBe(0);
|
||||
|
||||
jest.advanceTimersByTime(1);
|
||||
await wait();
|
||||
// "Should have called the scrubber once"
|
||||
expect(runCount).toBe(1);
|
||||
|
||||
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
||||
await wait();
|
||||
// "Should have called the scrubber a second time"
|
||||
expect(runCount).toBe(2);
|
||||
|
||||
expect((mockCtx.globalState as any).lastScrubTime).toBe(
|
||||
now + TWO_HOURS_IN_MS * 2,
|
||||
);
|
||||
});
|
||||
|
||||
it("should scrub directories", async () => {
|
||||
// create two query directories that are right around the cut off time
|
||||
const queryDir = createMockQueryDir(
|
||||
ONE_HOUR_IN_MS,
|
||||
TWO_HOURS_IN_MS,
|
||||
THREE_HOURS_IN_MS,
|
||||
);
|
||||
registerScrubber(queryDir);
|
||||
|
||||
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
||||
await wait();
|
||||
|
||||
// should have deleted only the invalid locations
|
||||
expectDirectories(
|
||||
queryDir,
|
||||
toQueryDirName(ONE_HOUR_IN_MS),
|
||||
toQueryDirName(TWO_HOURS_IN_MS),
|
||||
toQueryDirName(THREE_HOURS_IN_MS),
|
||||
);
|
||||
|
||||
jest.advanceTimersByTime(LESS_THAN_ONE_DAY);
|
||||
await wait();
|
||||
|
||||
// nothing should have happened...yet
|
||||
expectDirectories(
|
||||
queryDir,
|
||||
toQueryDirName(ONE_HOUR_IN_MS),
|
||||
toQueryDirName(TWO_HOURS_IN_MS),
|
||||
toQueryDirName(THREE_HOURS_IN_MS),
|
||||
);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
await wait();
|
||||
|
||||
// should have deleted the two older directories
|
||||
// even though they have different time stamps,
|
||||
// they both expire during the same scrubbing period
|
||||
expectDirectories(queryDir, toQueryDirName(THREE_HOURS_IN_MS));
|
||||
|
||||
// Wait until the next scrub time and the final directory is deleted
|
||||
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
||||
await wait();
|
||||
|
||||
// should have deleted everything
|
||||
expectDirectories(queryDir);
|
||||
});
|
||||
|
||||
function expectDirectories(queryDir: string, ...dirNames: string[]) {
|
||||
const files = readdirSync(queryDir);
|
||||
expect(files.sort()).toEqual(dirNames.sort());
|
||||
}
|
||||
|
||||
function createMockQueryDir(...timestamps: number[]) {
|
||||
const dir = tmpDir.name;
|
||||
const queryDir = join(dir, "query");
|
||||
// create qyuery directory and fill it with some query directories
|
||||
mkdirSync(queryDir);
|
||||
|
||||
// create an invalid file
|
||||
const invalidFile = join(queryDir, "invalid.txt");
|
||||
writeFileSync(invalidFile, "invalid");
|
||||
|
||||
// create a directory without a timestamp file
|
||||
const noTimestampDir = join(queryDir, "noTimestampDir");
|
||||
mkdirSync(noTimestampDir);
|
||||
writeFileSync(join(noTimestampDir, "invalid.txt"), "invalid");
|
||||
|
||||
// create a directory with a timestamp file, but is invalid
|
||||
const invalidTimestampDir = join(queryDir, "invalidTimestampDir");
|
||||
mkdirSync(invalidTimestampDir);
|
||||
writeFileSync(join(invalidTimestampDir, "timestamp"), "invalid");
|
||||
|
||||
// create a directories with a valid timestamp files from the args
|
||||
timestamps.forEach((timestamp) => {
|
||||
const dir = join(queryDir, toQueryDirName(timestamp));
|
||||
mkdirSync(dir);
|
||||
writeFileSync(join(dir, "timestamp"), `${now + timestamp}`);
|
||||
});
|
||||
|
||||
return queryDir;
|
||||
}
|
||||
|
||||
function toQueryDirName(timestamp: number) {
|
||||
return `query-${timestamp}`;
|
||||
}
|
||||
|
||||
function registerScrubber(dir: string) {
|
||||
deregister = registerQueryHistoryScrubber(
|
||||
ONE_HOUR_IN_MS,
|
||||
TWO_HOURS_IN_MS,
|
||||
LESS_THAN_ONE_DAY,
|
||||
dir,
|
||||
{
|
||||
removeDeletedQueries: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
} as QueryHistoryManager,
|
||||
mockCtx,
|
||||
{
|
||||
increment: () => runCount++,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function wait(ms = 500) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "../../../src/telemetry";
|
||||
import { UserCancellationException } from "../../../src/commandRunner";
|
||||
import { ENABLE_TELEMETRY } from "../../../src/config";
|
||||
import * as Config from "../../../src/config";
|
||||
import { createMockExtensionContext } from "./index";
|
||||
|
||||
// setting preferences can trigger lots of background activity
|
||||
@@ -388,6 +389,37 @@ describe("telemetry reporting", () => {
|
||||
expect(showInformationMessageSpy).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("when new telementry is not enabled", () => {
|
||||
it("should not send a telementry event", async () => {
|
||||
await telemetryListener.initialize();
|
||||
|
||||
telemetryListener.sendUIInteraction("test");
|
||||
|
||||
expect(sendTelemetryEventSpy).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when new telementry is enabled", () => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(Config, "newTelemetryEnabled").mockReturnValue(true);
|
||||
});
|
||||
|
||||
it("should not send a telementry event", async () => {
|
||||
await telemetryListener.initialize();
|
||||
|
||||
telemetryListener.sendUIInteraction("test");
|
||||
|
||||
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
|
||||
"ui-interaction",
|
||||
{
|
||||
name: "test",
|
||||
isCanary,
|
||||
},
|
||||
{},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
async function enableTelemetry(section: string, value: boolean | undefined) {
|
||||
await workspace
|
||||
.getConfiguration(section)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { workspace } from "vscode";
|
||||
|
||||
type MockConfigurationConfig = {
|
||||
values: {
|
||||
[section: string]: {
|
||||
[scope: string]: any | (() => any);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export function mockConfiguration(config: MockConfigurationConfig) {
|
||||
const originalGetConfiguration = workspace.getConfiguration;
|
||||
|
||||
jest
|
||||
.spyOn(workspace, "getConfiguration")
|
||||
.mockImplementation((section, scope) => {
|
||||
const configuration = originalGetConfiguration(section, scope);
|
||||
|
||||
return {
|
||||
get(key: string, defaultValue?: unknown) {
|
||||
if (
|
||||
section &&
|
||||
config.values[section] &&
|
||||
config.values[section][key]
|
||||
) {
|
||||
const value = config.values[section][key];
|
||||
return typeof value === "function" ? value() : value;
|
||||
}
|
||||
|
||||
return configuration.get(key, defaultValue);
|
||||
},
|
||||
has(key: string) {
|
||||
return configuration.has(key);
|
||||
},
|
||||
inspect(key: string) {
|
||||
return configuration.inspect(key);
|
||||
},
|
||||
update(
|
||||
key: string,
|
||||
value: unknown,
|
||||
configurationTarget?: boolean,
|
||||
overrideInLanguage?: boolean,
|
||||
) {
|
||||
return configuration.update(
|
||||
key,
|
||||
value,
|
||||
configurationTarget,
|
||||
overrideInLanguage,
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
4
extensions/ql-vscode/tsconfig.lint.json
Normal file
4
extensions/ql-vscode/tsconfig.lint.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src/**/*.ts", "**/.eslintrc.js", "jest.config.js"]
|
||||
}
|
||||
Reference in New Issue
Block a user