Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions extensions/ql-vscode/src/history-item-label-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { QueryHistoryConfig } from './config';
import { LocalQueryInfo, QueryHistoryInfo } from './query-results';
import { RemoteQueryHistoryItem } from './remote-queries/remote-query-history-item';
import { pluralize } from './helpers';
import { VariantAnalysisHistoryItem } from './remote-queries/variant-analysis-history-item';
import { assertNever } from './pure/helpers-pure';

interface InterpolateReplacements {
t: string; // Start time
Expand All @@ -21,9 +23,20 @@ export class HistoryItemLabelProvider {
}

getLabel(item: QueryHistoryInfo) {
const replacements = item.t === 'local'
? this.getLocalInterpolateReplacements(item)
: this.getRemoteInterpolateReplacements(item);
let replacements: InterpolateReplacements;
switch (item.t) {
case 'local':
replacements = this.getLocalInterpolateReplacements(item);
break;
case 'remote':
replacements = this.getRemoteInterpolateReplacements(item);
break;
case 'variant-analysis':
replacements = this.getVariantAnalysisInterpolateReplacements(item);
break;
default:
assertNever(item);
}

const rawLabel = item.userSpecifiedLabel ?? (this.config.format || '%q');

Expand All @@ -37,11 +50,20 @@ export class HistoryItemLabelProvider {
* @returns the name of the query, unless there is a custom label for this query.
*/
getShortLabel(item: QueryHistoryInfo): string {
return item.userSpecifiedLabel
? this.getLabel(item)
: item.t === 'local'
? item.getQueryName()
: item.remoteQuery.queryName;
if (item.userSpecifiedLabel) {
return this.getLabel(item);
} else {
switch (item.t) {
case 'local':
return item.getQueryName();
case 'remote':
return item.remoteQuery.queryName;
case 'variant-analysis':
return item.variantAnalysis.query.name;
default:
assertNever(item);
}
}
}


Expand Down Expand Up @@ -90,4 +112,17 @@ export class HistoryItemLabelProvider {
'%': '%'
};
}

private getVariantAnalysisInterpolateReplacements(item: VariantAnalysisHistoryItem): InterpolateReplacements {
const resultCount = item.resultCount ? `(${pluralize(item.resultCount, 'result', 'results')})` : '';
return {
t: new Date(item.variantAnalysis.executionStartTime).toLocaleString(env.language),
q: `${item.variantAnalysis.query.name} (${item.variantAnalysis.query.language})`,
d: 'TODO',
r: resultCount,
s: item.status,
f: path.basename(item.variantAnalysis.query.filePath),
'%': '%',
};
}
}
79 changes: 57 additions & 22 deletions extensions/ql-vscode/src/query-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,9 @@ export class HistoryTreeDataProvider extends DisposableObject implements TreeDat
const h1Label = this.labelProvider.getLabel(h1).toLowerCase();
const h2Label = this.labelProvider.getLabel(h2).toLowerCase();

const h1Date = h1.t === 'local'
? h1.initialInfo.start.getTime()
: h1.remoteQuery?.executionStartTime;
const h1Date = this.getItemDate(h1);

const h2Date = h2.t === 'local'
? h2.initialInfo.start.getTime()
: h2.remoteQuery?.executionStartTime;
const h2Date = this.getItemDate(h2);

const resultCount1 = h1.t === 'local'
? h1.completedQuery?.resultCount ?? -1
Expand Down Expand Up @@ -311,6 +307,19 @@ export class HistoryTreeDataProvider extends DisposableObject implements TreeDat
this._sortOrder = newSortOrder;
this._onDidChangeTreeData.fire(undefined);
}

private getItemDate(item: QueryHistoryInfo) {
switch (item.t) {
case 'local':
return item.initialInfo.start.getTime();
case 'remote':
return item.remoteQuery.executionStartTime;
case 'variant-analysis':
return item.variantAnalysis.executionStartTime;
default:
assertNever(item);
}
}
}

export class QueryHistoryManager extends DisposableObject {
Expand Down Expand Up @@ -649,10 +658,20 @@ export class QueryHistoryManager extends DisposableObject {
return;
}

const queryPath = finalSingleItem.t === 'local'
? finalSingleItem.initialInfo.queryPath
: finalSingleItem.remoteQuery.queryFilePath;

let queryPath: string;
switch (finalSingleItem.t) {
case 'local':
queryPath = finalSingleItem.initialInfo.queryPath;
break;
case 'remote':
queryPath = finalSingleItem.remoteQuery.queryFilePath;
break;
case 'variant-analysis':
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably add a default case.

queryPath = finalSingleItem.variantAnalysis.query.filePath;
break;
default:
assertNever(finalSingleItem);
}
const textDocument = await workspace.openTextDocument(
Uri.file(queryPath)
);
Expand Down Expand Up @@ -710,8 +729,12 @@ export class QueryHistoryManager extends DisposableObject {
// We need to delete it from disk as well.
await item.completedQuery?.query.deleteQuery();
}
} else {
} else if (item.t === 'remote') {
await this.removeRemoteQuery(item);
} else if (item.t === 'variant-analysis') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an else block to handle unknown types.

// TODO
} else {
assertNever(item);
}
}));

Expand Down Expand Up @@ -1025,15 +1048,20 @@ export class QueryHistoryManager extends DisposableObject {
isQuickEval: String(!!(finalSingleItem.t === 'local' && finalSingleItem.initialInfo.quickEvalPosition)),
queryText: encodeURIComponent(await this.getQueryText(finalSingleItem)),
});
const queryId = finalSingleItem.t === 'local'
? finalSingleItem.initialInfo.id
: finalSingleItem.queryId;

const uri = Uri.parse(
`codeql:${queryId}?${params.toString()}`, true
);
const doc = await workspace.openTextDocument(uri);
await window.showTextDocument(doc, { preview: false });
if (finalSingleItem.t === 'variant-analysis') {
// TODO
} else {
const queryId = finalSingleItem.t === 'local'
? finalSingleItem.initialInfo.id
: finalSingleItem.queryId;

const uri = Uri.parse(
`codeql:${queryId}?${params.toString()}`, true
);
const doc = await workspace.openTextDocument(uri);
await window.showTextDocument(doc, { preview: false });
}
}

async handleViewSarifAlerts(
Expand Down Expand Up @@ -1149,9 +1177,16 @@ export class QueryHistoryManager extends DisposableObject {
}

async getQueryText(item: QueryHistoryInfo): Promise<string> {
return item.t === 'local'
? item.initialInfo.queryText
: item.remoteQuery.queryText;
switch (item.t) {
case 'local':
return item.initialInfo.queryText;
case 'remote':
return item.remoteQuery.queryText;
case 'variant-analysis':
return 'TODO';
default:
assertNever(item);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default case.

}

async handleExportResults(): Promise<void> {
Expand Down
5 changes: 3 additions & 2 deletions extensions/ql-vscode/src/query-results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { QueryStatus } from './query-status';
import { RemoteQueryHistoryItem } from './remote-queries/remote-query-history-item';
import { QueryEvaluationInfo, QueryWithResults } from './run-queries-shared';
import { formatLegacyMessage } from './legacy-query-server/run-queries';
import { VariantAnalysisHistoryItem } from './remote-queries/variant-analysis-history-item';

/**
* query-results.ts
Expand Down Expand Up @@ -201,13 +202,13 @@ export function ensureMetadataIsComplete(metadata: QueryMetadata | undefined) {
}

/**
* Used in Interface and Compare-Interface for queries that we know have been complated.
* Used in Interface and Compare-Interface for queries that we know have been completed.
*/
export type CompletedLocalQueryInfo = LocalQueryInfo & {
completedQuery: CompletedQueryInfo
};

export type QueryHistoryInfo = LocalQueryInfo | RemoteQueryHistoryItem;
export type QueryHistoryInfo = LocalQueryInfo | RemoteQueryHistoryItem | VariantAnalysisHistoryItem;

export class LocalQueryInfo {
readonly t = 'local';
Expand Down
8 changes: 4 additions & 4 deletions extensions/ql-vscode/src/query-serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export async function slurpQueryHistory(fsPath: string): Promise<QueryHistoryInf

const data = await fs.readFile(fsPath, 'utf8');
const obj = JSON.parse(data);
if (obj.version !== 1) {
void showAndLogErrorMessage(`Unsupported query history format: v${obj.version}. `);
if (![1, 2].includes(obj.version)) {
void showAndLogErrorMessage(`Can't parse query history. Unsupported query history format: v${obj.version}. `);
return [];
}

Expand Down Expand Up @@ -54,7 +54,7 @@ export async function slurpQueryHistory(fsPath: string): Promise<QueryHistoryInf
// most likely another workspace has deleted them because the
// queries aged out.
return asyncFilter(parsedQueries, async (q) => {
if (q.t === 'remote') {
if (q.t === 'remote' || q.t === 'variant-analysis') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On line 18, there is a version property that gets added. We probably want to bump this to version 2. This would mean that in the rare case someone downgrades from a version of the extension with the variant-analysis type to one without, they won't have errors when loading query history (it will mean that they lose their query history, but I think that is a reasonable tradeoff).

// the slurper doesn't know where the remote queries are stored
// so we need to assume here that they exist. Later, we check to
// see if they exist on disk.
Expand Down Expand Up @@ -90,7 +90,7 @@ export async function splatQueryHistory(queries: QueryHistoryInfo[], fsPath: str
// remove incomplete local queries since they cannot be recreated on restart
const filteredQueries = queries.filter(q => q.t === 'local' ? q.completedQuery !== undefined : true);
const data = JSON.stringify({
version: 1,
version: 2, // version 2 adds the `variant-analysis` type.
queries: filteredQueries
}, null, 2);
await fs.writeFile(fsPath, data);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { QueryStatus } from '../query-status';
import { VariantAnalysis } from './shared/variant-analysis';

/**
* Information about a variant analysis.
*/
export interface VariantAnalysisHistoryItem {
readonly t: 'variant-analysis';
failureReason?: string;
resultCount?: number;
status: QueryStatus;
completed: boolean;
variantAnalysis: VariantAnalysis;
userSpecifiedLabel?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ describe('query-results', () => {
it('should handle an invalid query history version', async () => {
const badPath = path.join(tmpDir.name, 'bad-query-history.json');
fs.writeFileSync(badPath, JSON.stringify({
version: 2,
version: 3,
queries: allHistory
}), 'utf8');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ describe('Remote queries and query history manager', function() {

// also, both queries should be removed from on disk storage
expect(fs.readJSONSync(path.join(STORAGE_DIR, 'workspace-query-history.json'))).to.deep.eq({
version: 1,
version: 2,
queries: []
});
});
Expand Down