Skip to content

Commit d60bcf3

Browse files
authored
Merge pull request #2264 from github/koesie10/data-extension-editor-yaml
Add saving of data extension editor table to YAML
2 parents 9f78092 + 083b736 commit d60bcf3

File tree

5 files changed

+311
-4
lines changed

5 files changed

+311
-4
lines changed

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

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { CancellationTokenSource, ExtensionContext, ViewColumn } from "vscode";
1+
import {
2+
CancellationTokenSource,
3+
ExtensionContext,
4+
Uri,
5+
ViewColumn,
6+
workspace,
7+
} from "vscode";
28
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
39
import {
410
FromDataExtensionsEditorMessage,
@@ -17,9 +23,9 @@ import {
1723
} from "../helpers";
1824
import { DatabaseItem } from "../local-databases";
1925
import { CodeQLCliServer } from "../cli";
26+
import { assertNever, asError, getErrorMessage } from "../pure/helpers-pure";
2027
import { decodeBqrsToExternalApiUsages } from "./bqrs";
2128
import { redactableError } from "../pure/errors";
22-
import { asError, getErrorMessage } from "../pure/helpers-pure";
2329

2430
export class DataExtensionsEditorView extends AbstractWebview<
2531
ToDataExtensionsEditorMessage,
@@ -63,9 +69,14 @@ export class DataExtensionsEditorView extends AbstractWebview<
6369
case "viewLoaded":
6470
await this.onWebViewLoaded();
6571

72+
break;
73+
case "applyDataExtensionYaml":
74+
await this.saveYaml(msg.yaml);
75+
await this.loadExternalApiUsages();
76+
6677
break;
6778
default:
68-
throw new Error("Unexpected message type");
79+
assertNever(msg);
6980
}
7081
}
7182

@@ -75,6 +86,17 @@ export class DataExtensionsEditorView extends AbstractWebview<
7586
await this.loadExternalApiUsages();
7687
}
7788

89+
protected async saveYaml(yaml: string): Promise<void> {
90+
const modelFilename = this.calculateModelFilename();
91+
if (!modelFilename) {
92+
return;
93+
}
94+
95+
await writeFile(modelFilename, yaml);
96+
97+
void extLogger.log(`Saved data extension YAML to ${modelFilename}`);
98+
}
99+
78100
protected async loadExternalApiUsages(): Promise<void> {
79101
try {
80102
const queryResult = await this.runQuery();
@@ -221,4 +243,21 @@ export class DataExtensionsEditorView extends AbstractWebview<
221243
message: "",
222244
});
223245
}
246+
247+
private calculateModelFilename(): string | undefined {
248+
const workspaceFolder = workspace.workspaceFolders?.find(
249+
(folder) => folder.name === "ql",
250+
);
251+
if (!workspaceFolder) {
252+
void extLogger.log("No workspace folder 'ql' found");
253+
254+
return;
255+
}
256+
257+
return Uri.joinPath(
258+
workspaceFolder.uri,
259+
"java/ql/lib/ext",
260+
`${this.databaseItem.name.replaceAll("/", ".")}.model.yml`,
261+
).fsPath;
262+
}
224263
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { ExternalApiUsage } from "./external-api-usage";
2+
import { ModeledMethod, ModeledMethodType } from "./modeled-method";
3+
4+
type ExternalApiUsageByType = {
5+
externalApiUsage: ExternalApiUsage;
6+
modeledMethod: ModeledMethod;
7+
};
8+
9+
type DataExtensionDefinition = {
10+
extensible: string;
11+
generateMethodDefinition: (method: ExternalApiUsageByType) => any[];
12+
};
13+
14+
const definitions: Record<
15+
Exclude<ModeledMethodType, "none">,
16+
DataExtensionDefinition
17+
> = {
18+
source: {
19+
extensible: "sourceModel",
20+
// extensible predicate sourceModel(
21+
// string package, string type, boolean subtypes, string name, string signature, string ext,
22+
// string output, string kind, string provenance
23+
// );
24+
generateMethodDefinition: (method) => [
25+
method.externalApiUsage.packageName,
26+
method.externalApiUsage.typeName,
27+
true,
28+
method.externalApiUsage.methodName,
29+
method.externalApiUsage.methodParameters,
30+
"",
31+
method.modeledMethod.output,
32+
method.modeledMethod.kind,
33+
"manual",
34+
],
35+
},
36+
sink: {
37+
extensible: "sinkModel",
38+
// extensible predicate sinkModel(
39+
// string package, string type, boolean subtypes, string name, string signature, string ext,
40+
// string input, string kind, string provenance
41+
// );
42+
generateMethodDefinition: (method) => [
43+
method.externalApiUsage.packageName,
44+
method.externalApiUsage.typeName,
45+
true,
46+
method.externalApiUsage.methodName,
47+
method.externalApiUsage.methodParameters,
48+
"",
49+
method.modeledMethod.input,
50+
method.modeledMethod.kind,
51+
"manual",
52+
],
53+
},
54+
summary: {
55+
extensible: "summaryModel",
56+
// extensible predicate summaryModel(
57+
// string package, string type, boolean subtypes, string name, string signature, string ext,
58+
// string input, string output, string kind, string provenance
59+
// );
60+
generateMethodDefinition: (method) => [
61+
method.externalApiUsage.packageName,
62+
method.externalApiUsage.typeName,
63+
true,
64+
method.externalApiUsage.methodName,
65+
method.externalApiUsage.methodParameters,
66+
"",
67+
method.modeledMethod.input,
68+
method.modeledMethod.output,
69+
method.modeledMethod.kind,
70+
"manual",
71+
],
72+
},
73+
neutral: {
74+
extensible: "neutralModel",
75+
// extensible predicate neutralModel(
76+
// string package, string type, string name, string signature, string provenance
77+
// );
78+
generateMethodDefinition: (method) => [
79+
method.externalApiUsage.packageName,
80+
method.externalApiUsage.typeName,
81+
method.externalApiUsage.methodName,
82+
method.externalApiUsage.methodParameters,
83+
"manual",
84+
],
85+
},
86+
};
87+
88+
function createDataProperty(
89+
methods: ExternalApiUsageByType[],
90+
definition: DataExtensionDefinition,
91+
) {
92+
if (methods.length === 0) {
93+
return " []";
94+
}
95+
96+
return `\n${methods
97+
.map(
98+
(method) =>
99+
` - ${JSON.stringify(
100+
definition.generateMethodDefinition(method),
101+
)}`,
102+
)
103+
.join("\n")}`;
104+
}
105+
106+
export function createDataExtensionYaml(
107+
externalApiUsages: ExternalApiUsage[],
108+
modeledMethods: Record<string, ModeledMethod>,
109+
) {
110+
const methodsByType: Record<
111+
Exclude<ModeledMethodType, "none">,
112+
ExternalApiUsageByType[]
113+
> = {
114+
source: [],
115+
sink: [],
116+
summary: [],
117+
neutral: [],
118+
};
119+
120+
for (const externalApiUsage of externalApiUsages) {
121+
const modeledMethod = modeledMethods[externalApiUsage.signature];
122+
123+
if (modeledMethod?.type && modeledMethod.type !== "none") {
124+
methodsByType[modeledMethod.type].push({
125+
externalApiUsage,
126+
modeledMethod,
127+
});
128+
}
129+
}
130+
131+
const extensions = Object.entries(definitions).map(
132+
([type, definition]) => ` - addsTo:
133+
pack: codeql/java-all
134+
extensible: ${definition.extensible}
135+
data:${createDataProperty(
136+
methodsByType[type as Exclude<ModeledMethodType, "none">],
137+
definition,
138+
)}
139+
`,
140+
);
141+
142+
return `extensions:
143+
${extensions.join("\n")}`;
144+
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,15 @@ export interface ShowProgressMessage {
492492
message: string;
493493
}
494494

495+
export interface ApplyDataExtensionYamlMessage {
496+
t: "applyDataExtensionYaml";
497+
yaml: string;
498+
}
499+
495500
export type ToDataExtensionsEditorMessage =
496501
| SetExternalApiUsagesMessage
497502
| ShowProgressMessage;
498503

499-
export type FromDataExtensionsEditorMessage = ViewLoadedMsg;
504+
export type FromDataExtensionsEditorMessage =
505+
| ViewLoadedMsg
506+
| ApplyDataExtensionYamlMessage;

extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ToDataExtensionsEditorMessage,
66
} from "../../pure/interface-types";
77
import {
8+
VSCodeButton,
89
VSCodeDataGrid,
910
VSCodeDataGridCell,
1011
VSCodeDataGridRow,
@@ -14,6 +15,8 @@ import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usag
1415
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
1516
import { MethodRow } from "./MethodRow";
1617
import { assertNever } from "../../pure/helpers-pure";
18+
import { vscode } from "../vscode-api";
19+
import { createDataExtensionYaml } from "../../data-extensions-editor/yaml";
1720
import { calculateSupportedPercentage } from "./supported";
1821

1922
export const DataExtensionsEditorContainer = styled.div`
@@ -88,6 +91,18 @@ export function DataExtensionsEditor(): JSX.Element {
8891
[],
8992
);
9093

94+
const onApplyClick = useCallback(() => {
95+
const yamlString = createDataExtensionYaml(
96+
externalApiUsages,
97+
modeledMethods,
98+
);
99+
100+
vscode.postMessage({
101+
t: "applyDataExtensionYaml",
102+
yaml: yamlString,
103+
});
104+
}, [externalApiUsages, modeledMethods]);
105+
91106
return (
92107
<DataExtensionsEditorContainer>
93108
{progress.maxStep > 0 && (
@@ -108,6 +123,7 @@ export function DataExtensionsEditor(): JSX.Element {
108123
</div>
109124
<div>
110125
<h3>External API modelling</h3>
126+
<VSCodeButton onClick={onApplyClick}>Apply</VSCodeButton>
111127
<VSCodeDataGrid>
112128
<VSCodeDataGridRow rowType="header">
113129
<VSCodeDataGridCell cellType="columnheader" gridColumn={1}>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { createDataExtensionYaml } from "../../../src/data-extensions-editor/yaml";
2+
3+
describe("createDataExtensionYaml", () => {
4+
it("creates the correct YAML file", () => {
5+
const yaml = createDataExtensionYaml(
6+
[
7+
{
8+
signature: "org.sql2o.Connection#createQuery(String)",
9+
packageName: "org.sql2o",
10+
typeName: "Connection",
11+
methodName: "createQuery",
12+
methodParameters: "(String)",
13+
supported: true,
14+
usages: [
15+
{
16+
label: "createQuery(...)",
17+
url: {
18+
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
19+
startLine: 15,
20+
startColumn: 13,
21+
endLine: 15,
22+
endColumn: 56,
23+
},
24+
},
25+
{
26+
label: "createQuery(...)",
27+
url: {
28+
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
29+
startLine: 26,
30+
startColumn: 13,
31+
endLine: 26,
32+
endColumn: 39,
33+
},
34+
},
35+
],
36+
},
37+
{
38+
signature: "org.sql2o.Query#executeScalar(Class)",
39+
packageName: "org.sql2o",
40+
typeName: "Query",
41+
methodName: "executeScalar",
42+
methodParameters: "(Class)",
43+
supported: true,
44+
usages: [
45+
{
46+
label: "executeScalar(...)",
47+
url: {
48+
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
49+
startLine: 15,
50+
startColumn: 13,
51+
endLine: 15,
52+
endColumn: 85,
53+
},
54+
},
55+
{
56+
label: "executeScalar(...)",
57+
url: {
58+
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
59+
startLine: 26,
60+
startColumn: 13,
61+
endLine: 26,
62+
endColumn: 68,
63+
},
64+
},
65+
],
66+
},
67+
],
68+
{
69+
"org.sql2o.Connection#createQuery(String)": {
70+
type: "sink",
71+
input: "Argument[0]",
72+
output: "",
73+
kind: "sql",
74+
},
75+
},
76+
);
77+
78+
expect(yaml).toEqual(`extensions:
79+
- addsTo:
80+
pack: codeql/java-all
81+
extensible: sourceModel
82+
data: []
83+
84+
- addsTo:
85+
pack: codeql/java-all
86+
extensible: sinkModel
87+
data:
88+
- ["org.sql2o","Connection",true,"createQuery","(String)","","Argument[0]","sql","manual"]
89+
90+
- addsTo:
91+
pack: codeql/java-all
92+
extensible: summaryModel
93+
data: []
94+
95+
- addsTo:
96+
pack: codeql/java-all
97+
extensible: neutralModel
98+
data: []
99+
`);
100+
});
101+
});

0 commit comments

Comments
 (0)