Skip to content

Commit 422f0eb

Browse files
authored
Merge pull request #3071 from github/koesie10/download-github-database
Prompt user for database download on startup
2 parents c0db180 + 5c03f5b commit 422f0eb

8 files changed

Lines changed: 642 additions & 18 deletions

File tree

extensions/ql-vscode/package.json

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,33 @@
421421
},
422422
{
423423
"type": "object",
424-
"title": "Log insights",
424+
"title": "GitHub Databases",
425425
"order": 8,
426+
"properties": {
427+
"codeQL.githubDatabase.enable": {
428+
"type": "boolean",
429+
"default": false,
430+
"markdownDescription": "Enable automatic detection of GitHub databases."
431+
},
432+
"codeQL.githubDatabase.download": {
433+
"type": "string",
434+
"default": "ask",
435+
"enum": [
436+
"ask",
437+
"never"
438+
],
439+
"enumDescriptions": [
440+
"Ask to download a GitHub database when a workspace is opened.",
441+
"Never download a GitHub databases when a workspace is opened."
442+
],
443+
"description": "Ask to download a GitHub database when a workspace is opened."
444+
}
445+
}
446+
},
447+
{
448+
"type": "object",
449+
"title": "Log insights",
450+
"order": 9,
426451
"properties": {
427452
"codeQL.logInsights.joinOrderWarningThreshold": {
428453
"type": "number",
@@ -436,7 +461,7 @@
436461
{
437462
"type": "object",
438463
"title": "Telemetry",
439-
"order": 9,
464+
"order": 10,
440465
"properties": {
441466
"codeQL.telemetry.enableTelemetry": {
442467
"type": "boolean",

extensions/ql-vscode/src/config.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { DisposableObject } from "./common/disposable-object";
22
import {
3-
workspace,
4-
Event,
5-
EventEmitter,
63
ConfigurationChangeEvent,
7-
ConfigurationTarget,
84
ConfigurationScope,
5+
ConfigurationTarget,
6+
Event,
7+
EventEmitter,
8+
workspace,
99
} from "vscode";
1010
import { DistributionManager } from "./codeql-cli/distribution";
1111
import { extLogger } from "./common/logging/vscode";
1212
import { ONE_DAY_IN_MS } from "./common/time";
1313
import {
14+
defaultFilterSortState,
1415
FilterKey,
1516
SortKey,
16-
defaultFilterSortState,
1717
} from "./variant-analysis/shared/variant-analysis-filter-sort";
1818

1919
export const ALL_SETTINGS: Setting[] = [];
@@ -775,3 +775,52 @@ export class ModelConfigListener extends ConfigListener implements ModelConfig {
775775
return !!ENABLE_RUBY.getValue<boolean>();
776776
}
777777
}
778+
779+
const GITHUB_DATABASE_SETTING = new Setting("githubDatabase", ROOT_SETTING);
780+
781+
// Feature flag for the GitHub database downnload.
782+
const GITHUB_DATABASE_ENABLE = new Setting("enable", GITHUB_DATABASE_SETTING);
783+
const GITHUB_DATABASE_DOWNLOAD = new Setting(
784+
"download",
785+
GITHUB_DATABASE_SETTING,
786+
);
787+
788+
const GitHubDatabaseDownloadValues = ["ask", "never"] as const;
789+
type GitHubDatabaseDownload = (typeof GitHubDatabaseDownloadValues)[number];
790+
791+
export interface GitHubDatabaseConfig {
792+
enable: boolean;
793+
download: GitHubDatabaseDownload;
794+
setDownload(
795+
value: GitHubDatabaseDownload,
796+
target?: ConfigurationTarget,
797+
): Promise<void>;
798+
}
799+
800+
export class GitHubDatabaseConfigListener
801+
extends ConfigListener
802+
implements GitHubDatabaseConfig
803+
{
804+
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
805+
this.handleDidChangeConfigurationForRelevantSettings(
806+
[GITHUB_DATABASE_SETTING],
807+
e,
808+
);
809+
}
810+
811+
public get enable() {
812+
return !!GITHUB_DATABASE_ENABLE.getValue<boolean>();
813+
}
814+
815+
public get download(): GitHubDatabaseDownload {
816+
const value = GITHUB_DATABASE_DOWNLOAD.getValue<GitHubDatabaseDownload>();
817+
return GitHubDatabaseDownloadValues.includes(value) ? value : "ask";
818+
}
819+
820+
public async setDownload(
821+
value: GitHubDatabaseDownload,
822+
target: ConfigurationTarget = ConfigurationTarget.Workspace,
823+
): Promise<void> {
824+
await GITHUB_DATABASE_DOWNLOAD.updateValue(value, target);
825+
}
826+
}

extensions/ql-vscode/src/databases/database-fetcher.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,38 @@ export async function downloadGitHubDatabase(
207207
const { databaseUrl, name, owner, databaseId, databaseCreatedAt, commitOid } =
208208
result;
209209

210+
return downloadGitHubDatabaseFromUrl(
211+
databaseUrl,
212+
databaseId,
213+
databaseCreatedAt,
214+
commitOid,
215+
owner,
216+
name,
217+
octokit,
218+
progress,
219+
databaseManager,
220+
storagePath,
221+
cli,
222+
makeSelected,
223+
addSourceArchiveFolder,
224+
);
225+
}
226+
227+
export async function downloadGitHubDatabaseFromUrl(
228+
databaseUrl: string,
229+
databaseId: number,
230+
databaseCreatedAt: string,
231+
commitOid: string | null,
232+
owner: string,
233+
name: string,
234+
octokit: Octokit.Octokit,
235+
progress: ProgressCallback,
236+
databaseManager: DatabaseManager,
237+
storagePath: string,
238+
cli?: CodeQLCliServer,
239+
makeSelected = true,
240+
addSourceArchiveFolder = true,
241+
): Promise<DatabaseItem | undefined> {
210242
/**
211243
* The 'token' property of the token object returned by `octokit.auth()`.
212244
* The object is undocumented, but looks something like this:
@@ -229,7 +261,7 @@ export async function downloadGitHubDatabase(
229261
`${owner}/${name}`,
230262
{
231263
type: "github",
232-
repository: nwo,
264+
repository: `${owner}/${name}`,
233265
databaseId,
234266
databaseCreatedAt,
235267
commitOid,
@@ -577,7 +609,7 @@ export async function convertGithubNwoToDatabaseUrl(
577609
}
578610

579611
const databaseForLanguage = response.data.find(
580-
(db: any) => db.language === language,
612+
(db) => db.language === language,
581613
);
582614
if (!databaseForLanguage) {
583615
throw new Error(`No database found for language '${language}'`);
@@ -599,9 +631,9 @@ export async function convertGithubNwoToDatabaseUrl(
599631

600632
export async function promptForLanguage(
601633
languages: string[],
602-
progress: ProgressCallback,
634+
progress: ProgressCallback | undefined,
603635
): Promise<string | undefined> {
604-
progress({
636+
progress?.({
605637
message: "Choose language",
606638
step: 2,
607639
maxStep: 2,

extensions/ql-vscode/src/databases/github-database-module.ts

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,58 @@ import { DisposableObject } from "../common/disposable-object";
22
import { App } from "../common/app";
33
import { findGitHubRepositoryForWorkspace } from "./github-repository-finder";
44
import { redactableError } from "../common/errors";
5-
import { asError } from "../common/helpers-pure";
5+
import { asError, getErrorMessage } from "../common/helpers-pure";
6+
import {
7+
CodeqlDatabase,
8+
findGitHubDatabasesForRepository,
9+
promptAndDownloadGitHubDatabase,
10+
} from "./github-database-prompt";
11+
import {
12+
GitHubDatabaseConfig,
13+
GitHubDatabaseConfigListener,
14+
isCanary,
15+
} from "../config";
16+
import { AppOctokit } from "../common/octokit";
17+
import { DatabaseManager } from "./local-databases";
18+
import { CodeQLCliServer } from "../codeql-cli/cli";
619

720
export class GithubDatabaseModule extends DisposableObject {
8-
private constructor(private readonly app: App) {
21+
private readonly config: GitHubDatabaseConfig;
22+
23+
private constructor(
24+
private readonly app: App,
25+
private readonly databaseManager: DatabaseManager,
26+
private readonly databaseStoragePath: string,
27+
private readonly cliServer: CodeQLCliServer,
28+
) {
929
super();
30+
31+
this.config = this.push(new GitHubDatabaseConfigListener());
1032
}
1133

12-
public static async initialize(app: App): Promise<GithubDatabaseModule> {
13-
const githubDatabaseModule = new GithubDatabaseModule(app);
34+
public static async initialize(
35+
app: App,
36+
databaseManager: DatabaseManager,
37+
databaseStoragePath: string,
38+
cliServer: CodeQLCliServer,
39+
): Promise<GithubDatabaseModule> {
40+
const githubDatabaseModule = new GithubDatabaseModule(
41+
app,
42+
databaseManager,
43+
databaseStoragePath,
44+
cliServer,
45+
);
1446
app.subscriptions.push(githubDatabaseModule);
1547

1648
await githubDatabaseModule.initialize();
1749
return githubDatabaseModule;
1850
}
1951

2052
private async initialize(): Promise<void> {
53+
if (!this.config.enable) {
54+
return;
55+
}
56+
2157
// Start the check and downloading the database asynchronously. We don't want to block on this
2258
// in extension activation since this makes network requests and waits for user input.
2359
void this.promptGitHubRepositoryDownload().catch((e: unknown) => {
@@ -31,6 +67,10 @@ export class GithubDatabaseModule extends DisposableObject {
3167
}
3268

3369
private async promptGitHubRepositoryDownload(): Promise<void> {
70+
if (this.config.download === "never") {
71+
return;
72+
}
73+
3474
const githubRepositoryResult = await findGitHubRepositoryForWorkspace();
3575
if (githubRepositoryResult.isFailure) {
3676
void this.app.logger.log(
@@ -42,8 +82,58 @@ export class GithubDatabaseModule extends DisposableObject {
4282
}
4383

4484
const githubRepository = githubRepositoryResult.value;
45-
void this.app.logger.log(
46-
`Found GitHub repository for workspace: '${githubRepository.owner}/${githubRepository.name}'`,
85+
86+
const hasExistingDatabase = this.databaseManager.databaseItems.some(
87+
(db) =>
88+
db.origin?.type === "github" &&
89+
db.origin.repository ===
90+
`${githubRepository.owner}/${githubRepository.name}`,
91+
);
92+
if (hasExistingDatabase) {
93+
return;
94+
}
95+
96+
const credentials = isCanary() ? this.app.credentials : undefined;
97+
98+
const octokit = credentials
99+
? await credentials.getOctokit()
100+
: new AppOctokit();
101+
102+
let databases: CodeqlDatabase[];
103+
try {
104+
databases = await findGitHubDatabasesForRepository(
105+
octokit,
106+
githubRepository.owner,
107+
githubRepository.name,
108+
);
109+
} catch (e) {
110+
this.app.telemetry?.sendError(
111+
redactableError(
112+
asError(e),
113+
)`Failed to prompt for GitHub database download`,
114+
);
115+
116+
void this.app.logger.log(
117+
`Failed to find GitHub databases for repository: ${getErrorMessage(e)}`,
118+
);
119+
120+
return;
121+
}
122+
123+
if (databases.length === 0) {
124+
return;
125+
}
126+
127+
await promptAndDownloadGitHubDatabase(
128+
octokit,
129+
githubRepository.owner,
130+
githubRepository.name,
131+
databases,
132+
this.config,
133+
this.databaseManager,
134+
this.databaseStoragePath,
135+
this.cliServer,
136+
this.app.commands,
47137
);
48138
}
49139
}

0 commit comments

Comments
 (0)