Skip to content

Commit 3a761d0

Browse files
committed
Improve error handling in data extension editor
This improves the error handling in the data extension editor by showing more errors to the user and adding validation to data extension model files.
1 parent 807eb92 commit 3a761d0

File tree

6 files changed

+110
-31
lines changed

6 files changed

+110
-31
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/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)