Merge remote-tracking branch 'origin/main' into koesie10/automodel-v2

This commit is contained in:
Koen Vlaswinkel
2023-07-27 09:18:59 +02:00
22 changed files with 566 additions and 585 deletions

View File

@@ -110,6 +110,11 @@ jobs:
run: |
npm run lint:scenarios
- name: Find deadcode
working-directory: extensions/ql-vscode
run: |
npm run find-deadcode
unit-test:
name: Unit Test
runs-on: ${{ matrix.os }}

View File

@@ -318,7 +318,6 @@ This requires running a MRVA query and seeing the results view.
1. Alphabetically
2. By number of results
3. By popularity
4. By most recent commit
9. Can filter repos
10. Shows correct statistics
1. Total number of results

View File

@@ -2,6 +2,7 @@
## [UNRELEASED]
- Remove "last updated" information and sorting from variant analysis results view. [#2637](https://github.com/github/vscode-codeql/pull/2637)
- Links to code on GitHub now include column numbers as well as line numbers. [#2406](https://github.com/github/vscode-codeql/pull/2406)
- No longer highlight trailing commas for jump to definition. [#2615](https://github.com/github/vscode-codeql/pull/2615)

View File

@@ -15,9 +15,6 @@ export const config: webpack.Configuration = {
devtool: isDevBuild ? "inline-source-map" : "source-map",
resolve: {
extensions: [".js", ".ts", ".tsx", ".json"],
fallback: {
path: require.resolve("path-browserify"),
},
},
module: {
rules: [

View File

@@ -31,7 +31,6 @@
"nanoid": "^3.2.0",
"node-fetch": "~2.6.7",
"p-queue": "^6.0.0",
"path-browserify": "^1.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"semver": "~7.5.2",
@@ -151,6 +150,7 @@
"ts-loader": "^9.4.2",
"ts-node": "^10.7.0",
"ts-protoc-gen": "^0.9.0",
"ts-unused-exports": "^9.0.5",
"typescript": "^5.0.2",
"webpack": "^5.76.0",
"webpack-cli": "^5.0.1"
@@ -30610,7 +30610,8 @@
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
"dev": true
},
"node_modules/path-dirname": {
"version": "1.0.2",
@@ -34708,6 +34709,100 @@
"protoc-gen-ts": "bin/protoc-gen-ts"
}
},
"node_modules/ts-unused-exports": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-9.0.5.tgz",
"integrity": "sha512-1XAXaH2i4Al/aZO06pWDn9MUgTN0KQi+fvWudiWfHUTHAav45gzrx7Xq6JAsu6+LoMlVoyGvNvZSPW3KTjDncA==",
"dev": true,
"dependencies": {
"chalk": "^4.0.0",
"tsconfig-paths": "^3.9.0"
},
"bin": {
"ts-unused-exports": "bin/ts-unused-exports"
},
"funding": {
"url": "https://github.com/pzavolinsky/ts-unused-exports?sponsor=1"
},
"peerDependencies": {
"typescript": ">=3.8.3"
},
"peerDependenciesMeta": {
"typescript": {
"optional": false
}
}
},
"node_modules/ts-unused-exports/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/ts-unused-exports/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/ts-unused-exports/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/ts-unused-exports/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/ts-unused-exports/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/ts-unused-exports/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tsconfig-paths": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
@@ -59211,7 +59306,8 @@
"path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
"dev": true
},
"path-dirname": {
"version": "1.0.2",
@@ -62374,6 +62470,67 @@
"integrity": "sha512-cFEUTY9U9o6C4DPPfMHk2ZUdIAKL91hZN1fyx5Stz3g56BDVOC7hk+r5fEMCAGaaIgi2akkT1a2hrxu1wo2Phg==",
"dev": true
},
"ts-unused-exports": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-9.0.5.tgz",
"integrity": "sha512-1XAXaH2i4Al/aZO06pWDn9MUgTN0KQi+fvWudiWfHUTHAav45gzrx7Xq6JAsu6+LoMlVoyGvNvZSPW3KTjDncA==",
"dev": true,
"requires": {
"chalk": "^4.0.0",
"tsconfig-paths": "^3.9.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"tsconfig-paths": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",

View File

@@ -350,13 +350,11 @@
"enum": [
"alphabetically",
"popularity",
"mostRecentCommit",
"numberOfResults"
],
"enumDescriptions": [
"Sort repositories alphabetically in the results view.",
"Sort repositories by popularity in the results view.",
"Sort repositories by most recent commit in the results view.",
"Sort repositories by number of results in the results view."
],
"description": "The default sorting order for repositories in the variant analysis results view."
@@ -1732,6 +1730,7 @@
"format": "prettier --write **/*.{ts,tsx} && eslint . --ext .ts,.tsx --fix",
"lint": "eslint . --ext .js,.ts,.tsx --max-warnings=0",
"lint:markdown": "markdownlint-cli2 \"../../**/*.{md,mdx}\" \"!**/node_modules/**\" \"!**/.vscode-test/**\" \"!**/build/cli/v*/**\"",
"find-deadcode": "ts-node scripts/find-deadcode.ts",
"format-staged": "lint-staged",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
@@ -1762,7 +1761,6 @@
"nanoid": "^3.2.0",
"node-fetch": "~2.6.7",
"p-queue": "^6.0.0",
"path-browserify": "^1.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"semver": "~7.5.2",
@@ -1882,6 +1880,7 @@
"ts-loader": "^9.4.2",
"ts-node": "^10.7.0",
"ts-protoc-gen": "^0.9.0",
"ts-unused-exports": "^9.0.5",
"typescript": "^5.0.2",
"webpack": "^5.76.0",
"webpack-cli": "^5.0.1"

View File

@@ -0,0 +1,47 @@
import { basename, join, relative, resolve } from "path";
import analyzeTsConfig from "ts-unused-exports";
import { containsPath, pathsEqual } from "../src/common/files";
import { exit } from "process";
function ignoreFile(file: string): boolean {
return (
containsPath("gulpfile.ts", file) ||
containsPath(join("src", "stories"), file) ||
pathsEqual(
join("test", "vscode-tests", "jest-runner-installed-extensions.ts"),
file,
) ||
basename(file) === "jest.config.ts" ||
basename(file) === "index.tsx" ||
basename(file) === "index.ts"
);
}
function main() {
const repositoryRoot = resolve(join(__dirname, ".."));
const result = analyzeTsConfig("tsconfig.deadcode.json");
let foundUnusedExports = false;
for (const [filepath, exportNameAndLocations] of Object.entries(result)) {
const relativeFilepath = relative(repositoryRoot, filepath);
if (ignoreFile(relativeFilepath)) {
continue;
}
foundUnusedExports = true;
console.log(relativeFilepath);
for (const exportNameAndLocation of exportNameAndLocations) {
console.log(` ${exportNameAndLocation.exportName}`);
}
console.log();
}
if (foundUnusedExports) {
exit(1);
}
}
main();

View File

@@ -500,13 +500,6 @@ interface SetExternalApiUsagesMessage {
externalApiUsages: ExternalApiUsage[];
}
export interface ShowProgressMessage {
t: "showProgress";
step: number;
maxStep: number;
message: string;
}
interface LoadModeledMethodsMessage {
t: "loadModeledMethods";
modeledMethods: Record<string, ModeledMethod>;
@@ -527,6 +520,10 @@ interface JumpToUsageMessage {
location: ResolvableLocationValue;
}
interface OpenDatabaseMessage {
t: "openDatabase";
}
interface OpenExtensionPackMessage {
t: "openExtensionPack";
}
@@ -558,7 +555,6 @@ interface ModelDependencyMessage {
export type ToDataExtensionsEditorMessage =
| SetExtensionPackStateMessage
| SetExternalApiUsagesMessage
| ShowProgressMessage
| LoadModeledMethodsMessage
| AddModeledMethodsMessage;
@@ -566,6 +562,7 @@ export type FromDataExtensionsEditorMessage =
| ViewLoadedMsg
| SwitchModeMessage
| RefreshExternalApiUsages
| OpenDatabaseMessage
| OpenExtensionPackMessage
| JumpToUsageMessage
| SaveModeledMethods

View File

@@ -14,11 +14,7 @@ import {
FromDataExtensionsEditorMessage,
ToDataExtensionsEditorMessage,
} from "../common/interface-types";
import {
ProgressCallback,
ProgressUpdate,
withProgress,
} from "../common/vscode/progress";
import { ProgressCallback, withProgress } from "../common/vscode/progress";
import { QueryRunner } from "../query-server";
import {
showAndLogExceptionWithTelemetry,
@@ -120,6 +116,13 @@ export class DataExtensionsEditorView extends AbstractWebview<
case "viewLoaded":
await this.onWebViewLoaded();
break;
case "openDatabase":
await this.app.commands.execute(
"revealInExplorer",
this.databaseItem.getSourceArchiveExplorerUri(),
);
break;
case "openExtensionPack":
await this.app.commands.execute(
@@ -238,255 +241,254 @@ export class DataExtensionsEditorView extends AbstractWebview<
}
protected async loadExternalApiUsages(): Promise<void> {
const cancellationTokenSource = new CancellationTokenSource();
await withProgress(
async (progress) => {
try {
const cancellationTokenSource = new CancellationTokenSource();
const queryResult = await runQuery(
this.mode === Mode.Framework
? "frameworkModeQuery"
: "applicationModeQuery",
{
cliServer: this.cliServer,
queryRunner: this.queryRunner,
databaseItem: this.databaseItem,
queryStorageDir: this.queryStorageDir,
progress: (update) => progress({ ...update, maxStep: 1500 }),
token: cancellationTokenSource.token,
},
);
if (!queryResult) {
return;
}
try {
const queryResult = await runQuery(
this.mode === Mode.Framework
? "frameworkModeQuery"
: "applicationModeQuery",
{
cliServer: this.cliServer,
queryRunner: this.queryRunner,
databaseItem: this.databaseItem,
queryStorageDir: this.queryStorageDir,
progress: (progressUpdate: ProgressUpdate) => {
void this.showProgress(progressUpdate, 1500);
},
token: cancellationTokenSource.token,
},
);
if (!queryResult) {
await this.clearProgress();
return;
}
progress({
message: "Decoding results",
step: 1100,
maxStep: 1500,
});
await this.showProgress({
message: "Decoding results",
step: 1100,
maxStep: 1500,
});
const bqrsChunk = await readQueryResults({
cliServer: this.cliServer,
bqrsPath: queryResult.outputDir.bqrsPath,
});
if (!bqrsChunk) {
return;
}
const bqrsChunk = await readQueryResults({
cliServer: this.cliServer,
bqrsPath: queryResult.outputDir.bqrsPath,
});
if (!bqrsChunk) {
await this.clearProgress();
return;
}
progress({
message: "Finalizing results",
step: 1450,
maxStep: 1500,
});
await this.showProgress({
message: "Finalizing results",
step: 1450,
maxStep: 1500,
});
const externalApiUsages = decodeBqrsToExternalApiUsages(bqrsChunk);
const externalApiUsages = decodeBqrsToExternalApiUsages(bqrsChunk);
await this.postMessage({
t: "setExternalApiUsages",
externalApiUsages,
});
await this.clearProgress();
} catch (err) {
void showAndLogExceptionWithTelemetry(
this.app.logger,
this.app.telemetry,
redactableError(
asError(err),
)`Failed to load external API usages: ${getErrorMessage(err)}`,
);
}
await this.postMessage({
t: "setExternalApiUsages",
externalApiUsages,
});
} catch (err) {
void showAndLogExceptionWithTelemetry(
this.app.logger,
this.app.telemetry,
redactableError(
asError(err),
)`Failed to load external API usages: ${getErrorMessage(err)}`,
);
}
},
{ cancellable: false },
);
}
protected async generateModeledMethods(): Promise<void> {
const tokenSource = new CancellationTokenSource();
await withProgress(
async (progress) => {
const tokenSource = new CancellationTokenSource();
let addedDatabase: DatabaseItem | undefined;
let addedDatabase: DatabaseItem | undefined;
// In application mode, we need the database of a specific library to generate
// the modeled methods. In framework mode, we'll use the current database.
if (this.mode === Mode.Application) {
addedDatabase = await this.promptImportDatabase((update) =>
this.showProgress(update),
);
if (!addedDatabase) {
return;
}
}
await this.showProgress({
step: 0,
maxStep: 4000,
message: "Generating modeled methods for library",
});
try {
await generateFlowModel({
cliServer: this.cliServer,
queryRunner: this.queryRunner,
queryStorageDir: this.queryStorageDir,
databaseItem: addedDatabase ?? this.databaseItem,
onResults: async (modeledMethods) => {
const modeledMethodsByName: Record<string, ModeledMethod> = {};
for (const modeledMethod of modeledMethods) {
modeledMethodsByName[modeledMethod.signature] = modeledMethod;
// In application mode, we need the database of a specific library to generate
// the modeled methods. In framework mode, we'll use the current database.
if (this.mode === Mode.Application) {
addedDatabase = await this.promptImportDatabase(progress);
if (!addedDatabase) {
return;
}
}
await this.postMessage({
t: "addModeledMethods",
modeledMethods: modeledMethodsByName,
progress({
step: 0,
maxStep: 4000,
message: "Generating modeled methods for library",
});
try {
await generateFlowModel({
cliServer: this.cliServer,
queryRunner: this.queryRunner,
queryStorageDir: this.queryStorageDir,
databaseItem: addedDatabase ?? this.databaseItem,
onResults: async (modeledMethods) => {
const modeledMethodsByName: Record<string, ModeledMethod> = {};
for (const modeledMethod of modeledMethods) {
modeledMethodsByName[modeledMethod.signature] = modeledMethod;
}
await this.postMessage({
t: "addModeledMethods",
modeledMethods: modeledMethodsByName,
});
},
progress,
token: tokenSource.token,
});
},
progress: (update) => this.showProgress(update),
token: tokenSource.token,
});
} catch (e: unknown) {
void showAndLogExceptionWithTelemetry(
this.app.logger,
this.app.telemetry,
redactableError(
asError(e),
)`Failed to generate flow model: ${getErrorMessage(e)}`,
);
}
} catch (e: unknown) {
void showAndLogExceptionWithTelemetry(
this.app.logger,
this.app.telemetry,
redactableError(
asError(e),
)`Failed to generate flow model: ${getErrorMessage(e)}`,
);
}
if (addedDatabase) {
// After the flow model has been generated, we can remove the temporary database
// which we used for generating the flow model.
await this.showProgress({
step: 3900,
maxStep: 4000,
message: "Removing temporary database",
});
await this.databaseManager.removeDatabaseItem(addedDatabase);
}
await this.clearProgress();
if (addedDatabase) {
// After the flow model has been generated, we can remove the temporary database
// which we used for generating the flow model.
progress({
step: 3900,
maxStep: 4000,
message: "Removing temporary database",
});
await this.databaseManager.removeDatabaseItem(addedDatabase);
}
},
{ cancellable: false },
);
}
private async generateModeledMethodsFromLlm(
externalApiUsages: ExternalApiUsage[],
modeledMethods: Record<string, ModeledMethod>,
): Promise<void> {
const maxStep = 3000;
await withProgress(async (progress) => {
const maxStep = 3000;
await this.showProgress({
step: 0,
maxStep,
message: "Retrieving usages",
progress({
step: 0,
maxStep,
message: "Retrieving usages",
});
let predictedModeledMethods: Record<string, ModeledMethod>;
if (useLlmGenerationV2()) {
const usages = await runAutoModelQueries({
mode: this.mode,
cliServer: this.cliServer,
queryRunner: this.queryRunner,
queryStorageDir: this.queryStorageDir,
databaseItem: this.databaseItem,
progress: (update) => progress({ ...update, maxStep }),
});
if (!usages) {
return;
}
progress({
step: 1800,
maxStep,
message: "Creating request",
});
const request = await createAutoModelV2Request(this.mode, usages);
progress({
step: 2000,
maxStep,
message: "Sending request",
});
const response = await this.callAutoModelApiV2(request);
if (!response) {
return;
}
progress({
step: 2500,
maxStep,
message: "Parsing response",
});
const models = loadYaml(response.models, {
filename: "auto-model.yml",
});
const modeledMethods = loadDataExtensionYaml(models);
if (!modeledMethods) {
return;
}
predictedModeledMethods = modeledMethods;
} else {
const usages = await getAutoModelUsages({
cliServer: this.cliServer,
queryRunner: this.queryRunner,
queryStorageDir: this.queryStorageDir,
databaseItem: this.databaseItem,
progress: (update) => progress({ ...update, maxStep }),
});
progress({
step: 1800,
maxStep,
message: "Creating request",
});
const request = createAutoModelRequest(
this.databaseItem.language,
externalApiUsages,
modeledMethods,
usages,
this.mode,
);
progress({
step: 2000,
maxStep,
message: "Sending request",
});
const response = await this.callAutoModelApi(request);
if (!response) {
return;
}
progress({
step: 2500,
maxStep,
message: "Parsing response",
});
predictedModeledMethods = parsePredictedClassifications(
response.predicted || [],
);
}
progress({
step: 2800,
maxStep,
message: "Applying results",
});
await this.postMessage({
t: "addModeledMethods",
modeledMethods: predictedModeledMethods,
});
});
let predictedModeledMethods: Record<string, ModeledMethod>;
if (useLlmGenerationV2()) {
const usages = await runAutoModelQueries({
mode: this.mode,
cliServer: this.cliServer,
queryRunner: this.queryRunner,
queryStorageDir: this.queryStorageDir,
databaseItem: this.databaseItem,
progress: (update) => this.showProgress(update, maxStep),
});
if (!usages) {
return;
}
await this.showProgress({
step: 1800,
maxStep,
message: "Creating request",
});
const request = await createAutoModelV2Request(this.mode, usages);
await this.showProgress({
step: 2000,
maxStep,
message: "Sending request",
});
const response = await this.callAutoModelApiV2(request);
if (!response) {
return;
}
await this.showProgress({
step: 2500,
maxStep,
message: "Parsing response",
});
const models = loadYaml(response.models, {
filename: "auto-model.yml",
});
const modeledMethods = loadDataExtensionYaml(models);
if (!modeledMethods) {
return;
}
predictedModeledMethods = modeledMethods;
} else {
const usages = await getAutoModelUsages({
cliServer: this.cliServer,
queryRunner: this.queryRunner,
queryStorageDir: this.queryStorageDir,
databaseItem: this.databaseItem,
progress: (update) => this.showProgress(update, maxStep),
});
await this.showProgress({
step: 1800,
maxStep,
message: "Creating request",
});
const request = createAutoModelRequest(
this.databaseItem.language,
externalApiUsages,
modeledMethods,
usages,
this.mode,
);
await this.showProgress({
step: 2000,
maxStep,
message: "Sending request",
});
const response = await this.callAutoModelApi(request);
if (!response) {
return;
}
await this.showProgress({
step: 2500,
maxStep,
message: "Parsing response",
});
predictedModeledMethods = parsePredictedClassifications(
response.predicted || [],
);
}
await this.showProgress({
step: 2800,
maxStep,
message: "Applying results",
});
await this.postMessage({
t: "addModeledMethods",
modeledMethods: predictedModeledMethods,
});
await this.clearProgress();
}
private async modelDependency(): Promise<void> {
@@ -547,46 +549,12 @@ export class DataExtensionsEditorView extends AbstractWebview<
return addedDatabase;
}
/*
* Progress in this class is a bit weird. Most of the progress is based on running the query.
* Query progress is always between 0 and 1000. However, we still have some steps that need
* to be done after the query has finished. Therefore, the maximum step is 1500. This captures
* that there's 1000 steps of the query progress since that takes the most time, and then
* an additional 500 steps for the rest of the work. The progress doesn't need to be 100%
* accurate, so this is just a rough estimate.
*
* For generating the modeled methods for an external library, the max step is 4000. This is
* based on the following steps:
* - 1000 for the summary model
* - 1000 for the sink model
* - 1000 for the source model
* - 1000 for the neutral model
*/
private async showProgress(update: ProgressUpdate, maxStep?: number) {
await this.postMessage({
t: "showProgress",
step: update.step,
maxStep: maxStep ?? update.maxStep,
message: update.message,
});
}
private async clearProgress() {
await this.showProgress({
step: 0,
maxStep: 0,
message: "",
});
}
private async callAutoModelApi(
request: ModelRequest,
): Promise<ModelResponse | null> {
try {
return await autoModel(this.app.credentials, request);
} catch (e) {
await this.clearProgress();
if (e instanceof RequestError && e.status === 429) {
void showAndLogExceptionWithTelemetry(
this.app.logger,
@@ -606,8 +574,6 @@ export class DataExtensionsEditorView extends AbstractWebview<
try {
return await autoModelV2(this.app.credentials, request);
} catch (e) {
await this.clearProgress();
if (e instanceof RequestError && e.status === 429) {
void showAndLogExceptionWithTelemetry(
this.app.logger,

View File

@@ -5,7 +5,7 @@ import { extLogger } from "../common/logging/vscode";
import { promises } from "fs-extra";
import { BaseLogger } from "../common/logging";
export type LockFileForStandardQueryResult = {
type LockFileForStandardQueryResult = {
cleanup?: () => Promise<void>;
};

View File

@@ -1,20 +0,0 @@
import * as React from "react";
import { Meta, StoryFn } from "@storybook/react";
import { LastUpdated as LastUpdatedComponent } from "../../view/common/LastUpdated";
export default {
title: "Last Updated",
component: LastUpdatedComponent,
} as Meta<typeof LastUpdatedComponent>;
const Template: StoryFn<typeof LastUpdatedComponent> = (args) => (
<LastUpdatedComponent {...args} />
);
export const LastUpdated = Template.bind({});
LastUpdated.args = {
lastUpdated: new Date(Date.now() - 3_600_000).toISOString(), // 1 hour ago
};

View File

@@ -1,5 +1,4 @@
import { Repository, RepositoryWithMetadata } from "./repository";
import { parseDate } from "../../common/date";
import { assertNever } from "../../common/helpers-pure";
export enum FilterKey {
@@ -10,7 +9,6 @@ export enum FilterKey {
export enum SortKey {
Alphabetically = "alphabetically",
Popularity = "popularity",
MostRecentCommit = "mostRecentCommit",
NumberOfResults = "numberOfResults",
}
@@ -81,16 +79,6 @@ export function compareRepository(
}
}
// Newest to oldest
if (filterSortState?.sortKey === SortKey.MostRecentCommit) {
const lastUpdated =
(parseDate(right.updatedAt)?.getTime() ?? 0) -
(parseDate(left.updatedAt)?.getTime() ?? 0);
if (lastUpdated !== 0) {
return lastUpdated;
}
}
// Fall back on name compare. Use en-US because the repository name does not contain
// special characters due to restrictions in GitHub owner/repository names.
return left.fullName.localeCompare(right.fullName, "en-US", {

View File

@@ -1,42 +0,0 @@
import * as React from "react";
import { useMemo } from "react";
import styled from "styled-components";
import { parseDate } from "../../common/date";
import { humanizeRelativeTime } from "../../common/time";
import { Codicon } from "./icon";
const IconContainer = styled.span`
flex-grow: 0;
text-align: right;
margin-right: 0;
`;
const Duration = styled.span`
display: inline-block;
text-align: left;
width: 8em;
margin-left: 0.5em;
`;
type Props = {
lastUpdated?: string | null;
};
export const LastUpdated = ({ lastUpdated }: Props) => {
const date = useMemo(() => parseDate(lastUpdated), [lastUpdated]);
if (!date) {
return null;
}
return (
<div>
<IconContainer>
<Codicon name="repo-push" label="Most recent commit" />
</IconContainer>
<Duration>{humanizeRelativeTime(date.getTime() - Date.now())}</Duration>
</div>
);
};

View File

@@ -1,10 +1,11 @@
import * as React from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ToDataExtensionsEditorMessage } from "../../common/interface-types";
import {
ShowProgressMessage,
ToDataExtensionsEditorMessage,
} from "../../common/interface-types";
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react";
VSCodeButton,
VSCodeCheckbox,
VSCodeTag,
} from "@vscode/webview-ui-toolkit/react";
import styled from "styled-components";
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
@@ -12,7 +13,6 @@ import { assertNever } from "../../common/helpers-pure";
import { vscode } from "../vscode-api";
import { calculateModeledPercentage } from "../../data-extensions-editor/shared/modeled-percentage";
import { LinkIconButton } from "../variant-analysis/LinkIconButton";
import { ViewTitle } from "../common";
import { DataExtensionEditorViewState } from "../../data-extensions-editor/shared/view-state";
import { ModeledMethodsList } from "./ModeledMethodsList";
import { percentFormatter } from "./formatters";
@@ -30,12 +30,35 @@ const DataExtensionsEditorContainer = styled.div`
margin-top: 1rem;
`;
const DetailsContainer = styled.div`
const HeaderContainer = styled.div`
display: flex;
flex-direction: row;
align-items: end;
`;
const HeaderColumn = styled.div`
display: flex;
flex-direction: column;
gap: 0.5em;
`;
const HeaderSpacer = styled.div`
flex-grow: 1;
`;
const HeaderRow = styled.div`
display: flex;
flex-direction: row;
gap: 1em;
align-items: center;
`;
const ViewTitle = styled.h1`
font-size: 2em;
font-weight: 500;
margin: 0;
`;
const EditorContainer = styled.div`
margin-top: 1rem;
`;
@@ -46,17 +69,6 @@ const ButtonsContainer = styled.div`
margin-bottom: 1rem;
`;
type ProgressBarProps = {
completion: number;
};
const ProgressBar = styled.div<ProgressBarProps>`
height: 10px;
width: ${(props) => props.completion * 100}%;
background-color: var(--vscode-progressBar-background);
`;
type Props = {
initialViewState?: DataExtensionEditorViewState;
initialExternalApiUsages?: ExternalApiUsage[];
@@ -82,11 +94,6 @@ export function DataExtensionsEditor({
const [modeledMethods, setModeledMethods] = useState<
Record<string, ModeledMethod>
>(initialModeledMethods);
const [progress, setProgress] = useState<Omit<ShowProgressMessage, "t">>({
step: 0,
maxStep: 0,
message: "",
});
useEffect(() => {
const listener = (evt: MessageEvent) => {
@@ -99,9 +106,6 @@ export function DataExtensionsEditor({
case "setExternalApiUsages":
setExternalApiUsages(msg.externalApiUsages);
break;
case "showProgress":
setProgress(msg);
break;
case "loadModeledMethods":
setModeledMethods((oldModeledMethods) => {
return {
@@ -150,8 +154,6 @@ export function DataExtensionsEditor({
[externalApiUsages],
);
const unModeledPercentage = 100 - modeledPercentage;
const onChange = useCallback(
(modelName: string, method: ExternalApiUsage, model: ModeledMethod) => {
setModeledMethods((oldModeledMethods) => ({
@@ -208,14 +210,6 @@ export function DataExtensionsEditor({
});
}, []);
const onGenerateAllFromLlmClick = useCallback(() => {
vscode.postMessage({
t: "generateExternalApiFromLlm",
externalApiUsages,
modeledMethods,
});
}, [externalApiUsages, modeledMethods]);
const onModelDependencyClick = useCallback(() => {
vscode.postMessage({
t: "modelDependency",
@@ -236,6 +230,12 @@ export function DataExtensionsEditor({
[],
);
const onOpenDatabaseClick = useCallback(() => {
vscode.postMessage({
t: "openDatabase",
});
}, []);
const onOpenExtensionPackClick = useCallback(() => {
vscode.postMessage({
t: "openExtensionPack",
@@ -252,91 +252,81 @@ export function DataExtensionsEditor({
});
}, [viewState?.mode]);
if (viewState === undefined) {
if (viewState === undefined || externalApiUsages.length === 0) {
return <LoadingContainer>Loading...</LoadingContainer>;
}
return (
<DataExtensionsEditorContainer>
{progress.maxStep > 0 && (
<p>
<ProgressBar completion={progress.step / progress.maxStep} />{" "}
{progress.message}
</p>
)}
{externalApiUsages.length > 0 && (
<>
<ViewTitle>
{getLanguageDisplayName(viewState.extensionPack.language)}
</ViewTitle>
<DetailsContainer>
<HeaderContainer>
<HeaderColumn>
<HeaderRow>
<ViewTitle>
{getLanguageDisplayName(viewState.extensionPack.language)}
</ViewTitle>
<VSCodeTag>
{percentFormatter.format(modeledPercentage / 100)} modeled
</VSCodeTag>
</HeaderRow>
<HeaderRow>
<>{viewState.extensionPack.name}</>
</HeaderRow>
<HeaderRow>
<LinkIconButton onClick={onOpenDatabaseClick}>
<span slot="start" className="codicon codicon-package"></span>
Open database
</LinkIconButton>
<LinkIconButton onClick={onOpenExtensionPackClick}>
<span slot="start" className="codicon codicon-package"></span>
{viewState.extensionPack.name}
Open extension pack
</LinkIconButton>
<div>
{percentFormatter.format(modeledPercentage / 100)} modeled
</div>
<div>
{percentFormatter.format(unModeledPercentage / 100)} unmodeled
</div>
{viewState.enableFrameworkMode && (
<>
<div>
Mode:{" "}
{viewState.mode === Mode.Framework
? "Framework"
: "Application"}
</div>
<div>
<LinkIconButton onClick={onSwitchModeClick}>
<span
slot="start"
className="codicon codicon-library"
></span>
Switch mode
</LinkIconButton>
</div>
</>
)}
</DetailsContainer>
<EditorContainer>
<ButtonsContainer>
<VSCodeButton onClick={onSaveAllClick}>Apply</VSCodeButton>
{viewState.enableFrameworkMode && (
<VSCodeButton appearance="secondary" onClick={onRefreshClick}>
Refresh
</VSCodeButton>
)}
<VSCodeButton onClick={onGenerateFromSourceClick}>
<LinkIconButton onClick={onSwitchModeClick}>
<span slot="start" className="codicon codicon-library"></span>
{viewState.mode === Mode.Framework
? "Generate"
: "Download and generate"}
</VSCodeButton>
{viewState.showLlmButton && (
<>
<VSCodeButton onClick={onGenerateAllFromLlmClick}>
Generate using LLM
</VSCodeButton>
</>
)}
</ButtonsContainer>
<ModeledMethodsList
externalApiUsages={externalApiUsages}
modeledMethods={modeledMethods}
modifiedSignatures={modifiedSignatures}
viewState={viewState}
onChange={onChange}
onSaveModelClick={onSaveModelClick}
onGenerateFromLlmClick={onGenerateFromLlmClick}
onGenerateFromSourceClick={onGenerateFromSourceClick}
onModelDependencyClick={onModelDependencyClick}
/>
</EditorContainer>
</>
)}
? "Model as application"
: "Model as dependency"}
</LinkIconButton>
)}
</HeaderRow>
</HeaderColumn>
<HeaderSpacer />
<HeaderColumn>
<VSCodeCheckbox>Hide modeled APIs</VSCodeCheckbox>
</HeaderColumn>
</HeaderContainer>
<EditorContainer>
<ButtonsContainer>
<VSCodeButton
onClick={onSaveAllClick}
disabled={modifiedSignatures.size === 0}
>
Save all
</VSCodeButton>
{viewState.enableFrameworkMode && (
<VSCodeButton appearance="secondary" onClick={onRefreshClick}>
Refresh
</VSCodeButton>
)}
{viewState.mode === Mode.Framework && (
<VSCodeButton onClick={onGenerateFromSourceClick}>
Generate
</VSCodeButton>
)}
</ButtonsContainer>
<ModeledMethodsList
externalApiUsages={externalApiUsages}
modeledMethods={modeledMethods}
modifiedSignatures={modifiedSignatures}
viewState={viewState}
onChange={onChange}
onSaveModelClick={onSaveModelClick}
onGenerateFromLlmClick={onGenerateFromLlmClick}
onGenerateFromSourceClick={onGenerateFromSourceClick}
onModelDependencyClick={onModelDependencyClick}
/>
</EditorContainer>
</DataExtensionsEditorContainer>
);
}

View File

@@ -270,7 +270,7 @@ function UnmodelableMethodRow(props: Props) {
<MethodClassifications externalApiUsage={externalApiUsage} />
</ApiOrMethodCell>
<VSCodeDataGridCell gridColumn="span 4">
Method modeled by CodeQL or a different extension pack
Method already modeled
</VSCodeDataGridCell>
</VSCodeDataGridRow>
);

View File

@@ -2,7 +2,7 @@ import * as React from "react";
import * as Sarif from "sarif";
import { isLineColumnLoc, isWholeFileLoc } from "../../../common/bqrs-utils";
import { parseSarifLocation } from "../../../common/sarif-utils";
import { basename } from "path";
import { basename } from "../../../common/path";
import { useMemo } from "react";
import { Location } from "./Location";

View File

@@ -27,8 +27,7 @@ import {
} from "./result-table-utils";
import { vscode } from "../vscode-api";
import { sendTelemetry } from "../common/telemetry";
const FILE_PATH_REGEX = /^(?:.+[\\/])*(.+)$/;
import { basename } from "../../common/path";
/**
* Properties for the `ResultTables` component.
@@ -302,7 +301,7 @@ export class ResultTables extends React.Component<
openFile(this.props.queryPath);
sendTelemetry("local-results-open-query-file");
};
const fileName = FILE_PATH_REGEX.exec(this.props.queryPath)?.[1] || "query";
const fileName = basename(this.props.queryPath);
return (
<span className="vscode-codeql__table-selection-pagination">

View File

@@ -24,7 +24,6 @@ import {
import { vscode } from "../vscode-api";
import { AnalyzedRepoItemContent } from "./AnalyzedRepoItemContent";
import StarCount from "../common/StarCount";
import { LastUpdated } from "../common/LastUpdated";
import { useTelemetryOnChange } from "../common/telemetry";
import { DeterminateProgressRing } from "../common/DeterminateProgressRing";
@@ -297,7 +296,6 @@ export const RepoRow = ({
<div>
<StarCount starCount={repository.stargazersCount} />
</div>
<LastUpdated lastUpdated={repository.updatedAt} />
</MetadataContainer>
</TitleContainer>
{isExpanded && expandableContentLoaded && (

View File

@@ -34,9 +34,6 @@ export const RepositoriesSort = ({ value, onChange, className }: Props) => {
Number of results
</VSCodeOption>
<VSCodeOption value={SortKey.Popularity}>Popularity</VSCodeOption>
<VSCodeOption value={SortKey.MostRecentCommit}>
Most recent commit
</VSCodeOption>
</Dropdown>
);
};

View File

@@ -38,10 +38,7 @@ describe(RepoRow.name, () => {
expect(
screen.queryByRole("img", {
// There should not be any icons, except for the icons which are always shown
name: (name) =>
!["expand", "stars count", "most recent commit"].includes(
name.toLowerCase(),
),
name: (name) => !["expand", "stars count"].includes(name.toLowerCase()),
}),
).not.toBeInTheDocument();
@@ -279,26 +276,7 @@ describe(RepoRow.name, () => {
).toBeInTheDocument();
});
it("shows updated at", () => {
render({
repository: {
...createMockRepositoryWithMetadata(),
// 1 month ago
updatedAt: new Date(
Date.now() - 1000 * 60 * 60 * 24 * 30,
).toISOString(),
},
});
expect(screen.getByText("last month")).toBeInTheDocument();
expect(
screen.getByRole("img", {
name: "Most recent commit",
}),
).toBeInTheDocument();
});
it("does not show star count and updated at when unknown", () => {
it("does not show star count when unknown", () => {
render({
repository: {
id: undefined,
@@ -312,11 +290,6 @@ describe(RepoRow.name, () => {
name: "Stars count",
}),
).not.toBeInTheDocument();
expect(
screen.queryByRole("img", {
name: "Most recent commit",
}),
).not.toBeInTheDocument();
});
it("can expand the repo item", async () => {

View File

@@ -204,55 +204,6 @@ describe(compareRepository.name, () => {
).toBeLessThan(0);
});
});
describe("when sort key is 'Most recent commit'", () => {
const sorter = compareRepository({
...permissiveFilterSortState,
sortKey: SortKey.MostRecentCommit,
});
const left = {
fullName: "github/galaxy",
updatedAt: "2020-01-01T00:00:00Z",
};
const right = {
fullName: "github/world",
updatedAt: "2021-01-01T00:00:00Z",
};
it("compares correctly", () => {
expect(sorter(left, right)).toBeGreaterThan(0);
});
it("compares the inverse correctly", () => {
expect(sorter(right, left)).toBeLessThan(0);
});
it("compares equal values correctly", () => {
expect(sorter(left, left)).toBe(0);
});
it("compares equal single values correctly", () => {
expect(
sorter(left, {
...right,
updatedAt: left.updatedAt,
}),
).toBeLessThan(0);
});
it("compares missing single values correctly", () => {
expect(
sorter(
{
...left,
updatedAt: undefined,
},
right,
),
).toBeGreaterThan(0);
});
});
});
describe(compareWithResults.name, () => {
@@ -303,32 +254,6 @@ describe(compareWithResults.name, () => {
});
});
describe("when sort key is 'Most recent commit'", () => {
const sorter = compareWithResults({
...permissiveFilterSortState,
sortKey: SortKey.MostRecentCommit,
});
const left = {
repository: {
id: 11,
fullName: "github/galaxy",
updatedAt: "2020-01-01T00:00:00Z",
},
};
const right = {
repository: {
id: 12,
fullName: "github/world",
updatedAt: "2021-01-01T00:00:00Z",
},
};
it("compares correctly", () => {
expect(sorter(left, right)).toBeGreaterThan(0);
});
});
describe("when sort key is results count", () => {
const sorter = compareWithResults({
...permissiveFilterSortState,

View File

@@ -0,0 +1,5 @@
{
"extends": "./tsconfig.json",
"include": ["**/*.ts*"],
"exclude": ["node_modules"]
}