Merge remote-tracking branch 'origin/main' into koesie10/export-results
This commit is contained in:
5
extensions/ql-vscode/src/common/app.ts
Normal file
5
extensions/ql-vscode/src/common/app.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { AppEventEmitter } from './events';
|
||||||
|
|
||||||
|
export interface App {
|
||||||
|
createEventEmitter<T>(): AppEventEmitter<T>;
|
||||||
|
}
|
||||||
10
extensions/ql-vscode/src/common/events.ts
Normal file
10
extensions/ql-vscode/src/common/events.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Disposable } from '../pure/disposable-object';
|
||||||
|
|
||||||
|
export interface AppEvent<T> {
|
||||||
|
(listener: (event: T) => void): Disposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppEventEmitter<T> {
|
||||||
|
event: AppEvent<T>;
|
||||||
|
fire(data: T): void;
|
||||||
|
}
|
||||||
7
extensions/ql-vscode/src/common/vscode/events.ts
Normal file
7
extensions/ql-vscode/src/common/vscode/events.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { AppEventEmitter } from '../events';
|
||||||
|
|
||||||
|
export class VSCodeAppEventEmitter<T>
|
||||||
|
extends vscode.EventEmitter<T>
|
||||||
|
implements AppEventEmitter<T> {
|
||||||
|
}
|
||||||
9
extensions/ql-vscode/src/common/vscode/vscode-app.ts
Normal file
9
extensions/ql-vscode/src/common/vscode/vscode-app.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { App } from '../app';
|
||||||
|
import { AppEventEmitter } from '../events';
|
||||||
|
import { VSCodeAppEventEmitter } from './events';
|
||||||
|
|
||||||
|
export class ExtensionApp implements App {
|
||||||
|
public createEventEmitter<T>(): AppEventEmitter<T> {
|
||||||
|
return new VSCodeAppEventEmitter<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -948,6 +948,12 @@ async function activateWithInstalledDistribution(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ctx.subscriptions.push(
|
||||||
|
commandRunner('codeQL.copyVariantAnalysisRepoList', async (variantAnalysisId: number) => {
|
||||||
|
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysisId);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
ctx.subscriptions.push(
|
ctx.subscriptions.push(
|
||||||
commandRunner('codeQL.monitorVariantAnalysis', async (
|
commandRunner('codeQL.monitorVariantAnalysis', async (
|
||||||
variantAnalysis: VariantAnalysis,
|
variantAnalysis: VariantAnalysis,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
// Avoid explicitly referencing Disposable type in vscode.
|
// Avoid explicitly referencing Disposable type in vscode.
|
||||||
// This file cannot have dependencies on the vscode API.
|
// This file cannot have dependencies on the vscode API.
|
||||||
interface Disposable {
|
export interface Disposable {
|
||||||
dispose(): any;
|
dispose(): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -472,6 +472,10 @@ export interface OpenQueryTextMessage {
|
|||||||
t: 'openQueryText';
|
t: 'openQueryText';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CopyRepositoryListMessage {
|
||||||
|
t: 'copyRepositoryList';
|
||||||
|
}
|
||||||
|
|
||||||
export interface OpenLogsMessage {
|
export interface OpenLogsMessage {
|
||||||
t: 'openLogs';
|
t: 'openLogs';
|
||||||
}
|
}
|
||||||
@@ -490,5 +494,6 @@ export type FromVariantAnalysisMessage =
|
|||||||
| RequestRepositoryResultsMessage
|
| RequestRepositoryResultsMessage
|
||||||
| OpenQueryFileMessage
|
| OpenQueryFileMessage
|
||||||
| OpenQueryTextMessage
|
| OpenQueryTextMessage
|
||||||
|
| CopyRepositoryListMessage
|
||||||
| OpenLogsMessage
|
| OpenLogsMessage
|
||||||
| CancelVariantAnalysisMessage;
|
| CancelVariantAnalysisMessage;
|
||||||
|
|||||||
@@ -1256,11 +1256,15 @@ export class QueryHistoryManager extends DisposableObject {
|
|||||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||||
|
|
||||||
// Remote queries only
|
// Remote queries only
|
||||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem || finalSingleItem.t !== 'remote') {
|
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (finalSingleItem.t === 'remote') {
|
||||||
await commands.executeCommand('codeQL.copyRepoList', finalSingleItem.queryId);
|
await commands.executeCommand('codeQL.copyRepoList', finalSingleItem.queryId);
|
||||||
|
} else if (finalSingleItem.t === 'variant-analysis') {
|
||||||
|
await commands.executeCommand('codeQL.copyVariantAnalysisRepoList', finalSingleItem.variantAnalysis.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleExportResults(
|
async handleExportResults(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import * as ghApiClient from './gh-api/gh-api-client';
|
import * as ghApiClient from './gh-api/gh-api-client';
|
||||||
import { CancellationToken, commands, EventEmitter, ExtensionContext, Uri, window } from 'vscode';
|
import { CancellationToken, commands, env, EventEmitter, ExtensionContext, Uri, window } from 'vscode';
|
||||||
import { DisposableObject } from '../pure/disposable-object';
|
import { DisposableObject } from '../pure/disposable-object';
|
||||||
import { Credentials } from '../authentication';
|
import { Credentials } from '../authentication';
|
||||||
import { VariantAnalysisMonitor } from './variant-analysis-monitor';
|
import { VariantAnalysisMonitor } from './variant-analysis-monitor';
|
||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { createTimestampFile, showAndLogErrorMessage, showAndLogInformationMessage } from '../helpers';
|
import { createTimestampFile, showAndLogErrorMessage, showAndLogInformationMessage } from '../helpers';
|
||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
|
import * as os from 'os';
|
||||||
import { cancelVariantAnalysis } from './gh-api/gh-actions-api-client';
|
import { cancelVariantAnalysis } from './gh-api/gh-actions-api-client';
|
||||||
import { ProgressCallback, UserCancellationException } from '../commandRunner';
|
import { ProgressCallback, UserCancellationException } from '../commandRunner';
|
||||||
import { CodeQLCliServer } from '../cli';
|
import { CodeQLCliServer } from '../cli';
|
||||||
@@ -388,6 +389,27 @@ export class VariantAnalysisManager extends DisposableObject implements VariantA
|
|||||||
await cancelVariantAnalysis(credentials, variantAnalysis);
|
await cancelVariantAnalysis(credentials, variantAnalysis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async copyRepoListToClipboard(variantAnalysisId: number) {
|
||||||
|
const variantAnalysis = this.variantAnalyses.get(variantAnalysisId);
|
||||||
|
if (!variantAnalysis) {
|
||||||
|
throw new Error(`No variant analysis with id: ${variantAnalysisId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullNames = variantAnalysis.scannedRepos?.filter(a => a.resultCount && a.resultCount > 0).map(a => a.repository.fullName);
|
||||||
|
if (!fullNames || fullNames.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = [
|
||||||
|
'"new-repo-list": [',
|
||||||
|
...fullNames.slice(0, -1).map(repo => ` "${repo}",`),
|
||||||
|
` "${fullNames[fullNames.length - 1]}"`,
|
||||||
|
']'
|
||||||
|
];
|
||||||
|
|
||||||
|
await env.clipboard.writeText(text.join(os.EOL));
|
||||||
|
}
|
||||||
|
|
||||||
private getRepoStatesStoragePath(variantAnalysisId: number): string {
|
private getRepoStatesStoragePath(variantAnalysisId: number): string {
|
||||||
return path.join(
|
return path.join(
|
||||||
this.getVariantAnalysisStorageLocation(variantAnalysisId),
|
this.getVariantAnalysisStorageLocation(variantAnalysisId),
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ export class VariantAnalysisView extends AbstractWebview<ToVariantAnalysisMessag
|
|||||||
case 'openQueryText':
|
case 'openQueryText':
|
||||||
await this.openQueryText();
|
await this.openQueryText();
|
||||||
break;
|
break;
|
||||||
|
case 'copyRepositoryList':
|
||||||
|
void commands.executeCommand('codeQL.copyVariantAnalysisRepoList', this.variantAnalysisId);
|
||||||
|
break;
|
||||||
case 'openLogs':
|
case 'openLogs':
|
||||||
await this.openLogs();
|
await this.openLogs();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ const stopQuery = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const copyRepositoryList = () => {
|
||||||
|
vscode.postMessage({
|
||||||
|
t: 'copyRepositoryList',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const openLogs = () => {
|
const openLogs = () => {
|
||||||
vscode.postMessage({
|
vscode.postMessage({
|
||||||
t: 'openLogs',
|
t: 'openLogs',
|
||||||
@@ -95,7 +101,7 @@ export function VariantAnalysis({
|
|||||||
onOpenQueryFileClick={openQueryFile}
|
onOpenQueryFileClick={openQueryFile}
|
||||||
onViewQueryTextClick={openQueryText}
|
onViewQueryTextClick={openQueryText}
|
||||||
onStopQueryClick={stopQuery}
|
onStopQueryClick={stopQuery}
|
||||||
onCopyRepositoryListClick={() => console.log('Copy repository list')}
|
onCopyRepositoryListClick={copyRepositoryList}
|
||||||
onExportResultsClick={() => console.log('Export results')}
|
onExportResultsClick={() => console.log('Export results')}
|
||||||
onViewLogsClick={openLogs}
|
onViewLogsClick={openLogs}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import { assert, expect } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
import { CancellationTokenSource, commands, extensions, QuickPickItem, Uri, window } from 'vscode';
|
import { CancellationTokenSource, commands, env, extensions, QuickPickItem, Uri, window } from 'vscode';
|
||||||
import { CodeQLExtensionInterface } from '../../../extension';
|
import { CodeQLExtensionInterface } from '../../../extension';
|
||||||
import { logger } from '../../../logging';
|
import { logger } from '../../../logging';
|
||||||
import * as config from '../../../config';
|
import * as config from '../../../config';
|
||||||
@@ -16,7 +16,10 @@ import { storagePath } from '../global.helper';
|
|||||||
import { VariantAnalysisResultsManager } from '../../../remote-queries/variant-analysis-results-manager';
|
import { VariantAnalysisResultsManager } from '../../../remote-queries/variant-analysis-results-manager';
|
||||||
import { createMockVariantAnalysis } from '../../factories/remote-queries/shared/variant-analysis';
|
import { createMockVariantAnalysis } from '../../factories/remote-queries/shared/variant-analysis';
|
||||||
import * as VariantAnalysisModule from '../../../remote-queries/shared/variant-analysis';
|
import * as VariantAnalysisModule from '../../../remote-queries/shared/variant-analysis';
|
||||||
import { createMockScannedRepos } from '../../factories/remote-queries/shared/scanned-repositories';
|
import {
|
||||||
|
createMockScannedRepo,
|
||||||
|
createMockScannedRepos
|
||||||
|
} from '../../factories/remote-queries/shared/scanned-repositories';
|
||||||
import {
|
import {
|
||||||
VariantAnalysis,
|
VariantAnalysis,
|
||||||
VariantAnalysisScannedRepository,
|
VariantAnalysisScannedRepository,
|
||||||
@@ -252,7 +255,9 @@ describe('Variant Analysis Manager', async function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('when credentials are invalid', async () => {
|
describe('when credentials are invalid', async () => {
|
||||||
beforeEach(async () => { sandbox.stub(Credentials, 'initialize').resolves(undefined); });
|
beforeEach(async () => {
|
||||||
|
sandbox.stub(Credentials, 'initialize').resolves(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
it('should return early if credentials are wrong', async () => {
|
it('should return early if credentials are wrong', async () => {
|
||||||
try {
|
try {
|
||||||
@@ -695,4 +700,121 @@ describe('Variant Analysis Manager', async function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('copyRepoListToClipboard', async () => {
|
||||||
|
let variantAnalysis: VariantAnalysis;
|
||||||
|
let variantAnalysisStorageLocation: string;
|
||||||
|
|
||||||
|
let writeTextStub: sinon.SinonStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
variantAnalysis = createMockVariantAnalysis({});
|
||||||
|
|
||||||
|
variantAnalysisStorageLocation = variantAnalysisManager.getVariantAnalysisStorageLocation(variantAnalysis.id);
|
||||||
|
await createTimestampFile(variantAnalysisStorageLocation);
|
||||||
|
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||||
|
|
||||||
|
writeTextStub = sinon.stub();
|
||||||
|
sinon.stub(env, 'clipboard').value({
|
||||||
|
writeText: writeTextStub,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the variant analysis does not have any repositories', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await variantAnalysisManager.rehydrateVariantAnalysis({
|
||||||
|
...variantAnalysis,
|
||||||
|
scannedRepos: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not copy any text', async () => {
|
||||||
|
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysis.id);
|
||||||
|
|
||||||
|
expect(writeTextStub).not.to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the variant analysis does not have any repositories with results', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await variantAnalysisManager.rehydrateVariantAnalysis({
|
||||||
|
...variantAnalysis,
|
||||||
|
scannedRepos: [
|
||||||
|
{
|
||||||
|
...createMockScannedRepo(),
|
||||||
|
resultCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...createMockScannedRepo(),
|
||||||
|
resultCount: undefined,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not copy any text', async () => {
|
||||||
|
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysis.id);
|
||||||
|
|
||||||
|
expect(writeTextStub).not.to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the variant analysis has repositories with results', () => {
|
||||||
|
const scannedRepos = [
|
||||||
|
{
|
||||||
|
...createMockScannedRepo(),
|
||||||
|
resultCount: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...createMockScannedRepo(),
|
||||||
|
resultCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...createMockScannedRepo(),
|
||||||
|
resultCount: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...createMockScannedRepo(),
|
||||||
|
resultCount: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...createMockScannedRepo(),
|
||||||
|
resultCount: 5,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await variantAnalysisManager.rehydrateVariantAnalysis({
|
||||||
|
...variantAnalysis,
|
||||||
|
scannedRepos,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should copy text', async () => {
|
||||||
|
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysis.id);
|
||||||
|
|
||||||
|
expect(writeTextStub).to.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be valid JSON when put in object', async () => {
|
||||||
|
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysis.id);
|
||||||
|
|
||||||
|
const text = writeTextStub.getCalls()[0].lastArg;
|
||||||
|
|
||||||
|
const parsed = JSON.parse('{' + text + '}');
|
||||||
|
|
||||||
|
expect(parsed).to.deep.eq({
|
||||||
|
'new-repo-list': [
|
||||||
|
scannedRepos[0].repository.fullName,
|
||||||
|
scannedRepos[2].repository.fullName,
|
||||||
|
scannedRepos[4].repository.fullName,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user