Merge branch 'main' into aeisenberg/remote-query-save
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
## [UNRELEASED]
|
||||
|
||||
- Fix a bug where database upgrades could not be resolved if some of the target pack's dependencies are outside of the workspace. [#1138](https://github.com/github/vscode-codeql/pull/1138)
|
||||
- Open the query server logs for query errors (instead of the extension log). This will make it easier to track down query errors. [#1158](https://github.com/github/vscode-codeql/pull/1158)
|
||||
- Fix a bug where queries took a long time to run if there are no folders in the workspace. [#1157](https://github.com/github/vscode-codeql/pull/1157)
|
||||
|
||||
## 1.5.11 - 10 February 2022
|
||||
|
||||
|
||||
@@ -514,8 +514,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
async resolveLibraryPath(workspaces: string[], queryPath: string): Promise<QuerySetup> {
|
||||
const subcommandArgs = [
|
||||
'--query', queryPath,
|
||||
'--additional-packs',
|
||||
workspaces.join(path.delimiter)
|
||||
...this.getAdditionalPacksArg(workspaces)
|
||||
];
|
||||
return await this.runJsonCodeQlCliCommand<QuerySetup>(['resolve', 'library-path'], subcommandArgs, 'Resolving library paths');
|
||||
}
|
||||
@@ -528,8 +527,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
const subcommandArgs = [
|
||||
'--format', 'bylanguage',
|
||||
queryUri.fsPath,
|
||||
'--additional-packs',
|
||||
workspaces.join(path.delimiter)
|
||||
...this.getAdditionalPacksArg(workspaces)
|
||||
];
|
||||
return JSON.parse(await this.runCodeQlCliCommand(['resolve', 'queries'], subcommandArgs, 'Resolving query by language'));
|
||||
}
|
||||
@@ -562,6 +560,17 @@ export class CodeQLCliServer implements Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues an internal clear-cache command to the cli server. This
|
||||
* command is used to clear the qlpack cache of the server.
|
||||
*
|
||||
* This cache is generally cleared every 1s. This method is used
|
||||
* to force an early clearing of the cache.
|
||||
*/
|
||||
public async clearCache(): Promise<void> {
|
||||
await this.runCodeQlCliCommand(['clear-cache'], [], 'Clearing qlpack cache');
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs QL tests.
|
||||
* @param testPaths Full paths of the tests to run.
|
||||
@@ -573,7 +582,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
): AsyncGenerator<TestCompleted, void, unknown> {
|
||||
|
||||
const subcommandArgs = this.cliConfig.additionalTestArguments.concat([
|
||||
'--additional-packs', workspaces.join(path.delimiter),
|
||||
...this.getAdditionalPacksArg(workspaces),
|
||||
'--threads',
|
||||
this.cliConfig.numberTestThreads.toString(),
|
||||
...testPaths
|
||||
@@ -595,8 +604,12 @@ export class CodeQLCliServer implements Disposable {
|
||||
|
||||
/** Resolves the ML models that should be available when evaluating a query. */
|
||||
async resolveMlModels(additionalPacks: string[]): Promise<MlModelsInfo> {
|
||||
return await this.runJsonCodeQlCliCommand<MlModelsInfo>(['resolve', 'ml-models'], ['--additional-packs',
|
||||
additionalPacks.join(path.delimiter)], 'Resolving ML models', false);
|
||||
return await this.runJsonCodeQlCliCommand<MlModelsInfo>(
|
||||
['resolve', 'ml-models'],
|
||||
this.getAdditionalPacksArg(additionalPacks),
|
||||
'Resolving ML models',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -761,7 +774,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
* @returns A list of database upgrade script directories
|
||||
*/
|
||||
async resolveUpgrades(dbScheme: string, searchPath: string[], allowDowngradesIfPossible: boolean, targetDbScheme?: string): Promise<UpgradesInfo> {
|
||||
const args = ['--additional-packs', searchPath.join(path.delimiter), '--dbscheme', dbScheme];
|
||||
const args = [...this.getAdditionalPacksArg(searchPath), '--dbscheme', dbScheme];
|
||||
if (targetDbScheme) {
|
||||
args.push('--target-dbscheme', targetDbScheme);
|
||||
if (allowDowngradesIfPossible && await this.cliConstraints.supportsDowngrades()) {
|
||||
@@ -783,7 +796,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
* @returns A dictionary mapping qlpack name to the directory it comes from
|
||||
*/
|
||||
resolveQlpacks(additionalPacks: string[], searchPath?: string[]): Promise<QlpacksInfo> {
|
||||
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
|
||||
const args = this.getAdditionalPacksArg(additionalPacks);
|
||||
if (searchPath?.length) {
|
||||
args.push('--search-path', path.join(...searchPath));
|
||||
}
|
||||
@@ -829,7 +842,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
* @returns A list of query files found.
|
||||
*/
|
||||
async resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise<string[]> {
|
||||
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
|
||||
const args = this.getAdditionalPacksArg(additionalPacks);
|
||||
if (searchPath !== undefined) {
|
||||
args.push('--search-path', path.join(...searchPath));
|
||||
}
|
||||
@@ -862,8 +875,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
'-o',
|
||||
outputPath,
|
||||
dir,
|
||||
'--additional-packs',
|
||||
workspaceFolders.join(path.delimiter)
|
||||
...this.getAdditionalPacksArg(workspaceFolders)
|
||||
];
|
||||
if (!precompile && await this.cliConstraints.supportsNoPrecompile()) {
|
||||
args.push('--no-precompile');
|
||||
@@ -918,6 +930,12 @@ export class CodeQLCliServer implements Disposable {
|
||||
throw new Error('No distribution found');
|
||||
}
|
||||
}
|
||||
|
||||
private getAdditionalPacksArg(paths: string[]): string[] {
|
||||
return paths.length
|
||||
? ['--additional-packs', paths.join(path.delimiter)]
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -160,7 +160,8 @@ export function commandRunner(
|
||||
export function commandRunnerWithProgress<R>(
|
||||
commandId: string,
|
||||
task: ProgressTask<R>,
|
||||
progressOptions: Partial<ProgressOptions>
|
||||
progressOptions: Partial<ProgressOptions>,
|
||||
outputLogger = logger
|
||||
): Disposable {
|
||||
return commands.registerCommand(commandId, async (...args: any[]) => {
|
||||
const startTime = Date.now();
|
||||
@@ -177,9 +178,9 @@ export function commandRunnerWithProgress<R>(
|
||||
if (e instanceof UserCancellationException) {
|
||||
// User has cancelled this action manually
|
||||
if (e.silent) {
|
||||
void logger.log(errorMessage);
|
||||
void outputLogger.log(errorMessage);
|
||||
} else {
|
||||
void showAndLogWarningMessage(errorMessage);
|
||||
void showAndLogWarningMessage(errorMessage, { outputLogger });
|
||||
}
|
||||
} else {
|
||||
// Include the full stack in the error log only.
|
||||
@@ -187,6 +188,7 @@ export function commandRunnerWithProgress<R>(
|
||||
? `${errorMessage}\n${e.stack}`
|
||||
: errorMessage;
|
||||
void showAndLogErrorMessage(errorMessage, {
|
||||
outputLogger,
|
||||
fullMessage
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,7 +61,8 @@ import {
|
||||
showAndLogErrorMessage,
|
||||
showAndLogWarningMessage,
|
||||
showAndLogInformationMessage,
|
||||
showInformationMessageWithAction
|
||||
showInformationMessageWithAction,
|
||||
tmpDir
|
||||
} from './helpers';
|
||||
import { assertNever } from './pure/helpers-pure';
|
||||
import { spawnIdeServer } from './ide-server';
|
||||
@@ -538,6 +539,7 @@ async function activateWithInstalledDistribution(
|
||||
// Note we must update the query history view after showing results as the
|
||||
// display and sorting might depend on the number of results
|
||||
} catch (e) {
|
||||
e.message = `Error running query: ${e.message}`;
|
||||
item.failureReason = e.message;
|
||||
throw e;
|
||||
} finally {
|
||||
@@ -638,7 +640,10 @@ async function activateWithInstalledDistribution(
|
||||
{
|
||||
title: 'Running query',
|
||||
cancellable: true
|
||||
}
|
||||
},
|
||||
|
||||
// Open the query server logger on error since that's usually where the interesting errors appear.
|
||||
queryServerLogger
|
||||
)
|
||||
);
|
||||
interface DatabaseQuickPickItem extends QuickPickItem {
|
||||
@@ -770,7 +775,11 @@ async function activateWithInstalledDistribution(
|
||||
{
|
||||
title: 'Running queries',
|
||||
cancellable: true
|
||||
})
|
||||
},
|
||||
|
||||
// Open the query server logger on error since that's usually where the interesting errors appear.
|
||||
queryServerLogger
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commandRunnerWithProgress(
|
||||
@@ -783,7 +792,10 @@ async function activateWithInstalledDistribution(
|
||||
{
|
||||
title: 'Running query',
|
||||
cancellable: true
|
||||
})
|
||||
},
|
||||
// Open the query server logger on error since that's usually where the interesting errors appear.
|
||||
queryServerLogger
|
||||
)
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(
|
||||
@@ -798,7 +810,11 @@ async function activateWithInstalledDistribution(
|
||||
{
|
||||
title: 'Running query',
|
||||
cancellable: true
|
||||
})
|
||||
},
|
||||
|
||||
// Open the query server logger on error since that's usually where the interesting errors appear.
|
||||
queryServerLogger
|
||||
)
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(
|
||||
@@ -809,7 +825,10 @@ async function activateWithInstalledDistribution(
|
||||
displayQuickQuery(ctx, cliServer, databaseUI, progress, token),
|
||||
{
|
||||
title: 'Run Quick Query'
|
||||
}
|
||||
},
|
||||
|
||||
// Open the query server logger on error since that's usually where the interesting errors appear.
|
||||
queryServerLogger
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1003,18 +1022,23 @@ async function activateWithInstalledDistribution(
|
||||
|
||||
// Jump-to-definition and find-references
|
||||
void logger.log('Registering jump-to-definition handlers.');
|
||||
|
||||
// Store contextual queries in a temporary folder so that they are removed
|
||||
// when the application closes. There is no need for the user to interact with them.
|
||||
const contextualQueryStorageDir = path.join(tmpDir.name, 'contextual-query-storage');
|
||||
await fs.ensureDir(contextualQueryStorageDir);
|
||||
languages.registerDefinitionProvider(
|
||||
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
|
||||
new TemplateQueryDefinitionProvider(cliServer, qs, dbm, queryStorageDir)
|
||||
new TemplateQueryDefinitionProvider(cliServer, qs, dbm, contextualQueryStorageDir)
|
||||
);
|
||||
|
||||
languages.registerReferenceProvider(
|
||||
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
|
||||
new TemplateQueryReferenceProvider(cliServer, qs, dbm, queryStorageDir)
|
||||
new TemplateQueryReferenceProvider(cliServer, qs, dbm, contextualQueryStorageDir)
|
||||
);
|
||||
|
||||
const astViewer = new AstViewer();
|
||||
const templateProvider = new TemplatePrintAstProvider(cliServer, qs, dbm, queryStorageDir);
|
||||
const templateProvider = new TemplatePrintAstProvider(cliServer, qs, dbm, contextualQueryStorageDir);
|
||||
|
||||
ctx.subscriptions.push(astViewer);
|
||||
ctx.subscriptions.push(commandRunnerWithProgress('codeQL.viewAst', async (
|
||||
|
||||
@@ -25,7 +25,7 @@ export class AnalysesResultsManager {
|
||||
analysisSummary: AnalysisSummary,
|
||||
publishResults: (analysesResults: AnalysisResults[]) => Promise<void>
|
||||
): Promise<void> {
|
||||
if (this.analysesResults.some(x => x.nwo === analysisSummary.nwo)) {
|
||||
if (this.isAnalysisDownloaded(analysisSummary)) {
|
||||
// We already have the results for this analysis, don't download again.
|
||||
return;
|
||||
}
|
||||
@@ -38,10 +38,13 @@ export class AnalysesResultsManager {
|
||||
}
|
||||
|
||||
public async downloadAnalysesResults(
|
||||
analysesToDownload: AnalysisSummary[],
|
||||
allAnalysesToDownload: AnalysisSummary[],
|
||||
token: CancellationToken | undefined,
|
||||
publishResults: (analysesResults: AnalysisResults[]) => Promise<void>
|
||||
): Promise<void> {
|
||||
// Filter out analyses that we have already downloaded.
|
||||
const analysesToDownload = allAnalysesToDownload.filter(x => !this.isAnalysisDownloaded(x));
|
||||
|
||||
const credentials = await Credentials.initialize(this.ctx);
|
||||
|
||||
void this.logger.log('Downloading and processing analyses results');
|
||||
@@ -134,4 +137,8 @@ export class AnalysesResultsManager {
|
||||
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
private isAnalysisDownloaded(analysis: AnalysisSummary): boolean {
|
||||
return this.analysesResults.some(x => x.nwo === analysis.nwo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import { logger } from '../logging';
|
||||
import { RemoteQueryWorkflowResult } from './remote-query-workflow-result';
|
||||
import { DownloadLink } from './download-link';
|
||||
import { RemoteQuery } from './remote-query';
|
||||
import { RemoteQueryResultIndex, RemoteQueryResultIndexItem } from './remote-query-result-index';
|
||||
import { RemoteQueryFailureIndexItem, RemoteQueryResultIndex, RemoteQuerySuccessIndexItem } from './remote-query-result-index';
|
||||
|
||||
interface ApiResultIndexItem {
|
||||
interface ApiSuccessIndexItem {
|
||||
nwo: string;
|
||||
id: string;
|
||||
results_count: number;
|
||||
@@ -17,6 +17,17 @@ interface ApiResultIndexItem {
|
||||
sarif_file_size?: number;
|
||||
}
|
||||
|
||||
interface ApiFailureIndexItem {
|
||||
nwo: string;
|
||||
id: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
interface ApiResultIndex {
|
||||
successes: ApiSuccessIndexItem[];
|
||||
failures: ApiFailureIndexItem[];
|
||||
}
|
||||
|
||||
export async function getRemoteQueryIndex(
|
||||
credentials: Credentials,
|
||||
remoteQuery: RemoteQuery
|
||||
@@ -31,9 +42,9 @@ export async function getRemoteQueryIndex(
|
||||
|
||||
const artifactList = await listWorkflowRunArtifacts(credentials, owner, repoName, workflowRunId);
|
||||
const resultIndexArtifactId = getArtifactIDfromName('result-index', workflowUri, artifactList);
|
||||
const resultIndexItems = await getResultIndexItems(credentials, owner, repoName, resultIndexArtifactId);
|
||||
const resultIndex = await getResultIndex(credentials, owner, repoName, resultIndexArtifactId);
|
||||
|
||||
const items = resultIndexItems.map(item => {
|
||||
const successes = resultIndex?.successes.map(item => {
|
||||
const artifactId = getArtifactIDfromName(item.id, workflowUri, artifactList);
|
||||
|
||||
return {
|
||||
@@ -42,13 +53,22 @@ export async function getRemoteQueryIndex(
|
||||
nwo: item.nwo,
|
||||
resultCount: item.results_count,
|
||||
bqrsFileSize: item.bqrs_file_size,
|
||||
sarifFileSize: item.sarif_file_size,
|
||||
} as RemoteQueryResultIndexItem;
|
||||
sarifFileSize: item.sarif_file_size
|
||||
} as RemoteQuerySuccessIndexItem;
|
||||
});
|
||||
|
||||
const failures = resultIndex?.failures.map(item => {
|
||||
return {
|
||||
id: item.id.toString(),
|
||||
nwo: item.nwo,
|
||||
error: item.error
|
||||
} as RemoteQueryFailureIndexItem;
|
||||
});
|
||||
|
||||
return {
|
||||
artifactsUrlPath,
|
||||
items
|
||||
successes: successes || [],
|
||||
failures: failures || []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -81,17 +101,17 @@ export async function downloadArtifactFromLink(
|
||||
* @param workflowRunId The ID of the workflow run to get the result index for.
|
||||
* @returns An object containing the result index.
|
||||
*/
|
||||
async function getResultIndexItems(
|
||||
async function getResultIndex(
|
||||
credentials: Credentials,
|
||||
owner: string,
|
||||
repo: string,
|
||||
artifactId: number
|
||||
): Promise<ApiResultIndexItem[]> {
|
||||
): Promise<ApiResultIndex | undefined> {
|
||||
const artifactPath = await downloadArtifact(credentials, owner, repo, artifactId);
|
||||
const indexFilePath = path.join(artifactPath, 'index.json');
|
||||
if (!(await fs.pathExists(indexFilePath))) {
|
||||
void showAndLogWarningMessage('Could not find an `index.json` file in the result artifact.');
|
||||
return [];
|
||||
return undefined;
|
||||
}
|
||||
const resultIndex = await fs.readFile(path.join(artifactPath, 'index.json'), 'utf8');
|
||||
|
||||
|
||||
@@ -82,7 +82,8 @@ export class RemoteQueriesInterfaceManager {
|
||||
totalResultCount: totalResultCount,
|
||||
executionTimestamp: this.formatDate(query.executionStartTime),
|
||||
executionDuration: executionDuration,
|
||||
analysisSummaries: analysisSummaries
|
||||
analysisSummaries: analysisSummaries,
|
||||
analysisFailures: queryResult.analysisFailures,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,8 @@ export class RemoteQueriesManager {
|
||||
}
|
||||
|
||||
private mapQueryResult(executionEndTime: Date, resultIndex: RemoteQueryResultIndex, queryId: string): RemoteQueryResult {
|
||||
const analysisSummaries = resultIndex.items.map(item => ({
|
||||
|
||||
const analysisSummaries = resultIndex.successes.map(item => ({
|
||||
nwo: item.nwo,
|
||||
resultCount: item.resultCount,
|
||||
fileSizeInBytes: item.sarifFileSize ? item.sarifFileSize : item.bqrsFileSize,
|
||||
@@ -150,10 +151,15 @@ export class RemoteQueriesManager {
|
||||
queryId,
|
||||
} as DownloadLink
|
||||
}));
|
||||
const analysisFailures = resultIndex.failures.map(item => ({
|
||||
nwo: item.nwo,
|
||||
error: item.error
|
||||
}));
|
||||
|
||||
return {
|
||||
executionEndTime,
|
||||
analysisSummaries,
|
||||
analysisFailures
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
export interface RemoteQueryResultIndex {
|
||||
artifactsUrlPath: string;
|
||||
items: RemoteQueryResultIndexItem[];
|
||||
successes: RemoteQuerySuccessIndexItem[];
|
||||
failures: RemoteQueryFailureIndexItem[];
|
||||
}
|
||||
|
||||
export interface RemoteQueryResultIndexItem {
|
||||
export interface RemoteQuerySuccessIndexItem {
|
||||
id: string;
|
||||
artifactId: number;
|
||||
nwo: string;
|
||||
@@ -11,3 +12,10 @@ export interface RemoteQueryResultIndexItem {
|
||||
bqrsFileSize: number;
|
||||
sarifFileSize?: number;
|
||||
}
|
||||
|
||||
export interface RemoteQueryFailureIndexItem {
|
||||
id: string;
|
||||
artifactId: number;
|
||||
nwo: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { DownloadLink } from './download-link';
|
||||
import { AnalysisFailure } from './shared/analysis-failure';
|
||||
|
||||
export interface RemoteQueryResult {
|
||||
executionEndTime: Date;
|
||||
analysisSummaries: AnalysisSummary[];
|
||||
analysisFailures: AnalysisFailure[];
|
||||
}
|
||||
|
||||
export interface AnalysisSummary {
|
||||
|
||||
@@ -166,6 +166,9 @@ async function generateQueryPack(cliServer: cli.CodeQLCliServer, queryFile: stri
|
||||
|
||||
await ensureNameAndSuite(queryPackDir, packRelativePath);
|
||||
|
||||
// Clear the cliServer cache so that the previous qlpack text is purged from the CLI.
|
||||
await cliServer.clearCache();
|
||||
|
||||
const bundlePath = await getPackedBundlePath(queryPackDir);
|
||||
void logger.log(`Compiling and bundling query pack from ${queryPackDir} to ${bundlePath}. (This may take a while.)`);
|
||||
await cliServer.packInstall(queryPackDir);
|
||||
@@ -379,16 +382,12 @@ export async function attemptRerun(
|
||||
) {
|
||||
if (typeof error.message === 'string' && error.message.includes('Some repositories were invalid')) {
|
||||
const invalidRepos = error?.response?.data?.invalid_repos || [];
|
||||
const reposWithoutDbUploads = error?.response?.data?.repos_without_db_uploads || [];
|
||||
void logger.log('Unable to run query on some of the specified repositories');
|
||||
if (invalidRepos.length > 0) {
|
||||
void logger.log(`Invalid repos: ${invalidRepos.join(', ')}`);
|
||||
}
|
||||
if (reposWithoutDbUploads.length > 0) {
|
||||
void logger.log(`Repos without DB uploads: ${reposWithoutDbUploads.join(', ')}`);
|
||||
}
|
||||
|
||||
if (invalidRepos.length + reposWithoutDbUploads.length === repositories.length) {
|
||||
if (invalidRepos.length === repositories.length) {
|
||||
// Every repo is invalid in some way
|
||||
void showAndLogErrorMessage('Unable to run query on any of the specified repositories.');
|
||||
return;
|
||||
@@ -397,7 +396,7 @@ export async function attemptRerun(
|
||||
const popupMessage = 'Unable to run query on some of the specified repositories. [See logs for more details](command:codeQL.showLogs).';
|
||||
const rerunQuery = await showInformationMessageWithAction(popupMessage, 'Rerun on the valid repositories only');
|
||||
if (rerunQuery) {
|
||||
const validRepositories = repositories.filter(r => !invalidRepos.includes(r) && !reposWithoutDbUploads.includes(r));
|
||||
const validRepositories = repositories.filter(r => !invalidRepos.includes(r));
|
||||
void logger.log(`Rerunning query on set of valid repositories: ${JSON.stringify(validRepositories)}`);
|
||||
return await runRemoteQueriesApiRequest(credentials, ref, language, validRepositories, owner, repo, queryPackBase64, dryRun);
|
||||
}
|
||||
|
||||
@@ -83,6 +83,16 @@ export const sampleRemoteQueryResult: RemoteQueryResult = {
|
||||
queryId: 'query.ql-123-xyz'
|
||||
}
|
||||
}
|
||||
],
|
||||
analysisFailures: [
|
||||
{
|
||||
nwo: 'big-corp/repo5',
|
||||
error: 'Error message'
|
||||
},
|
||||
{
|
||||
nwo: 'big-corp/repo6',
|
||||
error: 'Error message'
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface AnalysisFailure {
|
||||
nwo: string,
|
||||
error: string
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DownloadLink } from '../download-link';
|
||||
import { AnalysisFailure } from './analysis-failure';
|
||||
|
||||
export interface RemoteQueryResult {
|
||||
queryTitle: string;
|
||||
@@ -10,7 +11,8 @@ export interface RemoteQueryResult {
|
||||
totalResultCount: number;
|
||||
executionTimestamp: string;
|
||||
executionDuration: string;
|
||||
analysisSummaries: AnalysisSummary[]
|
||||
analysisSummaries: AnalysisSummary[],
|
||||
analysisFailures: AnalysisFailure[];
|
||||
}
|
||||
|
||||
export interface AnalysisSummary {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import * as octicons from '../../view/octicons';
|
||||
import styled from 'styled-components';
|
||||
import { DownloadIcon } from '@primer/octicons-react';
|
||||
|
||||
const ButtonLink = styled.a`
|
||||
display: inline-block;
|
||||
@@ -16,7 +16,7 @@ const ButtonLink = styled.a`
|
||||
|
||||
const DownloadButton = ({ text, onClick }: { text: string, onClick: () => void }) => (
|
||||
<ButtonLink onClick={onClick}>
|
||||
{octicons.download}{text}
|
||||
<DownloadIcon size={16} />{text}
|
||||
</ButtonLink>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import * as Rdom from 'react-dom';
|
||||
import { ThemeProvider } from '@primer/react';
|
||||
import { Flash, ThemeProvider } from '@primer/react';
|
||||
import { ToRemoteQueriesMessage } from '../../pure/interface-types';
|
||||
import { AnalysisSummary, RemoteQueryResult } from '../shared/remote-query-result';
|
||||
import * as octicons from '../../view/octicons';
|
||||
|
||||
import { vscode } from '../../view/vscode-api';
|
||||
|
||||
@@ -17,7 +16,7 @@ import DownloadButton from './DownloadButton';
|
||||
import { AnalysisResults } from '../shared/analysis-result';
|
||||
import DownloadSpinner from './DownloadSpinner';
|
||||
import CollapsibleItem from './CollapsibleItem';
|
||||
import { FileSymlinkFileIcon } from '@primer/octicons-react';
|
||||
import { AlertIcon, CodeSquareIcon, FileCodeIcon, FileSymlinkFileIcon, RepoIcon } from '@primer/octicons-react';
|
||||
|
||||
const numOfReposInContractedMode = 10;
|
||||
|
||||
@@ -31,7 +30,8 @@ const emptyQueryResult: RemoteQueryResult = {
|
||||
totalResultCount: 0,
|
||||
executionTimestamp: '',
|
||||
executionDuration: '',
|
||||
analysisSummaries: []
|
||||
analysisSummaries: [],
|
||||
analysisFailures: [],
|
||||
};
|
||||
|
||||
const downloadAnalysisResults = (analysisSummary: AnalysisSummary) => {
|
||||
@@ -78,12 +78,14 @@ const QueryInfo = (queryResult: RemoteQueryResult) => (
|
||||
{queryResult.totalResultCount} results from running against {queryResult.totalRepositoryCount} repositories
|
||||
({queryResult.executionDuration}), {queryResult.executionTimestamp}
|
||||
<VerticalSpace size={1} />
|
||||
<span className="vscode-codeql__query-file">{octicons.file}
|
||||
<span className="vscode-codeql__query-file">
|
||||
<FileCodeIcon size={16} />
|
||||
<a className="vscode-codeql__query-file-link" href="#" onClick={() => openQueryFile(queryResult)}>
|
||||
{queryResult.queryFileName}
|
||||
</a>
|
||||
</span>
|
||||
<span>{octicons.codeSquare}
|
||||
<span>
|
||||
<CodeSquareIcon size={16} />
|
||||
<a className="vscode-codeql__query-file-link" href="#" onClick={() => openQueryTextVirtualFile(queryResult)}>
|
||||
query
|
||||
</a>
|
||||
@@ -91,6 +93,31 @@ const QueryInfo = (queryResult: RemoteQueryResult) => (
|
||||
</>
|
||||
);
|
||||
|
||||
const Failures = (queryResult: RemoteQueryResult) => {
|
||||
if (queryResult.analysisFailures.length === 0) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<VerticalSpace size={3} />
|
||||
<Flash variant="danger">
|
||||
{queryResult.analysisFailures.map((f, i) => (
|
||||
<>
|
||||
<p className="vscode-codeql__analysis-failure" key={i}>
|
||||
<AlertIcon size={16} />
|
||||
<b>{f.nwo}: </b>
|
||||
{f.error}
|
||||
</p>
|
||||
{
|
||||
i === queryResult.analysisFailures.length - 1 ? <></> : <VerticalSpace size={1} />
|
||||
}
|
||||
</>
|
||||
))}
|
||||
</Flash>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SummaryTitleWithResults = ({
|
||||
queryResult,
|
||||
analysesResults
|
||||
@@ -155,7 +182,7 @@ const SummaryItem = ({
|
||||
analysisResults: AnalysisResults | undefined
|
||||
}) => (
|
||||
<span>
|
||||
<span className="vscode-codeql__analysis-item">{octicons.repo}</span>
|
||||
<span className="vscode-codeql__analysis-item"><RepoIcon size={16} /></span>
|
||||
<span className="vscode-codeql__analysis-item">{analysisSummary.nwo}</span>
|
||||
<span className="vscode-codeql__analysis-item"><Badge text={analysisSummary.resultCount.toString()} /></span>
|
||||
<span className="vscode-codeql__analysis-item">
|
||||
@@ -293,9 +320,10 @@ export function RemoteQueries(): JSX.Element {
|
||||
|
||||
try {
|
||||
return <div>
|
||||
<ThemeProvider>
|
||||
<ThemeProvider colorMode="auto">
|
||||
<ViewTitle>{queryResult.queryTitle}</ViewTitle>
|
||||
<QueryInfo {...queryResult} />
|
||||
<Failures {...queryResult} />
|
||||
<Summary queryResult={queryResult} analysesResults={analysesResults} />
|
||||
{showAnalysesResults && <AnalysesResults analysesResults={analysesResults} totalResults={queryResult.totalResultCount} />}
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
.octicon {
|
||||
fill: var(--vscode-editor-foreground);
|
||||
height: 1.2em;
|
||||
width: 1.2em;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.octicon-light {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.vscode-codeql__query-file {
|
||||
padding-right: 1em;
|
||||
}
|
||||
@@ -64,3 +52,10 @@
|
||||
.vscode-codeql__analysis-result-file-link {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.vscode-codeql__analysis-failure {
|
||||
margin: 0;
|
||||
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
|
||||
Liberation Mono, monospace;
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
@@ -20,23 +20,3 @@ export const listUnordered = <svg className="octicon octicon-light" width="16" h
|
||||
export const info = <svg className="octicon octicon-light" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" >
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M8.568 1.03a6.8 6.8 0 0 1 4.192 2.02 7.06 7.06 0 0 1 .46 9.39 6.85 6.85 0 0 1-8.58 1.74 7 7 0 0 1-3.12-3.5 7.12 7.12 0 0 1-.23-4.71 7 7 0 0 1 2.77-3.79 6.8 6.8 0 0 1 4.508-1.15zm.472 12.85a5.89 5.89 0 0 0 3.41-2.07 6.07 6.07 0 0 0-.4-8.06 5.82 5.82 0 0 0-7.43-.74 6.06 6.06 0 0 0 .5 10.29 5.81 5.81 0 0 0 3.92.58zM8.51 7h-1v4h1V7zm0-2h-1v1h1V5z" />
|
||||
</svg>;
|
||||
|
||||
/**
|
||||
* The icons below come from https://primer.style/octicons/
|
||||
*/
|
||||
|
||||
export const file = <svg className="octicon octicon-light" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" d="M3.75 1.5a.25.25 0 00-.25.25v11.5c0 .138.112.25.25.25h8.5a.25.25 0 00.25-.25V6H9.75A1.75 1.75 0 018 4.25V1.5H3.75zm5.75.56v2.19c0 .138.112.25.25.25h2.19L9.5 2.06zM2 1.75C2 .784 2.784 0 3.75 0h5.086c.464 0 .909.184 1.237.513l3.414 3.414c.329.328.513.773.513 1.237v8.086A1.75 1.75 0 0112.25 15h-8.5A1.75 1.75 0 012 13.25V1.75z"></path>
|
||||
</svg>;
|
||||
|
||||
export const codeSquare = <svg className="octicon octicon-light" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" d="M1.75 1.5a.25.25 0 00-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25V1.75a.25.25 0 00-.25-.25H1.75zM0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0114.25 16H1.75A1.75 1.75 0 010 14.25V1.75zm9.22 3.72a.75.75 0 000 1.06L10.69 8 9.22 9.47a.75.75 0 101.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0zM6.78 6.53a.75.75 0 00-1.06-1.06l-2 2a.75.75 0 000 1.06l2 2a.75.75 0 101.06-1.06L5.31 8l1.47-1.47z"></path>
|
||||
</svg>;
|
||||
|
||||
export const repo = <svg className="octicon octicon-light" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"></path>
|
||||
</svg>;
|
||||
|
||||
export const download = <svg className="octicon octicon-light" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" d="M7.47 10.78a.75.75 0 001.06 0l3.75-3.75a.75.75 0 00-1.06-1.06L8.75 8.44V1.75a.75.75 0 00-1.5 0v6.69L4.78 5.97a.75.75 0 00-1.06 1.06l3.75 3.75zM3.75 13a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5z"></path>
|
||||
</svg>;
|
||||
|
||||
@@ -96,12 +96,16 @@ describe('Remote queries', function() {
|
||||
expect(fs.existsSync(path.join(queryPackDir, 'not-in-pack.ql'))).to.be.false;
|
||||
|
||||
// the compiled pack
|
||||
const compiledPackDir = path.join(queryPackDir, '.codeql/pack/github/remote-query-pack/0.0.0/');
|
||||
const compiledPackDir = path.join(queryPackDir, '.codeql/pack/codeql-remote/query/0.0.0/');
|
||||
printDirectoryContents(compiledPackDir);
|
||||
|
||||
expect(fs.existsSync(path.join(compiledPackDir, 'in-pack.ql'))).to.be.true;
|
||||
expect(fs.existsSync(path.join(compiledPackDir, 'lib.qll'))).to.be.true;
|
||||
expect(fs.existsSync(path.join(compiledPackDir, 'qlpack.yml'))).to.be.true;
|
||||
// should have generated a correct qlpack file
|
||||
const qlpackContents: any = yaml.safeLoad(fs.readFileSync(path.join(compiledPackDir, 'qlpack.yml'), 'utf8'));
|
||||
expect(qlpackContents.name).to.equal('codeql-remote/query');
|
||||
|
||||
// depending on the cli version, we should have one of these files
|
||||
expect(
|
||||
fs.existsSync(path.join(compiledPackDir, 'qlpack.lock.yml')) ||
|
||||
@@ -211,7 +215,7 @@ describe('Remote queries', function() {
|
||||
expect(fs.existsSync(path.join(queryPackDir, 'not-in-pack.ql'))).to.be.false;
|
||||
|
||||
// the compiled pack
|
||||
const compiledPackDir = path.join(queryPackDir, '.codeql/pack/github/remote-query-pack/0.0.0/');
|
||||
const compiledPackDir = path.join(queryPackDir, '.codeql/pack/codeql-remote/query/0.0.0/');
|
||||
printDirectoryContents(compiledPackDir);
|
||||
expect(fs.existsSync(path.join(compiledPackDir, 'otherfolder/lib.qll'))).to.be.true;
|
||||
expect(fs.existsSync(path.join(compiledPackDir, 'subfolder/in-pack.ql'))).to.be.true;
|
||||
|
||||
@@ -114,11 +114,10 @@ describe('run-remote-query', function() {
|
||||
let mod: any;
|
||||
|
||||
const error = {
|
||||
message: 'Unable to run query on the specified repositories. Some repositories were invalid or don\'t have database uploads enabled.',
|
||||
message: 'Unable to run query on the specified repositories. Some repositories were invalid.',
|
||||
response: {
|
||||
data: {
|
||||
invalid_repos: ['abc/def', 'ghi/jkl'],
|
||||
repos_without_db_uploads: ['mno/pqr', 'stu/vwx']
|
||||
invalid_repos: ['abc/def', 'ghi/jkl']
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -151,7 +150,7 @@ describe('run-remote-query', function() {
|
||||
});
|
||||
|
||||
it('should return and log error if it can\'t run on any repos', async () => {
|
||||
const repositories = ['abc/def', 'ghi/jkl', 'mno/pqr', 'stu/vwx'];
|
||||
const repositories = ['abc/def', 'ghi/jkl'];
|
||||
|
||||
// make the function call
|
||||
await mod.attemptRerun(error, credentials, ref, language, repositories, query, owner, repo);
|
||||
@@ -159,12 +158,11 @@ describe('run-remote-query', function() {
|
||||
// check logging output
|
||||
expect(logSpy.firstCall.args[0]).to.contain('Unable to run query');
|
||||
expect(logSpy.secondCall.args[0]).to.contain('Invalid repos: abc/def, ghi/jkl');
|
||||
expect(logSpy.thirdCall.args[0]).to.contain('Repos without DB uploads: mno/pqr, stu/vwx');
|
||||
expect(showAndLogErrorMessageSpy.firstCall.args[0]).to.contain('Unable to run query on any');
|
||||
});
|
||||
|
||||
it('should list invalid repos and repos without DB uploads, and rerun on valid ones', async () => {
|
||||
const repositories = ['foo/bar', 'abc/def', 'ghi/jkl', 'mno/pqr', 'foo/baz'];
|
||||
it('should list invalid repos and rerun on valid ones', async () => {
|
||||
const repositories = ['foo/bar', 'abc/def', 'ghi/jkl', 'foo/baz'];
|
||||
|
||||
// fake return values
|
||||
showInformationMessageWithActionSpy.resolves(true);
|
||||
@@ -175,7 +173,6 @@ describe('run-remote-query', function() {
|
||||
// check logging output
|
||||
expect(logSpy.firstCall.args[0]).to.contain('Unable to run query');
|
||||
expect(logSpy.secondCall.args[0]).to.contain('Invalid repos: abc/def, ghi/jkl');
|
||||
expect(logSpy.thirdCall.args[0]).to.contain('Repos without DB uploads: mno/pqr');
|
||||
|
||||
// check that the correct information message is displayed
|
||||
expect(showInformationMessageWithActionSpy.firstCall.args[0]).to.contain('Unable to run query on some');
|
||||
@@ -198,8 +195,4 @@ describe('run-remote-query', function() {
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
describe('runRemoteQuery', () => {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user