Merge branch 'main' into koesie10/replace-faker-uuid

This commit is contained in:
Koen Vlaswinkel
2023-06-20 16:22:51 +02:00
committed by GitHub
29 changed files with 343 additions and 394 deletions

View File

@@ -47,17 +47,5 @@ export function decodeBqrsToExternalApiUsages(
method.usages.push(usage);
});
const externalApiUsages = Array.from(methodsByApiName.values());
externalApiUsages.sort((a, b) => {
// Sort first by supported, putting unmodeled methods first.
if (a.supported && !b.supported) {
return 1;
}
if (!a.supported && b.supported) {
return -1;
}
// Then sort by number of usages descending
return b.usages.length - a.usages.length;
});
return externalApiUsages;
return Array.from(methodsByApiName.values());
}

View File

@@ -300,7 +300,6 @@ export class DataExtensionsEditorView extends AbstractWebview<
this.app.workspaceStoragePath ?? this.app.globalStoragePath,
this.app.credentials,
(update) => this.showProgress(update),
tokenSource.token,
this.cliServer,
);
if (!database) {
@@ -354,16 +353,12 @@ export class DataExtensionsEditorView extends AbstractWebview<
// After the flow model has been generated, we can remove the temporary database
// which we used for generating the flow model.
await this.databaseManager.removeDatabaseItem(
() =>
this.showProgress({
step: 3900,
maxStep: 4000,
message: "Removing temporary database",
}),
tokenSource.token,
database,
);
await this.showProgress({
step: 3900,
maxStep: 4000,
message: "Removing temporary database",
});
await this.databaseManager.removeDatabaseItem(database);
await this.clearProgress();
}

View File

@@ -1,7 +1,7 @@
import fetch, { Response } from "node-fetch";
import { zip } from "zip-a-folder";
import { Open } from "unzipper";
import { Uri, CancellationToken, window, InputBoxOptions } from "vscode";
import { Uri, window, InputBoxOptions } from "vscode";
import { CodeQLCliServer } from "../codeql-cli/cli";
import {
ensureDir,
@@ -44,7 +44,6 @@ export async function promptImportInternetDatabase(
databaseManager: DatabaseManager,
storagePath: string,
progress: ProgressCallback,
token: CancellationToken,
cli?: CodeQLCliServer,
): Promise<DatabaseItem | undefined> {
const databaseUrl = await window.showInputBox({
@@ -63,7 +62,6 @@ export async function promptImportInternetDatabase(
storagePath,
undefined,
progress,
token,
cli,
);
@@ -86,7 +84,6 @@ export async function promptImportInternetDatabase(
* @param storagePath where to store the unzipped database.
* @param credentials the credentials to use to authenticate with GitHub
* @param progress the progress callback
* @param token the cancellation token
* @param cli the CodeQL CLI server
*/
export async function promptImportGithubDatabase(
@@ -95,7 +92,6 @@ export async function promptImportGithubDatabase(
storagePath: string,
credentials: Credentials | undefined,
progress: ProgressCallback,
token: CancellationToken,
cli?: CodeQLCliServer,
): Promise<DatabaseItem | undefined> {
const githubRepo = await askForGitHubRepo(progress);
@@ -109,7 +105,6 @@ export async function promptImportGithubDatabase(
storagePath,
credentials,
progress,
token,
cli,
);
@@ -157,7 +152,6 @@ export async function askForGitHubRepo(
* @param storagePath where to store the unzipped database.
* @param credentials the credentials to use to authenticate with GitHub
* @param progress the progress callback
* @param token the cancellation token
* @param cli the CodeQL CLI server
* @param language the language to download. If undefined, the user will be prompted to choose a language.
**/
@@ -167,7 +161,6 @@ export async function downloadGitHubDatabase(
storagePath: string,
credentials: Credentials | undefined,
progress: ProgressCallback,
token: CancellationToken,
cli?: CodeQLCliServer,
language?: string,
): Promise<DatabaseItem | undefined> {
@@ -213,7 +206,6 @@ export async function downloadGitHubDatabase(
storagePath,
`${owner}/${name}`,
progress,
token,
cli,
);
}
@@ -231,7 +223,6 @@ export async function importArchiveDatabase(
databaseManager: DatabaseManager,
storagePath: string,
progress: ProgressCallback,
token: CancellationToken,
cli?: CodeQLCliServer,
): Promise<DatabaseItem | undefined> {
try {
@@ -242,7 +233,6 @@ export async function importArchiveDatabase(
storagePath,
undefined,
progress,
token,
cli,
);
if (item) {
@@ -275,7 +265,6 @@ export async function importArchiveDatabase(
* @param storagePath where to store the unzipped database.
* @param nameOverride a name for the database that overrides the default
* @param progress callback to send progress messages to
* @param token cancellation token
*/
async function databaseArchiveFetcher(
databaseUrl: string,
@@ -284,7 +273,6 @@ async function databaseArchiveFetcher(
storagePath: string,
nameOverride: string | undefined,
progress: ProgressCallback,
token: CancellationToken,
cli?: CodeQLCliServer,
): Promise<DatabaseItem> {
progress({
@@ -327,8 +315,6 @@ async function databaseArchiveFetcher(
const makeSelected = true;
const item = await databaseManager.openDatabase(
progress,
token,
Uri.file(dbPath),
makeSelected,
nameOverride,

View File

@@ -314,7 +314,7 @@ export class DatabaseUI extends DisposableObject {
private async handleSetDefaultTourDatabase(): Promise<void> {
return withProgress(
async (progress, token) => {
async () => {
try {
if (!workspace.workspaceFolders?.length) {
throw new Error("No workspace folder is open.");
@@ -332,8 +332,6 @@ export class DatabaseUI extends DisposableObject {
const isTutorialDatabase = true;
await this.databaseManager.openDatabase(
progress,
token,
uri,
makeSelected,
nameOverride,
@@ -485,13 +483,12 @@ export class DatabaseUI extends DisposableObject {
private async handleChooseDatabaseInternet(): Promise<void> {
return withProgress(
async (progress, token) => {
async (progress) => {
await promptImportInternetDatabase(
this.app.commands,
this.databaseManager,
this.storagePath,
progress,
token,
this.queryServer?.cliServer,
);
},
@@ -503,7 +500,7 @@ export class DatabaseUI extends DisposableObject {
private async handleChooseDatabaseGithub(): Promise<void> {
return withProgress(
async (progress, token) => {
async (progress) => {
const credentials = isCanary() ? this.app.credentials : undefined;
await promptImportGithubDatabase(
@@ -512,7 +509,6 @@ export class DatabaseUI extends DisposableObject {
this.storagePath,
credentials,
progress,
token,
this.queryServer?.cliServer,
);
},
@@ -608,14 +604,13 @@ export class DatabaseUI extends DisposableObject {
private async handleClearCache(): Promise<void> {
return withProgress(
async (progress, token) => {
async (_progress, token) => {
if (
this.queryServer !== undefined &&
this.databaseManager.currentDatabaseItem !== undefined
) {
await this.queryServer.clearCacheInDatabase(
this.databaseManager.currentDatabaseItem,
progress,
token,
);
}
@@ -633,7 +628,7 @@ export class DatabaseUI extends DisposableObject {
private async handleSetCurrentDatabase(uri: Uri): Promise<void> {
return withProgress(
async (progress, token) => {
async (progress) => {
try {
// Assume user has selected an archive if the file has a .zip extension
if (uri.path.endsWith(".zip")) {
@@ -643,11 +638,10 @@ export class DatabaseUI extends DisposableObject {
this.databaseManager,
this.storagePath,
progress,
token,
this.queryServer?.cliServer,
);
} else {
await this.databaseManager.openDatabase(progress, token, uri);
await this.databaseManager.openDatabase(uri);
}
} catch (e) {
// rethrow and let this be handled by default error handling.
@@ -668,10 +662,10 @@ export class DatabaseUI extends DisposableObject {
databaseItems: DatabaseItem[],
): Promise<void> {
return withProgress(
async (progress, token) => {
async () => {
await Promise.all(
databaseItems.map((dbItem) =>
this.databaseManager.removeDatabaseItem(progress, token, dbItem),
this.databaseManager.removeDatabaseItem(dbItem),
),
);
},
@@ -758,15 +752,11 @@ export class DatabaseUI extends DisposableObject {
return await withInheritedProgress(
progress,
async (progress, token) => {
async (progress) => {
if (byFolder) {
const fixedUri = await this.fixDbUri(uri);
// we are selecting a database folder
return await this.databaseManager.openDatabase(
progress,
token,
fixedUri,
);
return await this.databaseManager.openDatabase(fixedUri);
} else {
// we are selecting a database archive. Must unzip into a workspace-controlled area
// before importing.
@@ -776,7 +766,6 @@ export class DatabaseUI extends DisposableObject {
this.databaseManager,
this.storagePath,
progress,
token,
this.queryServer?.cliServer,
);
}

View File

@@ -104,8 +104,6 @@ export class DatabaseManager extends DisposableObject {
* databases.
*/
public async openDatabase(
progress: ProgressCallback,
token: vscode.CancellationToken,
uri: vscode.Uri,
makeSelected = true,
displayName?: string,
@@ -115,9 +113,7 @@ export class DatabaseManager extends DisposableObject {
return await this.addExistingDatabaseItem(
databaseItem,
progress,
makeSelected,
token,
isTutorialDatabase,
);
}
@@ -130,9 +126,7 @@ export class DatabaseManager extends DisposableObject {
*/
private async addExistingDatabaseItem(
databaseItem: DatabaseItemImpl,
progress: ProgressCallback,
makeSelected: boolean,
token: vscode.CancellationToken,
isTutorialDatabase?: boolean,
): Promise<DatabaseItem> {
const existingItem = this.findDatabaseItem(databaseItem.databaseUri);
@@ -143,7 +137,7 @@ export class DatabaseManager extends DisposableObject {
return existingItem;
}
await this.addDatabaseItem(progress, token, databaseItem);
await this.addDatabaseItem(databaseItem);
if (makeSelected) {
await this.setCurrentDatabaseItem(databaseItem);
}
@@ -260,14 +254,11 @@ export class DatabaseManager extends DisposableObject {
}
}
private async reregisterDatabases(
progress: ProgressCallback,
token: vscode.CancellationToken,
) {
private async reregisterDatabases(progress: ProgressCallback) {
let completed = 0;
await Promise.all(
this._databaseItems.map(async (databaseItem) => {
await this.registerDatabase(progress, token, databaseItem);
await this.registerDatabase(databaseItem);
completed++;
progress({
maxStep: this._databaseItems.length,
@@ -324,8 +315,6 @@ export class DatabaseManager extends DisposableObject {
}
private async createDatabaseItemFromPersistedState(
progress: ProgressCallback,
token: vscode.CancellationToken,
state: PersistedDatabaseItem,
): Promise<DatabaseItemImpl> {
let displayName: string | undefined = undefined;
@@ -356,12 +345,12 @@ export class DatabaseManager extends DisposableObject {
// Avoid persisting the database state after adding since that should happen only after
// all databases have been added.
await this.addDatabaseItem(progress, token, item, false);
await this.addDatabaseItem(item, false);
return item;
}
public async loadPersistedState(): Promise<void> {
return withProgress(async (progress, token) => {
return withProgress(async (progress) => {
const currentDatabaseUri =
this.ctx.workspaceState.get<string>(CURRENT_DB);
const databases = this.ctx.workspaceState.get<PersistedDatabaseItem[]>(
@@ -388,13 +377,11 @@ export class DatabaseManager extends DisposableObject {
});
const databaseItem = await this.createDatabaseItemFromPersistedState(
progress,
token,
database,
);
try {
await this.refreshDatabase(databaseItem);
await this.registerDatabase(progress, token, databaseItem);
await this.registerDatabase(databaseItem);
if (currentDatabaseUri === database.uri) {
await this.setCurrentDatabaseItem(databaseItem, true);
}
@@ -489,8 +476,6 @@ export class DatabaseManager extends DisposableObject {
}
private async addDatabaseItem(
progress: ProgressCallback,
token: vscode.CancellationToken,
item: DatabaseItemImpl,
updatePersistedState = true,
) {
@@ -504,7 +489,7 @@ export class DatabaseManager extends DisposableObject {
// Database items reconstituted from persisted state
// will not have their contents yet.
if (item.contents?.datasetUri) {
await this.registerDatabase(progress, token, item);
await this.registerDatabase(item);
}
// note that we use undefined as the item in order to reset the entire tree
this._onDidChangeDatabaseItem.fire({
@@ -523,11 +508,7 @@ export class DatabaseManager extends DisposableObject {
});
}
public async removeDatabaseItem(
progress: ProgressCallback,
token: vscode.CancellationToken,
item: DatabaseItem,
) {
public async removeDatabaseItem(item: DatabaseItem) {
if (this._currentDatabaseItem === item) {
this._currentDatabaseItem = undefined;
}
@@ -549,7 +530,7 @@ export class DatabaseManager extends DisposableObject {
}
// Remove this database item from the allow-list
await this.deregisterDatabase(progress, token, item);
await this.deregisterDatabase(item);
// Delete folder from file system only if it is controlled by the extension
if (this.isExtensionControlledLocation(item.databaseUri)) {
@@ -572,22 +553,15 @@ export class DatabaseManager extends DisposableObject {
});
}
public async removeAllDatabases(
progress: ProgressCallback,
token: vscode.CancellationToken,
) {
public async removeAllDatabases() {
for (const item of this.databaseItems) {
await this.removeDatabaseItem(progress, token, item);
await this.removeDatabaseItem(item);
}
}
private async deregisterDatabase(
progress: ProgressCallback,
token: vscode.CancellationToken,
dbItem: DatabaseItem,
) {
private async deregisterDatabase(dbItem: DatabaseItem) {
try {
await this.qs.deregisterDatabase(progress, token, dbItem);
await this.qs.deregisterDatabase(dbItem);
} catch (e) {
const message = getErrorMessage(e);
if (message === "Connection is disposed.") {
@@ -600,12 +574,8 @@ export class DatabaseManager extends DisposableObject {
throw e;
}
}
private async registerDatabase(
progress: ProgressCallback,
token: vscode.CancellationToken,
dbItem: DatabaseItem,
) {
await this.qs.registerDatabase(progress, token, dbItem);
private async registerDatabase(dbItem: DatabaseItem) {
await this.qs.registerDatabase(dbItem);
}
/**

View File

@@ -284,7 +284,7 @@ export class LocalQueries extends DisposableObject {
private async createSkeletonQuery(): Promise<void> {
await withProgress(
async (progress: ProgressCallback, token: CancellationToken) => {
async (progress: ProgressCallback) => {
const credentials = isCanary() ? this.app.credentials : undefined;
const contextStoragePath =
this.app.workspaceStoragePath || this.app.globalStoragePath;
@@ -294,7 +294,6 @@ export class LocalQueries extends DisposableObject {
credentials,
this.app.logger,
this.databaseManager,
token,
contextStoragePath,
);
await skeletonQueryWizard.execute();

View File

@@ -7,8 +7,9 @@ export function pluralize(
numItems: number | undefined,
singular: string,
plural: string,
numberFormatter: (value: number) => string = (value) => value.toString(),
): string {
return numItems !== undefined
? `${numItems} ${numItems === 1 ? singular : plural}`
? `${numberFormatter(numItems)} ${numItems === 1 ? singular : plural}`
: "";
}

View File

@@ -55,10 +55,9 @@ export class LegacyQueryRunner extends QueryRunner {
}
async clearCacheInDatabase(
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> {
await clearCacheInDatabase(this.qs, dbItem, progress, token);
await clearCacheInDatabase(this.qs, dbItem, token);
}
public async compileAndRunQueryAgainstDatabaseCore(
@@ -88,11 +87,7 @@ export class LegacyQueryRunner extends QueryRunner {
);
}
async deregisterDatabase(
progress: ProgressCallback,
token: CancellationToken,
dbItem: DatabaseItem,
): Promise<void> {
async deregisterDatabase(dbItem: DatabaseItem): Promise<void> {
if (dbItem.contents) {
const databases: Dataset[] = [
{
@@ -100,19 +95,10 @@ export class LegacyQueryRunner extends QueryRunner {
workingSet: "default",
},
];
await this.qs.sendRequest(
deregisterDatabases,
{ databases },
token,
progress,
);
await this.qs.sendRequest(deregisterDatabases, { databases });
}
}
async registerDatabase(
progress: ProgressCallback,
token: CancellationToken,
dbItem: DatabaseItem,
): Promise<void> {
async registerDatabase(dbItem: DatabaseItem): Promise<void> {
if (dbItem.contents) {
const databases: Dataset[] = [
{
@@ -120,12 +106,7 @@ export class LegacyQueryRunner extends QueryRunner {
workingSet: "default",
},
];
await this.qs.sendRequest(
registerDatabases,
{ databases },
token,
progress,
);
await this.qs.sendRequest(registerDatabases, { databases });
}
}

View File

@@ -200,7 +200,6 @@ export class QueryInProgress {
export async function clearCacheInDatabase(
qs: qsClient.QueryServerClient,
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
): Promise<messages.ClearCacheResult> {
if (dbItem.contents === undefined) {
@@ -217,7 +216,7 @@ export async function clearCacheInDatabase(
db,
};
return qs.sendRequest(messages.clearCache, params, token, progress);
return qs.sendRequest(messages.clearCache, params, token);
}
function reportNoUpgradePath(

View File

@@ -56,7 +56,6 @@ export class NewQueryRunner extends QueryRunner {
async clearCacheInDatabase(
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> {
if (dbItem.contents === undefined) {
@@ -68,7 +67,7 @@ export class NewQueryRunner extends QueryRunner {
dryRun: false,
db,
};
await this.qs.sendRequest(clearCache, params, token, progress);
await this.qs.sendRequest(clearCache, params, token);
}
public async compileAndRunQueryAgainstDatabaseCore(
@@ -98,34 +97,16 @@ export class NewQueryRunner extends QueryRunner {
);
}
async deregisterDatabase(
progress: ProgressCallback,
token: CancellationToken,
dbItem: DatabaseItem,
): Promise<void> {
async deregisterDatabase(dbItem: DatabaseItem): Promise<void> {
if (dbItem.contents) {
const databases: string[] = [dbItem.databaseUri.fsPath];
await this.qs.sendRequest(
deregisterDatabases,
{ databases },
token,
progress,
);
await this.qs.sendRequest(deregisterDatabases, { databases });
}
}
async registerDatabase(
progress: ProgressCallback,
token: CancellationToken,
dbItem: DatabaseItem,
): Promise<void> {
async registerDatabase(dbItem: DatabaseItem): Promise<void> {
if (dbItem.contents) {
const databases: string[] = [dbItem.databaseUri.fsPath];
await this.qs.sendRequest(
registerDatabases,
{ databases },
token,
progress,
);
await this.qs.sendRequest(registerDatabases, { databases });
}
}

View File

@@ -61,9 +61,9 @@ export abstract class QueryRunner {
token: CancellationToken,
) => Promise<void>,
): void;
abstract clearCacheInDatabase(
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
): Promise<void>;
@@ -83,17 +83,9 @@ export abstract class QueryRunner {
logger: BaseLogger,
): Promise<CoreQueryResults>;
abstract deregisterDatabase(
progress: ProgressCallback,
token: CancellationToken,
dbItem: DatabaseItem,
): Promise<void>;
abstract deregisterDatabase(dbItem: DatabaseItem): Promise<void>;
abstract registerDatabase(
progress: ProgressCallback,
token: CancellationToken,
dbItem: DatabaseItem,
): Promise<void>;
abstract registerDatabase(dbItem: DatabaseItem): Promise<void>;
abstract upgradeDatabaseExplicit(
dbItem: DatabaseItem,

View File

@@ -48,7 +48,7 @@ export class TestRunner extends DisposableObject {
}
}
await this.removeDatabasesBeforeTests(databasesUnderTest, token);
await this.removeDatabasesBeforeTests(databasesUnderTest);
try {
const workspacePaths = getOnDiskWorkspaceFolders();
for await (const event of this.cliServer.runTests(tests, workspacePaths, {
@@ -66,24 +66,16 @@ export class TestRunner extends DisposableObject {
await this.reopenDatabasesAfterTests(
databasesUnderTest,
currentDatabaseUri,
token,
);
}
}
private async removeDatabasesBeforeTests(
databasesUnderTest: DatabaseItem[],
token: CancellationToken,
): Promise<void> {
for (const database of databasesUnderTest) {
try {
await this.databaseManager.removeDatabaseItem(
(_) => {
/* no progress reporting */
},
token,
database,
);
await this.databaseManager.removeDatabaseItem(database);
} catch (e) {
// This method is invoked from Test Explorer UI, and testing indicates that Test
// Explorer UI swallows any thrown exception without reporting it to the user.
@@ -103,17 +95,12 @@ export class TestRunner extends DisposableObject {
private async reopenDatabasesAfterTests(
databasesUnderTest: DatabaseItem[],
currentDatabaseUri: Uri | undefined,
token: CancellationToken,
): Promise<void> {
for (const closedDatabase of databasesUnderTest) {
const uri = closedDatabase.databaseUri;
if (await isFileAccessible(uri)) {
try {
const reopenedDatabase = await this.databaseManager.openDatabase(
(_) => {
/* no progress reporting */
},
token,
uri,
false,
);

View File

@@ -1,5 +1,5 @@
import { join } from "path";
import { CancellationToken, Uri, workspace, window as Window } from "vscode";
import { Uri, workspace, window as Window } from "vscode";
import { CodeQLCliServer } from "./codeql-cli/cli";
import { BaseLogger } from "./common";
import { Credentials } from "./common/authentication";
@@ -51,7 +51,6 @@ export class SkeletonQueryWizard {
private readonly credentials: Credentials | undefined,
private readonly logger: BaseLogger,
private readonly databaseManager: DatabaseManager,
private readonly token: CancellationToken,
private readonly databaseStoragePath: string | undefined,
) {}
@@ -258,7 +257,6 @@ export class SkeletonQueryWizard {
this.databaseStoragePath,
this.credentials,
this.progress,
this.token,
this.cliServer,
this.language,
);

View File

@@ -16,6 +16,7 @@ import { basename } from "../common/path";
import { ViewTitle } from "../common";
import { DataExtensionEditorViewState } from "../../data-extensions-editor/shared/view-state";
import { ModeledMethodsList } from "./ModeledMethodsList";
import { percentFormatter } from "./formatters";
const DataExtensionsEditorContainer = styled.div`
margin-top: 1rem;
@@ -213,8 +214,12 @@ export function DataExtensionsEditor({
)}
</>
)}
<div>{modeledPercentage.toFixed(2)}% modeled</div>
<div>{unModeledPercentage.toFixed(2)}% unmodeled</div>
<div>
{percentFormatter.format(modeledPercentage / 100)} modeled
</div>
<div>
{percentFormatter.format(unModeledPercentage / 100)} unmodeled
</div>
</DetailsContainer>
<EditorContainer>

View File

@@ -0,0 +1,125 @@
import * as React from "react";
import { useCallback, useMemo, useState } from "react";
import styled from "styled-components";
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
import { pluralize } from "../../pure/word";
import { ModeledMethodDataGrid } from "./ModeledMethodDataGrid";
import { calculateModeledPercentage } from "./modeled";
import { decimalFormatter, percentFormatter } from "./formatters";
import { Codicon } from "../common";
const LibraryContainer = styled.div`
margin-bottom: 1rem;
`;
const TitleContainer = styled.button`
display: flex;
gap: 0.5em;
align-items: center;
width: 100%;
font-size: 1.2em;
font-weight: bold;
color: var(--vscode-editor-foreground);
background-color: transparent;
border: none;
cursor: pointer;
`;
const StatusContainer = styled.div`
display: flex;
gap: 1em;
align-items: center;
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-left: 1em;
`;
type Props = {
libraryName: string;
externalApiUsages: ExternalApiUsage[];
modeledMethods: Record<string, ModeledMethod>;
onChange: (
externalApiUsage: ExternalApiUsage,
modeledMethod: ModeledMethod,
) => void;
};
export const LibraryRow = ({
libraryName,
externalApiUsages,
modeledMethods,
onChange,
}: Props) => {
const modeledPercentage = useMemo(() => {
return calculateModeledPercentage(externalApiUsages);
}, [externalApiUsages]);
const [isExpanded, setExpanded] = useState(modeledPercentage < 100);
const toggleExpanded = useCallback(async () => {
setExpanded((oldIsExpanded) => !oldIsExpanded);
}, []);
const usagesCount = useMemo(() => {
return externalApiUsages.reduce((acc, curr) => acc + curr.usages.length, 0);
}, [externalApiUsages]);
return (
<LibraryContainer>
<TitleContainer onClick={toggleExpanded} aria-expanded={isExpanded}>
{isExpanded ? (
<Codicon name="chevron-down" label="Collapse" />
) : (
<Codicon name="chevron-right" label="Expand" />
)}
{libraryName}
{isExpanded ? null : (
<>
{" "}
(
{pluralize(
externalApiUsages.length,
"method",
"methods",
decimalFormatter.format.bind(decimalFormatter),
)}
, {percentFormatter.format(modeledPercentage / 100)} modeled)
</>
)}
</TitleContainer>
{isExpanded && (
<>
<StatusContainer>
<div>
{pluralize(
externalApiUsages.length,
"method",
"methods",
decimalFormatter.format.bind(decimalFormatter),
)}
</div>
<div>
{pluralize(
usagesCount,
"usage",
"usages",
decimalFormatter.format.bind(decimalFormatter),
)}
</div>
<div>
{percentFormatter.format(modeledPercentage / 100)} modeled
</div>
</StatusContainer>
<ModeledMethodDataGrid
externalApiUsages={externalApiUsages}
modeledMethods={modeledMethods}
onChange={onChange}
/>
</>
)}
</LibraryContainer>
);
};

View File

@@ -7,6 +7,7 @@ import {
import { MethodRow } from "./MethodRow";
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
import { useMemo } from "react";
type Props = {
externalApiUsages: ExternalApiUsage[];
@@ -22,6 +23,22 @@ export const ModeledMethodDataGrid = ({
modeledMethods,
onChange,
}: Props) => {
const sortedExternalApiUsages = useMemo(() => {
const sortedExternalApiUsages = [...externalApiUsages];
sortedExternalApiUsages.sort((a, b) => {
// Sort first by supported, putting unmodeled methods first.
if (a.supported && !b.supported) {
return 1;
}
if (!a.supported && b.supported) {
return -1;
}
// Then sort by number of usages descending
return b.usages.length - a.usages.length;
});
return sortedExternalApiUsages;
}, [externalApiUsages]);
return (
<VSCodeDataGrid>
<VSCodeDataGridRow rowType="header">
@@ -47,7 +64,7 @@ export const ModeledMethodDataGrid = ({
Kind
</VSCodeDataGridCell>
</VSCodeDataGridRow>
{externalApiUsages.map((externalApiUsage) => (
{sortedExternalApiUsages.map((externalApiUsage) => (
<MethodRow
key={externalApiUsage.signature}
externalApiUsage={externalApiUsage}

View File

@@ -1,13 +1,9 @@
import * as React from "react";
import { useMemo } from "react";
import styled from "styled-components";
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
import { ModeledMethodDataGrid } from "./ModeledMethodDataGrid";
const LibraryContainer = styled.div`
margin-bottom: 1rem;
`;
import { calculateModeledPercentage } from "./modeled";
import { LibraryRow } from "./LibraryRow";
type Props = {
externalApiUsages: ExternalApiUsage[];
@@ -35,20 +31,59 @@ export const ModeledMethodsList = ({
}, [externalApiUsages]);
const sortedLibraryNames = useMemo(() => {
return Object.keys(groupedByLibrary).sort();
return Object.keys(groupedByLibrary).sort((a, b) => {
const supportedPercentageA = calculateModeledPercentage(
groupedByLibrary[a],
);
const supportedPercentageB = calculateModeledPercentage(
groupedByLibrary[b],
);
// Sort first by supported percentage ascending
if (supportedPercentageA > supportedPercentageB) {
return 1;
}
if (supportedPercentageA < supportedPercentageB) {
return -1;
}
const numberOfUsagesA = groupedByLibrary[a].reduce(
(acc, curr) => acc + curr.usages.length,
0,
);
const numberOfUsagesB = groupedByLibrary[b].reduce(
(acc, curr) => acc + curr.usages.length,
0,
);
// If the number of usages is equal, sort by number of methods descending
if (numberOfUsagesA === numberOfUsagesB) {
const numberOfMethodsA = groupedByLibrary[a].length;
const numberOfMethodsB = groupedByLibrary[b].length;
// If the number of methods is equal, sort by library name ascending
if (numberOfMethodsA === numberOfMethodsB) {
return a.localeCompare(b);
}
return numberOfMethodsB - numberOfMethodsA;
}
// Then sort by number of usages descending
return numberOfUsagesB - numberOfUsagesA;
});
}, [groupedByLibrary]);
return (
<>
{sortedLibraryNames.map((libraryName) => (
<LibraryContainer key={libraryName}>
<h3>{libraryName}</h3>
<ModeledMethodDataGrid
externalApiUsages={groupedByLibrary[libraryName]}
modeledMethods={modeledMethods}
onChange={onChange}
/>
</LibraryContainer>
<LibraryRow
key={libraryName}
libraryName={libraryName}
externalApiUsages={groupedByLibrary[libraryName]}
modeledMethods={modeledMethods}
onChange={onChange}
/>
))}
</>
);

View File

@@ -0,0 +1,9 @@
export const decimalFormatter = new Intl.NumberFormat("en-US", {
style: "decimal",
maximumFractionDigits: 2,
});
export const percentFormatter = new Intl.NumberFormat("en-US", {
style: "percent",
maximumFractionDigits: 2,
});

View File

@@ -1,5 +1,5 @@
[
"v2.13.3",
"v2.13.4",
"v2.12.7",
"v2.11.6",
"v2.7.6",

View File

@@ -168,6 +168,26 @@ describe("decodeBqrsToExternalApiUsages", () => {
// - Iterating over a map (as done by .values()) is guaranteed to be in insertion order
// - Sorting the array of usages is guaranteed to be a stable sort
expect(decodeBqrsToExternalApiUsages(chunk)).toEqual([
{
signature: "java.io.PrintStream#println(String)",
packageName: "java.io",
typeName: "PrintStream",
methodName: "println",
methodParameters: "(String)",
supported: true,
usages: [
{
label: "println(...)",
url: {
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
startLine: 29,
startColumn: 9,
endLine: 29,
endColumn: 49,
},
},
],
},
{
signature:
"org.springframework.boot.SpringApplication#run(Class,String[])",
@@ -279,26 +299,6 @@ describe("decodeBqrsToExternalApiUsages", () => {
},
],
},
{
signature: "java.io.PrintStream#println(String)",
packageName: "java.io",
typeName: "PrintStream",
methodName: "println",
methodParameters: "(String)",
supported: true,
usages: [
{
label: "println(...)",
url: {
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
startLine: 29,
startColumn: 9,
endLine: 29,
endColumn: 49,
},
},
],
},
{
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
packageName: "org.sql2o",

View File

@@ -14,5 +14,22 @@ describe("word helpers", () => {
it("should return the empty string if the number is undefined", () => {
expect(pluralize(undefined, "thing", "things")).toBe("");
});
it("should return an unformatted number when no formatter is specified", () => {
expect(pluralize(1_000_000, "thing", "things")).toBe("1000000 things");
});
it("should return a formatted number when a formatter is specified", () => {
const formatter = new Intl.NumberFormat("en-US", {
style: "decimal",
});
expect(
pluralize(
1_000_000,
"thing",
"things",
formatter.format.bind(formatter),
),
).toBe("1,000,000 things");
});
});
});

View File

@@ -1,5 +1,5 @@
import { join } from "path";
import { CancellationToken, Uri, window } from "vscode";
import { Uri, window } from "vscode";
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
import { DatabaseManager } from "../../../../src/databases/local-databases";
@@ -52,7 +52,6 @@ describe("database-fetcher", () => {
databaseManager,
storagePath,
progressCallback,
{} as CancellationToken,
cli,
);
expect(dbItem).toBe(databaseManager.currentDatabaseItem);
@@ -74,7 +73,6 @@ describe("database-fetcher", () => {
databaseManager,
storagePath,
progressCallback,
{} as CancellationToken,
cli,
);
expect(dbItem).toBeDefined();

View File

@@ -142,14 +142,7 @@ describeWithCodeQL()("using the legacy query server", () => {
const parsedResults = new Checkpoint<void>();
it("should register the database if necessary", async () => {
await qs.sendRequest(
messages.registerDatabases,
{ databases: [db] },
token,
(() => {
/**/
}) as any,
);
await qs.sendRequest(messages.registerDatabases, { databases: [db] });
});
it(`should be able to compile query ${queryName}`, async () => {

View File

@@ -152,14 +152,7 @@ describeWithCodeQL()("using the new query server", () => {
return;
}
await qs.sendRequest(
messages.registerDatabases,
{ databases: [db] },
token,
(() => {
/**/
}) as any,
);
await qs.sendRequest(messages.registerDatabases, { databases: [db] });
});
it(`should be able to run query ${queryName}`, async () => {

View File

@@ -11,7 +11,6 @@ import { QlPackGenerator } from "../../../src/qlpack-generator";
import * as workspaceFolders from "../../../src/common/vscode/workspace-folders";
import { createFileSync, ensureDirSync, removeSync } from "fs-extra";
import { join } from "path";
import { CancellationTokenSource } from "vscode-jsonrpc";
import { testCredentialsWithStub } from "../../factories/authentication";
import {
DatabaseItem,
@@ -47,7 +46,6 @@ describe("SkeletonQueryWizard", () => {
typeof workspace.openTextDocument
>;
const token = new CancellationTokenSource().token;
const credentials = testCredentialsWithStub();
const chosenLanguage = "ruby";
@@ -117,7 +115,6 @@ describe("SkeletonQueryWizard", () => {
credentials,
extLogger,
mockDatabaseManager,
token,
storagePath,
);
@@ -252,7 +249,6 @@ describe("SkeletonQueryWizard", () => {
credentials,
extLogger,
mockDatabaseManagerWithItems,
token,
storagePath,
);
});

View File

@@ -1,12 +1,7 @@
import { join } from "path";
import { load, dump } from "js-yaml";
import { realpathSync, readFileSync, writeFileSync } from "fs-extra";
import {
CancellationToken,
CancellationTokenSource,
Uri,
extensions,
} from "vscode";
import { Uri, extensions } from "vscode";
import {
DatabaseItem,
DatabaseManager,
@@ -14,7 +9,6 @@ import {
import { CodeQLCliServer } from "../../src/codeql-cli/cli";
import { removeWorkspaceRefs } from "../../src/variant-analysis/run-remote-query";
import { CodeQLExtensionInterface } from "../../src/extension";
import { ProgressCallback } from "../../src/common/vscode/progress";
import { importArchiveDatabase } from "../../src/databases/database-fetcher";
import { createMockCommandManager } from "../__mocks__/commandsMock";
@@ -49,7 +43,6 @@ export async function ensureTestDatabase(
(_p) => {
/**/
},
new CancellationTokenSource().token,
cli,
);
@@ -77,10 +70,7 @@ export async function getActivatedExtension(): Promise<CodeQLExtensionInterface>
}
export async function cleanDatabases(databaseManager: DatabaseManager) {
await databaseManager.removeAllDatabases(
{} as ProgressCallback,
{} as CancellationToken,
);
await databaseManager.removeAllDatabases();
}
/**

View File

@@ -1,7 +1,7 @@
import * as tmp from "tmp";
import * as fs from "fs-extra";
import { join } from "path";
import { CancellationToken, ExtensionContext, Uri, workspace } from "vscode";
import { ExtensionContext, Uri, workspace } from "vscode";
import {
DatabaseContentsWithDbScheme,
@@ -12,7 +12,6 @@ import {
FullDatabaseOptions,
} from "../../../src/databases/local-databases";
import { Logger } from "../../../src/common";
import { ProgressCallback } from "../../../src/common/vscode/progress";
import { CodeQLCliServer, DbInfo } from "../../../src/codeql-cli/cli";
import {
encodeArchiveBasePath,
@@ -119,11 +118,7 @@ describe("local databases", () => {
const mockDbItem = createMockDB(dir);
const onDidChangeDatabaseItem = jest.fn();
databaseManager.onDidChangeDatabaseItem(onDidChangeDatabaseItem);
await (databaseManager as any).addDatabaseItem(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem,
);
await (databaseManager as any).addDatabaseItem(mockDbItem);
expect((databaseManager as any)._databaseItems).toEqual([mockDbItem]);
expect(updateSpy).toBeCalledWith("databaseList", [
@@ -141,11 +136,7 @@ describe("local databases", () => {
onDidChangeDatabaseItem.mockClear();
// now remove the item
await databaseManager.removeDatabaseItem(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem,
);
await databaseManager.removeDatabaseItem(mockDbItem);
expect((databaseManager as any)._databaseItems).toEqual([]);
expect(updateSpy).toBeCalledWith("databaseList", []);
expect(onDidChangeDatabaseItem).toBeCalledWith({
@@ -159,11 +150,7 @@ describe("local databases", () => {
const mockDbItem = createMockDB(dir);
const onDidChangeDatabaseItem = jest.fn();
databaseManager.onDidChangeDatabaseItem(onDidChangeDatabaseItem);
await (databaseManager as any).addDatabaseItem(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem,
);
await (databaseManager as any).addDatabaseItem(mockDbItem);
await databaseManager.renameDatabaseItem(mockDbItem, "new name");
@@ -188,11 +175,7 @@ describe("local databases", () => {
databaseManager.onDidChangeDatabaseItem(onDidChangeDatabaseItem);
const mockDbItem = createMockDB(dir);
await (databaseManager as any).addDatabaseItem(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem,
);
await (databaseManager as any).addDatabaseItem(mockDbItem);
expect(databaseManager.databaseItems).toEqual([mockDbItem]);
expect(updateSpy).toBeCalledWith("databaseList", [
@@ -235,19 +218,11 @@ describe("local databases", () => {
.spyOn(mockDbItem, "belongsToSourceArchiveExplorerUri")
.mockReturnValue(true);
await (databaseManager as any).addDatabaseItem(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem,
);
await (databaseManager as any).addDatabaseItem(mockDbItem);
updateSpy.mockClear();
await databaseManager.removeDatabaseItem(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem,
);
await databaseManager.removeDatabaseItem(mockDbItem);
expect(databaseManager.databaseItems).toEqual([]);
expect(updateSpy).toBeCalledWith("databaseList", []);
@@ -268,22 +243,14 @@ describe("local databases", () => {
jest
.spyOn(mockDbItem, "belongsToSourceArchiveExplorerUri")
.mockReturnValue(true);
await (databaseManager as any).addDatabaseItem(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem,
);
await (databaseManager as any).addDatabaseItem(mockDbItem);
updateSpy.mockClear();
// pretend that the database location is not controlled by the extension
(databaseManager as any).ctx.storageUri = Uri.file("hucairz");
extensionContextStoragePath = "hucairz";
await databaseManager.removeDatabaseItem(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem,
);
await databaseManager.removeDatabaseItem(mockDbItem);
expect(databaseManager.databaseItems).toEqual([]);
expect(updateSpy).toBeCalledWith("databaseList", []);
@@ -301,22 +268,14 @@ describe("local databases", () => {
// registration messages.
const mockDbItem = createMockDB(dir);
await (databaseManager as any).addDatabaseItem(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem,
);
await (databaseManager as any).addDatabaseItem(mockDbItem);
// Should have registered this database
expect(registerSpy).toBeCalledWith({}, {}, mockDbItem);
expect(registerSpy).toBeCalledWith(mockDbItem);
await databaseManager.removeDatabaseItem(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem,
);
await databaseManager.removeDatabaseItem(mockDbItem);
// Should have deregistered this database
expect(deregisterSpy).toBeCalledWith({}, {}, mockDbItem);
expect(deregisterSpy).toBeCalledWith(mockDbItem);
});
});
@@ -753,31 +712,19 @@ describe("local databases", () => {
});
it("should resolve the database contents", async () => {
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
await databaseManager.openDatabase(mockDbItem.databaseUri);
expect(resolveDatabaseContentsSpy).toBeCalledTimes(2);
});
it("should set the database as the currently selected one", async () => {
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
await databaseManager.openDatabase(mockDbItem.databaseUri);
expect(setCurrentDatabaseItemSpy).toBeCalledTimes(1);
});
it("should add database source archive folder", async () => {
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
await databaseManager.openDatabase(mockDbItem.databaseUri);
expect(addDatabaseSourceArchiveFolderSpy).toBeCalledTimes(1);
});
@@ -792,8 +739,6 @@ describe("local databases", () => {
const nameOverride = "CodeQL Tutorial Database";
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
makeSelected,
nameOverride,
@@ -808,11 +753,7 @@ describe("local databases", () => {
it("should create a skeleton QL pack", async () => {
jest.spyOn(Setting.prototype, "getValue").mockReturnValue(true);
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
await databaseManager.openDatabase(mockDbItem.databaseUri);
expect(createSkeletonPacksSpy).toBeCalledTimes(1);
});
@@ -823,11 +764,7 @@ describe("local databases", () => {
it("should not create a skeleton QL pack", async () => {
jest.spyOn(Setting.prototype, "getValue").mockReturnValue(false);
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
await databaseManager.openDatabase(mockDbItem.databaseUri);
expect(createSkeletonPacksSpy).toBeCalledTimes(0);
});
});
@@ -836,11 +773,7 @@ describe("local databases", () => {
it("should not create a skeleton QL pack", async () => {
jest.spyOn(Setting.prototype, "getValue").mockReturnValue(undefined);
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
await databaseManager.openDatabase(mockDbItem.databaseUri);
expect(createSkeletonPacksSpy).toBeCalledTimes(0);
});
});

View File

@@ -155,16 +155,10 @@ describe("test-runner", () => {
).toBeGreaterThan(openDatabaseSpy.mock.invocationCallOrder[0]);
expect(removeDatabaseItemSpy).toBeCalledTimes(1);
expect(removeDatabaseItemSpy).toBeCalledWith(
expect.anything(),
expect.anything(),
preTestDatabaseItem,
);
expect(removeDatabaseItemSpy).toBeCalledWith(preTestDatabaseItem);
expect(openDatabaseSpy).toBeCalledTimes(1);
expect(openDatabaseSpy).toBeCalledWith(
expect.anything(),
expect.anything(),
preTestDatabaseItem.databaseUri,
false,
);

View File

@@ -232,8 +232,6 @@ describe("run-queries", () => {
it("should register", async () => {
const qs = createMockQueryServerClient();
const runner = new LegacyQueryRunner(qs);
const mockProgress = "progress-monitor";
const mockCancel = "cancel-token";
const datasetUri = Uri.file("dataset-uri");
const dbItem: DatabaseItem = {
@@ -242,33 +240,22 @@ describe("run-queries", () => {
},
} as any;
await runner.registerDatabase(
mockProgress as any,
mockCancel as any,
dbItem,
);
await runner.registerDatabase(dbItem);
expect(qs.sendRequest).toHaveBeenCalledTimes(1);
expect(qs.sendRequest).toHaveBeenCalledWith(
registerDatabases,
{
databases: [
{
dbDir: datasetUri.fsPath,
workingSet: "default",
},
],
},
mockCancel,
mockProgress,
);
expect(qs.sendRequest).toHaveBeenCalledWith(registerDatabases, {
databases: [
{
dbDir: datasetUri.fsPath,
workingSet: "default",
},
],
});
});
it("should deregister", async () => {
const qs = createMockQueryServerClient();
const runner = new LegacyQueryRunner(qs);
const mockProgress = "progress-monitor";
const mockCancel = "cancel-token";
const datasetUri = Uri.file("dataset-uri");
const dbItem: DatabaseItem = {
@@ -277,26 +264,17 @@ describe("run-queries", () => {
},
} as any;
await runner.deregisterDatabase(
mockProgress as any,
mockCancel as any,
dbItem,
);
await runner.deregisterDatabase(dbItem);
expect(qs.sendRequest).toHaveBeenCalledTimes(1);
expect(qs.sendRequest).toHaveBeenCalledWith(
deregisterDatabases,
{
databases: [
{
dbDir: datasetUri.fsPath,
workingSet: "default",
},
],
},
mockCancel,
mockProgress,
);
expect(qs.sendRequest).toHaveBeenCalledWith(deregisterDatabases, {
databases: [
{
dbDir: datasetUri.fsPath,
workingSet: "default",
},
],
});
});
});