Skip to content

Commit 73bd6d6

Browse files
committed
Show external API calls in data extensions editor
This updates the view of the data extensions editor to show a table of possible sources/sinks/flow summaries that can be edited. It's not yet possible to save the changes or load the existing file.
1 parent 60e3963 commit 73bd6d6

File tree

7 files changed

+597
-7
lines changed

7 files changed

+597
-7
lines changed

extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
11
import { ExtensionContext } from "vscode";
22
import { DataExtensionsEditorView } from "./data-extensions-editor-view";
33
import { DataExtensionsEditorCommands } from "../common/commands";
4+
import { CodeQLCliServer } from "../cli";
5+
import { QueryRunner } from "../queryRunner";
6+
import { DatabaseManager } from "../local-databases";
7+
import { extLogger } from "../common";
48

59
export class DataExtensionsEditorModule {
6-
public constructor(private readonly ctx: ExtensionContext) {}
10+
public constructor(
11+
private readonly ctx: ExtensionContext,
12+
private readonly databaseManager: DatabaseManager,
13+
private readonly cliServer: CodeQLCliServer,
14+
private readonly queryRunner: QueryRunner,
15+
private readonly queryStorageDir: string,
16+
) {}
717

818
public getCommands(): DataExtensionsEditorCommands {
919
return {
1020
"codeQL.openDataExtensionsEditor": async () => {
11-
const view = new DataExtensionsEditorView(this.ctx);
21+
const db = this.databaseManager.currentDatabaseItem;
22+
if (!db) {
23+
void extLogger.log("No database selected");
24+
return;
25+
}
26+
27+
const view = new DataExtensionsEditorView(
28+
this.ctx,
29+
this.cliServer,
30+
this.queryRunner,
31+
this.queryStorageDir,
32+
db,
33+
);
1234
await view.openView();
1335
},
1436
};

extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
1-
import { ExtensionContext, ViewColumn } from "vscode";
1+
import { CancellationTokenSource, ExtensionContext, ViewColumn } from "vscode";
22
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
33
import {
44
FromDataExtensionsEditorMessage,
55
ToDataExtensionsEditorMessage,
66
} from "../pure/interface-types";
7+
import { ProgressUpdate } from "../progress";
8+
import { extLogger, TeeLogger } from "../common";
9+
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
10+
import { qlpackOfDatabase } from "../contextual/queryResolver";
11+
import { file } from "tmp-promise";
12+
import { writeFile } from "fs-extra";
13+
import { dump } from "js-yaml";
14+
import { getOnDiskWorkspaceFolders } from "../helpers";
15+
import { DatabaseItem } from "../local-databases";
16+
import { CodeQLCliServer } from "../cli";
717

818
export class DataExtensionsEditorView extends AbstractWebview<
919
ToDataExtensionsEditorMessage,
1020
FromDataExtensionsEditorMessage
1121
> {
12-
public constructor(ctx: ExtensionContext) {
22+
public constructor(
23+
ctx: ExtensionContext,
24+
private readonly cliServer: CodeQLCliServer,
25+
private readonly queryRunner: QueryRunner,
26+
private readonly queryStorageDir: string,
27+
private readonly databaseItem: DatabaseItem,
28+
) {
1329
super(ctx);
1430
}
1531

@@ -49,5 +65,136 @@ export class DataExtensionsEditorView extends AbstractWebview<
4965

5066
protected async onWebViewLoaded() {
5167
super.onWebViewLoaded();
68+
69+
await this.loadExternalApiUsages();
70+
}
71+
72+
protected async loadExternalApiUsages(): Promise<void> {
73+
const queryResult = await this.runQuery();
74+
if (!queryResult) {
75+
await this.clearProgress();
76+
return;
77+
}
78+
79+
await this.showProgress({
80+
message: "Loading results",
81+
step: 1100,
82+
maxStep: 1500,
83+
});
84+
85+
const bqrsPath = queryResult.outputDir.bqrsPath;
86+
87+
const results = await this.getResults(bqrsPath);
88+
if (!results) {
89+
await this.clearProgress();
90+
return;
91+
}
92+
93+
await this.showProgress({
94+
message: "Finalizing results",
95+
step: 1450,
96+
maxStep: 1500,
97+
});
98+
99+
await this.postMessage({
100+
t: "setExternalApiRepoResults",
101+
results,
102+
});
103+
104+
await this.clearProgress();
105+
}
106+
107+
private async runQuery(): Promise<CoreCompletedQuery | undefined> {
108+
const qlpacks = await qlpackOfDatabase(this.cliServer, this.databaseItem);
109+
110+
const packsToSearch = [qlpacks.dbschemePack];
111+
if (qlpacks.queryPack) {
112+
packsToSearch.push(qlpacks.queryPack);
113+
}
114+
115+
const suiteFile = (
116+
await file({
117+
postfix: ".qls",
118+
})
119+
).path;
120+
const suiteYaml = [];
121+
for (const qlpack of packsToSearch) {
122+
suiteYaml.push({
123+
from: qlpack,
124+
queries: ".",
125+
include: {
126+
id: `${this.databaseItem.language}/telemetry/fetch-external-apis`,
127+
},
128+
});
129+
}
130+
await writeFile(suiteFile, dump(suiteYaml), "utf8");
131+
132+
const queries = await this.cliServer.resolveQueriesInSuite(
133+
suiteFile,
134+
getOnDiskWorkspaceFolders(),
135+
);
136+
137+
if (queries.length !== 1) {
138+
void extLogger.log(`Expected exactly one query, got ${queries.length}`);
139+
return;
140+
}
141+
142+
const query = queries[0];
143+
144+
const tokenSource = new CancellationTokenSource();
145+
146+
const queryRun = this.queryRunner.createQueryRun(
147+
this.databaseItem.databaseUri.fsPath,
148+
{ queryPath: query, quickEvalPosition: undefined },
149+
false,
150+
getOnDiskWorkspaceFolders(),
151+
undefined,
152+
this.queryStorageDir,
153+
undefined,
154+
undefined,
155+
);
156+
157+
return queryRun.evaluate(
158+
(update) => this.showProgress(update, 1500),
159+
tokenSource.token,
160+
new TeeLogger(this.queryRunner.logger, queryRun.outputDir.logPath),
161+
);
162+
}
163+
164+
private async getResults(bqrsPath: string) {
165+
const bqrsInfo = await this.cliServer.bqrsInfo(bqrsPath);
166+
if (bqrsInfo["result-sets"].length !== 1) {
167+
void extLogger.log(
168+
`Expected exactly one result set, got ${bqrsInfo["result-sets"].length}`,
169+
);
170+
return undefined;
171+
}
172+
173+
const resultSet = bqrsInfo["result-sets"][0];
174+
175+
await this.showProgress({
176+
message: "Decoding results",
177+
step: 1200,
178+
maxStep: 1500,
179+
});
180+
181+
return this.cliServer.bqrsDecode(bqrsPath, resultSet.name);
182+
}
183+
184+
private async showProgress(update: ProgressUpdate, maxStep?: number) {
185+
await this.postMessage({
186+
t: "showProgress",
187+
step: update.step,
188+
maxStep: maxStep ?? update.maxStep,
189+
message: update.message,
190+
});
191+
}
192+
193+
private async clearProgress() {
194+
await this.showProgress({
195+
step: 0,
196+
maxStep: 0,
197+
message: "",
198+
});
52199
}
53200
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { ResolvableLocationValue } from "../pure/bqrs-cli-types";
2+
3+
export type Call = {
4+
label: string;
5+
url: ResolvableLocationValue;
6+
};
7+
8+
export type ExternalApiUsage = {
9+
externalApiInfo: string;
10+
packageName: string;
11+
typeName: string;
12+
methodName: string;
13+
methodParameters: string;
14+
supported: boolean;
15+
usages: Call[];
16+
};
17+
18+
export type ModeledMethodType =
19+
| "none"
20+
| "source"
21+
| "sink"
22+
| "summary"
23+
| "neutral";
24+
25+
export type ModeledMethod = {
26+
type: ModeledMethodType;
27+
input: string;
28+
output: string;
29+
kind: string;
30+
};

extensions/ql-vscode/src/extension.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,18 @@ async function activateWithInstalledDistribution(
861861
);
862862
ctx.subscriptions.push(localQueries);
863863

864-
const dataExtensionsEditorModule = new DataExtensionsEditorModule(ctx);
864+
const dataExtensionsEditorQueryStorageDir = join(
865+
tmpDir.name,
866+
"data-extensions-editor-results",
867+
);
868+
await ensureDir(dataExtensionsEditorQueryStorageDir);
869+
const dataExtensionsEditorModule = new DataExtensionsEditorModule(
870+
ctx,
871+
dbm,
872+
cliServer,
873+
qs,
874+
dataExtensionsEditorQueryStorageDir,
875+
);
865876

866877
void extLogger.log("Initializing QLTest interface.");
867878
const testExplorerExtension = extensions.getExtension<TestHub>(

extensions/ql-vscode/src/pure/interface-types.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ResultSetSchema,
66
Column,
77
ResolvableLocationValue,
8+
DecodedBqrsChunk,
89
} from "./bqrs-cli-types";
910
import {
1011
VariantAnalysis,
@@ -479,6 +480,20 @@ export type ToDataFlowPathsMessage = SetDataFlowPathsMessage;
479480

480481
export type FromDataFlowPathsMessage = CommonFromViewMessages;
481482

482-
export type ToDataExtensionsEditorMessage = never;
483+
export interface SetExternalApiResultsMessage {
484+
t: "setExternalApiRepoResults";
485+
results: DecodedBqrsChunk;
486+
}
487+
488+
export interface ShowProgressMessage {
489+
t: "showProgress";
490+
step: number;
491+
maxStep: number;
492+
message: string;
493+
}
494+
495+
export type ToDataExtensionsEditorMessage =
496+
| SetExternalApiResultsMessage
497+
| ShowProgressMessage;
483498

484499
export type FromDataExtensionsEditorMessage = ViewLoadedMsg;

0 commit comments

Comments
 (0)