Skip to content

Commit

Permalink
Prompt user for database download on startup
Browse files Browse the repository at this point in the history
  • Loading branch information
koesie10 committed Nov 14, 2023
1 parent 019195b commit b190c35
Show file tree
Hide file tree
Showing 9 changed files with 649 additions and 33 deletions.
29 changes: 27 additions & 2 deletions extensions/ql-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,33 @@
},
{
"type": "object",
"title": "Log insights",
"title": "GitHub Databases",
"order": 8,
"properties": {
"codeQL.githubDatabase.enable": {
"type": "boolean",
"default": false,
"markdownDescription": "Enable automatic detection of GitHub databases."
},
"codeQL.githubDatabase.download": {
"type": "string",
"default": "ask",
"enum": [
"ask",
"never"
],
"enumDescriptions": [
"Ask to download a GitHub database when a workspace is opened.",
"Never download a GitHub databases when a workspace is opened."
],
"description": "Ask to download a GitHub database when a workspace is opened."
}
}
},
{
"type": "object",
"title": "Log insights",
"order": 9,
"properties": {
"codeQL.logInsights.joinOrderWarningThreshold": {
"type": "number",
Expand All @@ -426,7 +451,7 @@
{
"type": "object",
"title": "Telemetry",
"order": 9,
"order": 10,
"properties": {
"codeQL.telemetry.enableTelemetry": {
"type": "boolean",
Expand Down
59 changes: 54 additions & 5 deletions extensions/ql-vscode/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { DisposableObject } from "./common/disposable-object";
import {
workspace,
Event,
EventEmitter,
ConfigurationChangeEvent,
ConfigurationTarget,
ConfigurationScope,
ConfigurationTarget,
Event,
EventEmitter,
workspace,
} from "vscode";
import { DistributionManager } from "./codeql-cli/distribution";
import { extLogger } from "./common/logging/vscode";
import { ONE_DAY_IN_MS } from "./common/time";
import {
defaultFilterSortState,
FilterKey,
SortKey,
defaultFilterSortState,
} from "./variant-analysis/shared/variant-analysis-filter-sort";

export const ALL_SETTINGS: Setting[] = [];
Expand Down Expand Up @@ -764,3 +764,52 @@ export class ModelConfigListener extends ConfigListener implements ModelConfig {
return !!ENABLE_RUBY.getValue<boolean>();
}
}

const GITHUB_DATABASE_SETTING = new Setting("githubDatabase", ROOT_SETTING);

// Feature flag for the GitHub database downnload.
const GITHUB_DATABASE_ENABLE = new Setting("enable", GITHUB_DATABASE_SETTING);
const GITHUB_DATABASE_DOWNLOAD = new Setting(
"download",
GITHUB_DATABASE_SETTING,
);

const GitHubDatabaseDownloadValues = ["ask", "never"] as const;
type GitHubDatabaseDownload = (typeof GitHubDatabaseDownloadValues)[number];

export interface GitHubDatabaseConfig {
enable: boolean;
download: GitHubDatabaseDownload;
setDownload(
value: GitHubDatabaseDownload,
target?: ConfigurationTarget,
): Promise<void>;
}

export class GitHubDatabaseConfigListener
extends ConfigListener
implements GitHubDatabaseConfig
{
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
this.handleDidChangeConfigurationForRelevantSettings(
[GITHUB_DATABASE_SETTING],
e,
);
}

public get enable() {
return !!GITHUB_DATABASE_ENABLE.getValue<boolean>();
}

public get download(): GitHubDatabaseDownload {
const value = GITHUB_DATABASE_DOWNLOAD.getValue<GitHubDatabaseDownload>();
return GitHubDatabaseDownloadValues.includes(value) ? value : "ask";
}

public async setDownload(
value: GitHubDatabaseDownload,
target: ConfigurationTarget = ConfigurationTarget.Workspace,
): Promise<void> {
await GITHUB_DATABASE_DOWNLOAD.updateValue(value, target);
}
}
45 changes: 38 additions & 7 deletions extensions/ql-vscode/src/databases/database-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,32 @@ export async function downloadGitHubDatabase(

const { databaseUrl, name, owner } = result;

return downloadGitHubDatabaseFromUrl(
databaseUrl,
owner,
name,
octokit,
progress,
databaseManager,
storagePath,
cli,
makeSelected,
addSourceArchiveFolder,
);
}

export async function downloadGitHubDatabaseFromUrl(
databaseUrl: string,
owner: string,
name: string,
octokit: Octokit.Octokit,
progress: ProgressCallback,
databaseManager: DatabaseManager,
storagePath: string,
cli?: CodeQLCliServer,
makeSelected = true,
addSourceArchiveFolder = true,
): Promise<DatabaseItem | undefined> {
/**
* The 'token' property of the token object returned by `octokit.auth()`.
* The object is undocumented, but looks something like this:
Expand Down Expand Up @@ -539,10 +565,10 @@ export async function convertGithubNwoToDatabaseUrl(
try {
const [owner, repo] = nwo.split("/");

const response = await octokit.request(
"GET /repos/:owner/:repo/code-scanning/codeql/databases",
{ owner, repo },
);
const response = await octokit.rest.codeScanning.listCodeqlDatabases({
owner,
repo,
});

const languages = response.data.map((db: any) => db.language);

Expand All @@ -553,8 +579,13 @@ export async function convertGithubNwoToDatabaseUrl(
}
}

const database = response.data.find((db: any) => db.language === language);
if (!database) {
throw new Error(`No database found for language '${language}'`);
}

return {
databaseUrl: `https://api.github.com/repos/${owner}/${repo}/code-scanning/codeql/databases/${language}`,
databaseUrl: database.url,
owner,
name: repo,
};
Expand All @@ -566,9 +597,9 @@ export async function convertGithubNwoToDatabaseUrl(

export async function promptForLanguage(
languages: string[],
progress: ProgressCallback,
progress: ProgressCallback | undefined,
): Promise<string | undefined> {
progress({
progress?.({
message: "Choose language",
step: 2,
maxStep: 2,
Expand Down
103 changes: 97 additions & 6 deletions extensions/ql-vscode/src/databases/github-database-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,58 @@ import { DisposableObject } from "../common/disposable-object";
import { App } from "../common/app";
import { findGitHubRepositoryForWorkspace } from "./github-repository-finder";
import { redactableError } from "../common/errors";
import { asError } from "../common/helpers-pure";
import { asError, getErrorMessage } from "../common/helpers-pure";
import {
CodeqlDatabase,
findGitHubDatabasesForRepository,
promptGitHubDatabaseDownload,
} from "./github-database-prompt";
import {
GitHubDatabaseConfig,
GitHubDatabaseConfigListener,
isCanary,
} from "../config";
import { AppOctokit } from "../common/octokit";
import { DatabaseManager } from "./local-databases";
import { CodeQLCliServer } from "../codeql-cli/cli";

export class GithubDatabaseModule extends DisposableObject {
private constructor(private readonly app: App) {
private readonly config: GitHubDatabaseConfig;

private constructor(
private readonly app: App,
private readonly databaseManager: DatabaseManager,
private readonly databaseStoragePath: string,
private readonly cliServer: CodeQLCliServer,
) {
super();

this.config = this.push(new GitHubDatabaseConfigListener());
}

public static async initialize(app: App): Promise<GithubDatabaseModule> {
const githubDatabaseModule = new GithubDatabaseModule(app);
public static async initialize(
app: App,
databaseManager: DatabaseManager,
databaseStoragePath: string,
cliServer: CodeQLCliServer,
): Promise<GithubDatabaseModule> {
const githubDatabaseModule = new GithubDatabaseModule(
app,
databaseManager,
databaseStoragePath,
cliServer,
);
app.subscriptions.push(githubDatabaseModule);

await githubDatabaseModule.initialize();
return githubDatabaseModule;
}

private async initialize(): Promise<void> {
if (!this.config.enable) {
return;
}

void this.promptGitHubRepositoryDownload().catch((e: unknown) => {
this.app.telemetry?.sendError(
redactableError(
Expand All @@ -28,6 +64,10 @@ export class GithubDatabaseModule extends DisposableObject {
}

private async promptGitHubRepositoryDownload(): Promise<void> {
if (this.config.download === "never") {
return;
}

const githubRepositoryResult = await findGitHubRepositoryForWorkspace();
if (githubRepositoryResult.isFailure) {
void this.app.logger.log(
Expand All @@ -39,8 +79,59 @@ export class GithubDatabaseModule extends DisposableObject {
}

const githubRepository = githubRepositoryResult.value;
void this.app.logger.log(
`Found GitHub repository for workspace: '${githubRepository.owner}/${githubRepository.name}'`,

// TODO: implement this
// const hasExistingDatabase = this.databaseManager.databaseItems.some(
// (db) =>
// db.origin?.type === "github" &&
// db.origin.repository ===
// `${githubRepository.owner}/${githubRepository.name}`,
// );
// if (hasExistingDatabase) {
// return;
// }

const credentials = isCanary() ? this.app.credentials : undefined;

const octokit = credentials
? await credentials.getOctokit()
: new AppOctokit();

let databases: CodeqlDatabase[];
try {
databases = await findGitHubDatabasesForRepository(
octokit,
githubRepository.owner,
githubRepository.name,
);
} catch (e) {
this.app.telemetry?.sendError(
redactableError(
asError(e),
)`Failed to prompt for GitHub database download`,
);

void this.app.logger.log(
`Failed to find GitHub databases for repository: ${getErrorMessage(e)}`,
);

return;
}

if (databases.length === 0) {
return;
}

void promptGitHubDatabaseDownload(
octokit,
githubRepository.owner,
githubRepository.name,
databases,
this.config,
this.databaseManager,
this.databaseStoragePath,
this.cliServer,
this.app.commands,
);
}
}
Loading

0 comments on commit b190c35

Please sign in to comment.