Merge branch 'main' into aeisenberg/remote-query-save

This commit is contained in:
Andrew Eisenberg
2022-02-22 10:13:16 -08:00
20 changed files with 208 additions and 103 deletions

View File

@@ -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

View File

@@ -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)]
: [];
}
}
/**

View File

@@ -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
});
}

View File

@@ -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 (

View File

@@ -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);
}
}

View File

@@ -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');

View File

@@ -82,7 +82,8 @@ export class RemoteQueriesInterfaceManager {
totalResultCount: totalResultCount,
executionTimestamp: this.formatDate(query.executionStartTime),
executionDuration: executionDuration,
analysisSummaries: analysisSummaries
analysisSummaries: analysisSummaries,
analysisFailures: queryResult.analysisFailures,
};
}

View File

@@ -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
};
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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'
},
]
};

View File

@@ -0,0 +1,4 @@
export interface AnalysisFailure {
nwo: string,
error: string
}

View File

@@ -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 {

View File

@@ -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>
);

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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>;

View File

@@ -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;

View File

@@ -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
});
});