Add copying of repository list for variant analyses
This adds the ability to copy the repository list for variant analyses from the context menu in the query history.
This commit is contained in:
@@ -934,6 +934,12 @@ async function activateWithInstalledDistribution(
|
||||
})
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commandRunner('codeQL.copyVariantAnalysisRepoList', async (variantAnalysisId: number) => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysisId);
|
||||
})
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commandRunner('codeQL.monitorVariantAnalysis', async (
|
||||
variantAnalysis: VariantAnalysis,
|
||||
|
||||
@@ -1256,11 +1256,15 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
// Remote queries only
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem || finalSingleItem.t !== 'remote') {
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
await commands.executeCommand('codeQL.copyRepoList', finalSingleItem.queryId);
|
||||
if (finalSingleItem.t === 'remote') {
|
||||
await commands.executeCommand('codeQL.copyRepoList', finalSingleItem.queryId);
|
||||
} else if (finalSingleItem.t === 'variant-analysis') {
|
||||
await commands.executeCommand('codeQL.copyVariantAnalysisRepoList', finalSingleItem.variantAnalysis.id);
|
||||
}
|
||||
}
|
||||
|
||||
async handleExportResults(): Promise<void> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as path from 'path';
|
||||
|
||||
import * as ghApiClient from './gh-api/gh-api-client';
|
||||
import { CancellationToken, commands, EventEmitter, ExtensionContext, window } from 'vscode';
|
||||
import { CancellationToken, commands, env, EventEmitter, ExtensionContext, window } from 'vscode';
|
||||
import { DisposableObject } from '../pure/disposable-object';
|
||||
import { Credentials } from '../authentication';
|
||||
import { VariantAnalysisMonitor } from './variant-analysis-monitor';
|
||||
@@ -24,6 +24,7 @@ import { processUpdatedVariantAnalysis, processVariantAnalysisRepositoryTask } f
|
||||
import PQueue from 'p-queue';
|
||||
import { createTimestampFile, showAndLogErrorMessage, showAndLogInformationMessage } from '../helpers';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as os from 'os';
|
||||
import { cancelVariantAnalysis } from './gh-api/gh-actions-api-client';
|
||||
|
||||
export class VariantAnalysisManager extends DisposableObject implements VariantAnalysisViewManager<VariantAnalysisView> {
|
||||
@@ -301,6 +302,27 @@ export class VariantAnalysisManager extends DisposableObject implements VariantA
|
||||
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 {
|
||||
return path.join(
|
||||
this.getVariantAnalysisStorageLocation(variantAnalysisId),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import { CancellationTokenSource, commands, extensions } from 'vscode';
|
||||
import { CancellationTokenSource, commands, env, extensions } from 'vscode';
|
||||
import { CodeQLExtensionInterface } from '../../../extension';
|
||||
import { logger } from '../../../logging';
|
||||
import * as config from '../../../config';
|
||||
@@ -16,7 +16,10 @@ import { storagePath } from '../global.helper';
|
||||
import { VariantAnalysisResultsManager } from '../../../remote-queries/variant-analysis-results-manager';
|
||||
import { createMockVariantAnalysis } from '../../factories/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 {
|
||||
VariantAnalysis,
|
||||
VariantAnalysisScannedRepository,
|
||||
@@ -142,7 +145,9 @@ describe('Variant Analysis Manager', async function() {
|
||||
});
|
||||
|
||||
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 () => {
|
||||
try {
|
||||
@@ -585,4 +590,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