Merge pull request #1753 from github/koesie10/copy-repo-list

Add copying of repository list for variant analyses
This commit is contained in:
Koen Vlaswinkel
2022-11-14 09:48:43 +01:00
committed by GitHub
4 changed files with 160 additions and 6 deletions

View File

@@ -944,6 +944,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,

View File

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

View File

@@ -1,7 +1,7 @@
import * as path from 'path';
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 { Credentials } from '../authentication';
import { VariantAnalysisMonitor } from './variant-analysis-monitor';
@@ -28,6 +28,7 @@ import {
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';
import { ProgressCallback, UserCancellationException } from '../commandRunner';
import { CodeQLCliServer } from '../cli';
@@ -367,6 +368,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),

View File

@@ -1,6 +1,6 @@
import * as sinon from 'sinon';
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 { 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,
@@ -252,7 +255,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 {
@@ -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,
],
});
});
});
});
});