Add link to open query results from compare view

This commit is contained in:
Andrew Eisenberg 2020-06-09 11:16:04 -07:00
Родитель b803a80d39
Коммит 9be355aa9d
5 изменённых файлов: 231 добавлений и 85 удалений

Просмотреть файл

@ -35,10 +35,13 @@ export class CompareInterfaceManager extends DisposableObject {
private panelLoadedCallBacks: (() => void)[] = [];
constructor(
public ctx: ExtensionContext,
public databaseManager: DatabaseManager,
public cliServer: CodeQLCliServer,
public logger: Logger
private ctx: ExtensionContext,
private databaseManager: DatabaseManager,
private cliServer: CodeQLCliServer,
private logger: Logger,
private showQueryResultsCallback: (
item: CompletedQuery
) => Promise<void>
) {
super();
}
@ -57,7 +60,11 @@ export class CompareInterfaceManager extends DisposableObject {
currentResultSetName,
fromResultSet,
toResultSet,
] = await this.findCommonResultSetNames(from, to, selectedResultSetName);
] = await this.findCommonResultSetNames(
from,
to,
selectedResultSetName
);
if (currentResultSetName) {
await this.postMessage({
t: "setComparisons",
@ -147,7 +154,9 @@ export class CompareInterfaceManager extends DisposableObject {
});
}
private async handleMsgFromView(msg: FromCompareViewMessage): Promise<void> {
private async handleMsgFromView(
msg: FromCompareViewMessage
): Promise<void> {
switch (msg.t) {
case "compareViewLoaded":
this.panelLoaded = true;
@ -162,6 +171,10 @@ export class CompareInterfaceManager extends DisposableObject {
case "viewSourceFile":
await jumpToLocation(msg, this.databaseManager, this.logger);
break;
case "openQuery":
await this.openQuery(msg.kind);
break;
}
}
@ -183,7 +196,9 @@ export class CompareInterfaceManager extends DisposableObject {
const fromSchemaNames = fromSchemas["result-sets"].map(
(schema) => schema.name
);
const toSchemaNames = toSchemas["result-sets"].map((schema) => schema.name);
const toSchemaNames = toSchemas["result-sets"].map(
(schema) => schema.name
);
const commonResultSetNames = fromSchemaNames.filter((name) =>
toSchemaNames.includes(name)
);
@ -229,7 +244,10 @@ export class CompareInterfaceManager extends DisposableObject {
if (!schema) {
throw new Error(`Schema ${resultSetName} not found.`);
}
const chunk = await this.cliServer.bqrsDecode(resultsPath, resultSetName);
const chunk = await this.cliServer.bqrsDecode(
resultsPath,
resultSetName
);
const adaptedSchema = adaptSchema(schema);
return adaptBqrs(adaptedSchema, chunk);
}
@ -241,4 +259,12 @@ export class CompareInterfaceManager extends DisposableObject {
// Only compare columns that have the same name
return resultsDiff(fromResults, toResults);
}
private openQuery(kind: "from" | "to") {
const toOpen =
kind === "from" ? this.comparePair?.from : this.comparePair?.to;
if (toOpen) {
this.showQueryResultsCallback(toOpen);
}
}
}

Просмотреть файл

@ -60,8 +60,22 @@ export function Compare(props: {}): JSX.Element {
<table className="vscode-codeql__compare-body">
<thead>
<tr>
<td>{comparison.stats.fromQuery?.name}</td>
<td>{comparison.stats.toQuery?.name}</td>
<td>
<a
onClick={() => openQuery("from")}
className="vscode-codeql__compare-open"
>
{comparison.stats.fromQuery?.name}
</a>
</td>
<td>
<a
onClick={() => openQuery("to")}
className="vscode-codeql__compare-open"
>
{comparison.stats.toQuery?.name}
</a>
</td>
</tr>
<tr>
<td>{comparison.stats.fromQuery?.time}</td>
@ -105,6 +119,13 @@ export function Compare(props: {}): JSX.Element {
}
}
async function openQuery(kind: "from" | "to") {
vscode.postMessage({
t: "openQuery",
kind,
});
}
function createRows(rows: ResultRow[], databaseUri: string) {
return (
<tbody>

Просмотреть файл

@ -272,54 +272,87 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
});
}
async function activateWithInstalledDistribution(ctx: ExtensionContext, distributionManager: DistributionManager): Promise<void> {
async function activateWithInstalledDistribution(
ctx: ExtensionContext,
distributionManager: DistributionManager
): Promise<void> {
beganMainExtensionActivation = true;
// Remove any error stubs command handlers left over from first part
// of activation.
errorStubs.forEach(stub => stub.dispose());
errorStubs.forEach((stub) => stub.dispose());
logger.log('Initializing configuration listener...');
const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener(distributionManager);
logger.log("Initializing configuration listener...");
const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener(
distributionManager
);
ctx.subscriptions.push(qlConfigurationListener);
logger.log('Initializing CodeQL cli server...');
logger.log("Initializing CodeQL cli server...");
const cliServer = new CodeQLCliServer(distributionManager, logger);
ctx.subscriptions.push(cliServer);
logger.log('Initializing query server client.');
const qs = new qsClient.QueryServerClient(qlConfigurationListener, cliServer, {
logger: queryServerLogger,
}, task => Window.withProgress({ title: 'CodeQL query server', location: ProgressLocation.Window }, task));
logger.log("Initializing query server client.");
const qs = new qsClient.QueryServerClient(
qlConfigurationListener,
cliServer,
{
logger: queryServerLogger,
},
(task) =>
Window.withProgress(
{ title: "CodeQL query server", location: ProgressLocation.Window },
task
)
);
ctx.subscriptions.push(qs);
await qs.startQueryServer();
logger.log('Initializing database manager.');
logger.log("Initializing database manager.");
const dbm = new DatabaseManager(ctx, qlConfigurationListener, logger);
ctx.subscriptions.push(dbm);
logger.log('Initializing database panel.');
const databaseUI = new DatabaseUI(ctx, cliServer, dbm, qs, getContextStoragePath(ctx));
logger.log("Initializing database panel.");
const databaseUI = new DatabaseUI(
ctx,
cliServer,
dbm,
qs,
getContextStoragePath(ctx)
);
ctx.subscriptions.push(databaseUI);
logger.log('Initializing query history manager.');
logger.log("Initializing query history manager.");
const queryHistoryConfigurationListener = new QueryHistoryConfigListener();
const showResults = async (item: CompletedQuery) =>
showResultsForCompletedQuery(item, WebviewReveal.Forced);
const qhm = new QueryHistoryManager(
ctx,
queryHistoryConfigurationListener,
async item => showResultsForCompletedQuery(item, WebviewReveal.Forced),
async (from: CompletedQuery, to: CompletedQuery) => showResultsForComparison(from, to),
showResults,
async (from: CompletedQuery, to: CompletedQuery) =>
showResultsForComparison(from, to),
);
logger.log('Initializing results panel interface.');
logger.log("Initializing results panel interface.");
const intm = new InterfaceManager(ctx, dbm, cliServer, queryServerLogger);
ctx.subscriptions.push(intm);
logger.log('Initializing compare panel interface.');
const cmpm = new CompareInterfaceManager(ctx, dbm, cliServer, queryServerLogger);
logger.log("Initializing compare panel interface.");
const cmpm = new CompareInterfaceManager(
ctx,
dbm,
cliServer,
queryServerLogger,
showResults
);
ctx.subscriptions.push(cmpm);
logger.log('Initializing source archive filesystem provider.');
logger.log("Initializing source archive filesystem provider.");
archiveFilesystemProvider.activate(ctx);
async function showResultsForComparison(from: CompletedQuery, to: CompletedQuery): Promise<void> {
async function showResultsForComparison(
from: CompletedQuery,
to: CompletedQuery
): Promise<void> {
try {
await cmpm.showResults(from, to);
} catch (e) {
@ -327,18 +360,30 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
}
}
async function showResultsForCompletedQuery(query: CompletedQuery, forceReveal: WebviewReveal): Promise<void> {
async function showResultsForCompletedQuery(
query: CompletedQuery,
forceReveal: WebviewReveal
): Promise<void> {
await intm.showResults(query, forceReveal, false);
}
async function compileAndRunQuery(quickEval: boolean, selectedQuery: Uri | undefined): Promise<void> {
async function compileAndRunQuery(
quickEval: boolean,
selectedQuery: Uri | undefined
): Promise<void> {
if (qs !== undefined) {
try {
const dbItem = await databaseUI.getDatabaseItem();
if (dbItem === undefined) {
throw new Error('Can\'t run query without a selected database');
throw new Error("Can't run query without a selected database");
}
const info = await compileAndRunQueryAgainstDatabase(cliServer, qs, dbItem, quickEval, selectedQuery);
const info = await compileAndRunQueryAgainstDatabase(
cliServer,
qs,
dbItem,
quickEval,
selectedQuery
);
const item = qhm.addQuery(info);
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
} catch (e) {
@ -355,21 +400,28 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
ctx.subscriptions.push(tmpDirDisposal);
logger.log('Initializing CodeQL language server.');
const client = new LanguageClient('CodeQL Language Server', () => spawnIdeServer(qlConfigurationListener), {
documentSelector: [
{ language: 'ql', scheme: 'file' },
{ language: 'yaml', scheme: 'file', pattern: '**/qlpack.yml' }
],
synchronize: {
configurationSection: 'codeQL'
logger.log("Initializing CodeQL language server.");
const client = new LanguageClient(
"CodeQL Language Server",
() => spawnIdeServer(qlConfigurationListener),
{
documentSelector: [
{ language: "ql", scheme: "file" },
{ language: "yaml", scheme: "file", pattern: "**/qlpack.yml" },
],
synchronize: {
configurationSection: "codeQL",
},
// Ensure that language server exceptions are logged to the same channel as its output.
outputChannel: ideServerLogger.outputChannel,
},
// Ensure that language server exceptions are logged to the same channel as its output.
outputChannel: ideServerLogger.outputChannel
}, true);
true
);
logger.log('Initializing QLTest interface.');
const testExplorerExtension = extensions.getExtension<TestHub>(testExplorerExtensionId);
logger.log("Initializing QLTest interface.");
const testExplorerExtension = extensions.getExtension<TestHub>(
testExplorerExtensionId
);
if (testExplorerExtension) {
const testHub = testExplorerExtension.exports;
const testAdapterFactory = new QLTestAdapterFactory(testHub, cliServer);
@ -379,24 +431,58 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
ctx.subscriptions.push(testUIService);
}
logger.log('Registering top-level command palette commands.');
ctx.subscriptions.push(commands.registerCommand('codeQL.runQuery', async (uri: Uri | undefined) => await compileAndRunQuery(false, uri)));
ctx.subscriptions.push(commands.registerCommand('codeQL.quickEval', async (uri: Uri | undefined) => await compileAndRunQuery(true, uri)));
ctx.subscriptions.push(commands.registerCommand('codeQL.quickQuery', async () => displayQuickQuery(ctx, cliServer, databaseUI)));
ctx.subscriptions.push(commands.registerCommand('codeQL.restartQueryServer', async () => {
await qs.restartQueryServer();
helpers.showAndLogInformationMessage('CodeQL Query Server restarted.', { outputLogger: queryServerLogger });
}));
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseFolder', () => databaseUI.handleChooseDatabaseFolder()));
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseArchive', () => databaseUI.handleChooseDatabaseArchive()));
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseLgtm', () => databaseUI.handleChooseDatabaseLgtm()));
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseInternet', () => databaseUI.handleChooseDatabaseInternet()));
logger.log("Registering top-level command palette commands.");
ctx.subscriptions.push(
commands.registerCommand(
"codeQL.runQuery",
async (uri: Uri | undefined) => await compileAndRunQuery(false, uri)
)
);
ctx.subscriptions.push(
commands.registerCommand(
"codeQL.quickEval",
async (uri: Uri | undefined) => await compileAndRunQuery(true, uri)
)
);
ctx.subscriptions.push(
commands.registerCommand("codeQL.quickQuery", async () =>
displayQuickQuery(ctx, cliServer, databaseUI)
)
);
ctx.subscriptions.push(
commands.registerCommand("codeQL.restartQueryServer", async () => {
await qs.restartQueryServer();
helpers.showAndLogInformationMessage("CodeQL Query Server restarted.", {
outputLogger: queryServerLogger,
});
})
);
ctx.subscriptions.push(
commands.registerCommand("codeQL.chooseDatabaseFolder", () =>
databaseUI.handleChooseDatabaseFolder()
)
);
ctx.subscriptions.push(
commands.registerCommand("codeQL.chooseDatabaseArchive", () =>
databaseUI.handleChooseDatabaseArchive()
)
);
ctx.subscriptions.push(
commands.registerCommand("codeQL.chooseDatabaseLgtm", () =>
databaseUI.handleChooseDatabaseLgtm()
)
);
ctx.subscriptions.push(
commands.registerCommand("codeQL.chooseDatabaseInternet", () =>
databaseUI.handleChooseDatabaseInternet()
)
);
logger.log('Starting language server.');
logger.log("Starting language server.");
ctx.subscriptions.push(client.start());
// Jump-to-definition and find-references
logger.log('Registering jump-to-definition handlers.');
logger.log("Registering jump-to-definition handlers.");
languages.registerDefinitionProvider(
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
new TemplateQueryDefinitionProvider(cliServer, qs, dbm)
@ -406,7 +492,7 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
new TemplateQueryReferenceProvider(cliServer, qs, dbm)
);
logger.log('Successfully finished extension initialization.');
logger.log("Successfully finished extension initialization.");
}
function getContextStoragePath(ctx: ExtensionContext) {

Просмотреть файл

@ -1,10 +1,10 @@
import * as sarif from 'sarif';
import * as sarif from "sarif";
import {
ResolvableLocationValue,
ColumnSchema,
ResultSetSchema,
} from "semmle-bqrs";
import { ResultRow, ParsedResultSets, RawResultSet } from './adapt';
import { ResultRow, ParsedResultSets, RawResultSet } from "./adapt";
/**
* This module contains types and code that are shared between
@ -87,11 +87,11 @@ export type SortedResultsMap = { [resultSet: string]: SortedResultSetInfo };
* As a result of receiving this message, listeners might want to display a loading indicator.
*/
export interface ResultsUpdatingMsg {
t: 'resultsUpdating';
t: "resultsUpdating";
}
export interface SetStateMsg {
t: 'setState';
t: "setState";
resultsPath: string;
origResultsPaths: ResultsPaths;
sortedResultsMap: SortedResultsMap;
@ -115,13 +115,16 @@ export interface SetStateMsg {
/** Advance to the next or previous path no in the path viewer */
export interface NavigatePathMsg {
t: 'navigatePath';
t: "navigatePath";
/** 1 for next, -1 for previous */
direction: number;
}
export type IntoResultsViewMsg = ResultsUpdatingMsg | SetStateMsg | NavigatePathMsg;
export type IntoResultsViewMsg =
| ResultsUpdatingMsg
| SetStateMsg
| NavigatePathMsg;
export type FromResultsViewMsg =
| ViewSourceFileMsg
@ -132,13 +135,13 @@ export type FromResultsViewMsg =
| ChangePage;
export interface ViewSourceFileMsg {
t: 'viewSourceFile';
t: "viewSourceFile";
loc: ResolvableLocationValue;
databaseUri: string;
}
interface ToggleDiagnostics {
t: 'toggleDiagnostics';
t: "toggleDiagnostics";
databaseUri: string;
metadata?: QueryMetadata;
origResultsPaths: ResultsPaths;
@ -147,17 +150,18 @@ interface ToggleDiagnostics {
}
interface ResultViewLoaded {
t: 'resultViewLoaded';
t: "resultViewLoaded";
}
interface ChangePage {
t: 'changePage';
t: "changePage";
pageNumber: number; // 0-indexed, displayed to the user as 1-indexed
selectedTable: string;
}
export enum SortDirection {
asc, desc
asc,
desc,
}
export interface RawResultsSortState {
@ -165,8 +169,7 @@ export interface RawResultsSortState {
sortDirection: SortDirection;
}
export type InterpretedResultsSortColumn =
'alert-message';
export type InterpretedResultsSortColumn = "alert-message";
export interface InterpretedResultsSortState {
sortBy: InterpretedResultsSortColumn;
@ -174,7 +177,7 @@ export interface InterpretedResultsSortState {
}
interface ChangeRawResultsSortMsg {
t: 'changeSort';
t: "changeSort";
resultSetName: string;
/**
* sortState being undefined means don't sort, just present results in the order
@ -184,7 +187,7 @@ interface ChangeRawResultsSortMsg {
}
interface ChangeInterpretedResultsSortMsg {
t: 'changeInterpretedSort';
t: "changeInterpretedSort";
/**
* sortState being undefined means don't sort, just present results in the order
* they appear in the sarif file.
@ -195,21 +198,26 @@ interface ChangeInterpretedResultsSortMsg {
export type FromCompareViewMessage =
| CompareViewLoadedMessage
| ChangeCompareMessage
| ViewSourceFileMsg;
| ViewSourceFileMsg
| OpenQueryMessage;
interface CompareViewLoadedMessage {
t: 'compareViewLoaded';
t: "compareViewLoaded";
}
export interface OpenQueryMessage {
readonly t: "openQuery";
readonly kind: "from" | "to";
}
interface ChangeCompareMessage {
t: 'changeCompare';
t: "changeCompare";
newResultSetName: string;
// TODO do we need to include the ids of the queries
}
export type ToCompareViewMessage = SetComparisonsMessage;
export interface SetComparisonsMessage {
export interface SetComparisonsMessage {
readonly t: "setComparisons";
readonly stats: {
fromQuery?: {
@ -231,9 +239,9 @@ export interface SetComparisonsMessage {
}
export enum DiffKind {
Add = 'Add',
Remove = 'Remove',
Change = 'Change'
Add = "Add",
Remove = "Remove",
Change = "Change",
}
/**

Просмотреть файл

@ -165,3 +165,8 @@ td.vscode-codeql__path-index-cell {
margin: 20px 0;
width: 100%;
}
.vscode-codeql__compare-open {
cursor: pointer;
text-decoration: underline;
}