Skip to content

Commit 42aacce

Browse files
authored
Merge pull request #2316 from github/koesie10/data-extension-editor-errors
Improve error handling in data extension editor
2 parents abe74fc + 7e9a7f2 commit 42aacce

File tree

7 files changed

+114
-35
lines changed

7 files changed

+114
-35
lines changed

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import {
1414
import { ProgressUpdate } from "../progress";
1515
import { QueryRunner } from "../queryRunner";
1616
import {
17+
showAndLogErrorMessage,
1718
showAndLogExceptionWithTelemetry,
18-
showAndLogWarningMessage,
1919
} from "../helpers";
2020
import { extLogger } from "../common";
2121
import { readFile, writeFile } from "fs-extra";
@@ -166,7 +166,9 @@ export class DataExtensionsEditorView extends AbstractWebview<
166166
const existingModeledMethods = loadDataExtensionYaml(data);
167167

168168
if (!existingModeledMethods) {
169-
void showAndLogWarningMessage("Failed to parse data extension YAML.");
169+
void showAndLogErrorMessage(
170+
`Failed to parse data extension YAML ${this.modelFilename}.`,
171+
);
170172
return;
171173
}
172174

@@ -175,7 +177,11 @@ export class DataExtensionsEditorView extends AbstractWebview<
175177
modeledMethods: existingModeledMethods,
176178
});
177179
} catch (e: unknown) {
178-
void extLogger.log(`Unable to read data extension YAML: ${e}`);
180+
void showAndLogErrorMessage(
181+
`Unable to read data extension YAML ${
182+
this.modelFilename
183+
}: ${getErrorMessage(e)}`,
184+
);
179185
}
180186
}
181187

@@ -208,7 +214,6 @@ export class DataExtensionsEditorView extends AbstractWebview<
208214
const bqrsChunk = await readQueryResults({
209215
cliServer: this.cliServer,
210216
bqrsPath: queryResult.outputDir.bqrsPath,
211-
logger: extLogger,
212217
});
213218
if (!bqrsChunk) {
214219
await this.clearProgress();
@@ -233,7 +238,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
233238
void showAndLogExceptionWithTelemetry(
234239
redactableError(
235240
asError(err),
236-
)`Failed to load external APi usages: ${getErrorMessage(err)}`,
241+
)`Failed to load external API usages: ${getErrorMessage(err)}`,
237242
);
238243
}
239244
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"extensions": {
5+
"type": "array",
6+
"items": {
7+
"type": "object",
8+
"required": ["addsTo", "data"],
9+
"properties": {
10+
"addsTo": {
11+
"type": "object",
12+
"required": ["pack", "extensible"],
13+
"properties": {
14+
"pack": {
15+
"type": "string"
16+
},
17+
"extensible": {
18+
"type": "string"
19+
}
20+
}
21+
},
22+
"data": {
23+
"type": "array",
24+
"items": {
25+
"type": "array",
26+
"items": {
27+
"oneOf": [
28+
{
29+
"type": "string"
30+
},
31+
{
32+
"type": "boolean"
33+
},
34+
{
35+
"type": "number"
36+
}
37+
]
38+
}
39+
}
40+
}
41+
}
42+
}
43+
}
44+
}
45+
}

extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ import { qlpackOfDatabase } from "../contextual/queryResolver";
33
import { file } from "tmp-promise";
44
import { writeFile } from "fs-extra";
55
import { dump as dumpYaml } from "js-yaml";
6-
import { getOnDiskWorkspaceFolders } from "../helpers";
6+
import {
7+
getOnDiskWorkspaceFolders,
8+
showAndLogExceptionWithTelemetry,
9+
} from "../helpers";
710
import { Logger, TeeLogger } from "../common";
811
import { CancellationToken } from "vscode";
912
import { CodeQLCliServer } from "../cli";
1013
import { DatabaseItem } from "../local-databases";
1114
import { ProgressCallback } from "../progress";
15+
import { redactableError } from "../pure/errors";
1216

1317
export type RunQueryOptions = {
1418
cliServer: Pick<CodeQLCliServer, "resolveQlpacks" | "resolveQueriesInSuite">;
@@ -92,18 +96,16 @@ export async function runQuery({
9296
export type GetResultsOptions = {
9397
cliServer: Pick<CodeQLCliServer, "bqrsInfo" | "bqrsDecode">;
9498
bqrsPath: string;
95-
logger: Logger;
9699
};
97100

98101
export async function readQueryResults({
99102
cliServer,
100103
bqrsPath,
101-
logger,
102104
}: GetResultsOptions) {
103105
const bqrsInfo = await cliServer.bqrsInfo(bqrsPath);
104106
if (bqrsInfo["result-sets"].length !== 1) {
105-
void logger.log(
106-
`Expected exactly one result set, got ${bqrsInfo["result-sets"].length}`,
107+
void showAndLogExceptionWithTelemetry(
108+
redactableError`Expected exactly one result set, got ${bqrsInfo["result-sets"].length}`,
107109
);
108110
return undefined;
109111
}

extensions/ql-vscode/src/data-extensions-editor/generate-flow-model.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ import { CodeQLCliServer } from "../cli";
66
import { TeeLogger } from "../common";
77
import { extensiblePredicateDefinitions } from "./yaml";
88
import { ProgressCallback } from "../progress";
9-
import { getOnDiskWorkspaceFolders } from "../helpers";
9+
import {
10+
getOnDiskWorkspaceFolders,
11+
showAndLogExceptionWithTelemetry,
12+
} from "../helpers";
1013
import {
1114
ModeledMethodType,
1215
ModeledMethodWithSignature,
1316
} from "./modeled-method";
17+
import { redactableError } from "../pure/errors";
18+
import { QueryResultType } from "../pure/new-messages";
1419

1520
type FlowModelOptions = {
1621
cliServer: CodeQLCliServer;
@@ -67,13 +72,21 @@ async function getModeledMethodsFromFlow(
6772
token,
6873
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
6974
);
75+
if (queryResult.resultType !== QueryResultType.SUCCESS) {
76+
void showAndLogExceptionWithTelemetry(
77+
redactableError`Failed to run ${queryName} query: ${
78+
queryResult.message ?? "No message"
79+
}`,
80+
);
81+
return [];
82+
}
7083

7184
const bqrsPath = queryResult.outputDir.bqrsPath;
7285

7386
const bqrsInfo = await cliServer.bqrsInfo(bqrsPath);
7487
if (bqrsInfo["result-sets"].length !== 1) {
75-
throw new Error(
76-
`Expected exactly one result set, got ${bqrsInfo["result-sets"].length}`,
88+
void showAndLogExceptionWithTelemetry(
89+
redactableError`Expected exactly one result set, got ${bqrsInfo["result-sets"].length} for ${queryName}`,
7790
);
7891
}
7992

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
import Ajv from "ajv";
2+
13
import { ExternalApiUsage } from "./external-api-usage";
24
import {
35
ModeledMethod,
46
ModeledMethodType,
57
ModeledMethodWithSignature,
68
} from "./modeled-method";
79

10+
import * as dataSchemaJson from "./data-schema.json";
11+
12+
const ajv = new Ajv({ allErrors: true });
13+
const dataSchemaValidate = ajv.compile(dataSchemaJson);
14+
815
type ExternalApiUsageByType = {
916
externalApiUsage: ExternalApiUsage;
1017
modeledMethod: ModeledMethod;
@@ -191,8 +198,14 @@ ${extensions.join("\n")}`;
191198
export function loadDataExtensionYaml(
192199
data: any,
193200
): Record<string, ModeledMethod> | undefined {
194-
if (typeof data !== "object") {
195-
return undefined;
201+
dataSchemaValidate(data);
202+
203+
if (dataSchemaValidate.errors) {
204+
throw new Error(
205+
`Invalid data extension YAML: ${dataSchemaValidate.errors
206+
.map((error) => `${error.instancePath} ${error.message}`)
207+
.join(", ")}`,
208+
);
196209
}
197210

198211
const extensions = data.extensions;
@@ -204,19 +217,8 @@ export function loadDataExtensionYaml(
204217

205218
for (const extension of extensions) {
206219
const addsTo = extension.addsTo;
207-
if (typeof addsTo !== "object") {
208-
continue;
209-
}
210-
211220
const extensible = addsTo.extensible;
212-
if (typeof extensible !== "string") {
213-
continue;
214-
}
215-
216221
const data = extension.data;
217-
if (!Array.isArray(data)) {
218-
continue;
219-
}
220222

221223
const definition = Object.values(extensiblePredicateDefinitions).find(
222224
(definition) => definition.extensiblePredicate === extensible,

extensions/ql-vscode/test/unit-tests/data-extensions-editor/yaml.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,14 @@ describe("loadDataExtensionYaml", () => {
149149
});
150150

151151
it("returns undefined if given a string", () => {
152-
const data = loadDataExtensionYaml(`extensions:
152+
expect(() =>
153+
loadDataExtensionYaml(`extensions:
153154
- addsTo:
154155
pack: codeql/java-all
155156
extensible: sinkModel
156157
data:
157158
- ["org.sql2o","Connection",true,"createQuery","(String)","","Argument[0]","sql","manual"]
158-
`);
159-
160-
expect(data).toBeUndefined();
159+
`),
160+
).toThrow("Invalid data extension YAML: must be object");
161161
});
162162
});

extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { file } from "tmp-promise";
1010
import { QueryResultType } from "../../../../src/pure/new-messages";
1111
import { readFile } from "fs-extra";
1212
import { load } from "js-yaml";
13+
import * as helpers from "../../../../src/helpers";
14+
import { RedactableError } from "../../../../src/pure/errors";
1315

1416
function createMockUri(path = "/a/b/c/foo"): Uri {
1517
return {
@@ -127,17 +129,27 @@ describe("readQueryResults", () => {
127129
bqrsDecode: jest.fn(),
128130
},
129131
bqrsPath: "/tmp/results.bqrs",
130-
logger: createMockLogger(),
131132
};
132133

134+
let showAndLogExceptionWithTelemetrySpy: jest.SpiedFunction<
135+
typeof helpers.showAndLogExceptionWithTelemetry
136+
>;
137+
138+
beforeEach(() => {
139+
showAndLogExceptionWithTelemetrySpy = jest.spyOn(
140+
helpers,
141+
"showAndLogExceptionWithTelemetry",
142+
);
143+
});
144+
133145
it("returns undefined when there are no results", async () => {
134146
options.cliServer.bqrsInfo.mockResolvedValue({
135147
"result-sets": [],
136148
});
137149

138150
expect(await readQueryResults(options)).toBeUndefined();
139-
expect(options.logger.log).toHaveBeenCalledWith(
140-
expect.stringMatching(/Expected exactly one result set/),
151+
expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith(
152+
expect.any(RedactableError),
141153
);
142154
});
143155

@@ -166,8 +178,8 @@ describe("readQueryResults", () => {
166178
});
167179

168180
expect(await readQueryResults(options)).toBeUndefined();
169-
expect(options.logger.log).toHaveBeenCalledWith(
170-
expect.stringMatching(/Expected exactly one result set/),
181+
expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith(
182+
expect.any(RedactableError),
171183
);
172184
});
173185

0 commit comments

Comments
 (0)