Skip to content

Commit 6168145

Browse files
authored
Merge pull request #1753 from github/koesie10/copy-repo-list
Add copying of repository list for variant analyses
2 parents a053137 + baae8d5 commit 6168145

File tree

4 files changed

+160
-6
lines changed

4 files changed

+160
-6
lines changed

extensions/ql-vscode/src/extension.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,12 @@ async function activateWithInstalledDistribution(
944944
})
945945
);
946946

947+
ctx.subscriptions.push(
948+
commandRunner('codeQL.copyVariantAnalysisRepoList', async (variantAnalysisId: number) => {
949+
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysisId);
950+
})
951+
);
952+
947953
ctx.subscriptions.push(
948954
commandRunner('codeQL.monitorVariantAnalysis', async (
949955
variantAnalysis: VariantAnalysis,

extensions/ql-vscode/src/query-history.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,11 +1256,15 @@ export class QueryHistoryManager extends DisposableObject {
12561256
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
12571257

12581258
// Remote queries only
1259-
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem || finalSingleItem.t !== 'remote') {
1259+
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
12601260
return;
12611261
}
12621262

1263-
await commands.executeCommand('codeQL.copyRepoList', finalSingleItem.queryId);
1263+
if (finalSingleItem.t === 'remote') {
1264+
await commands.executeCommand('codeQL.copyRepoList', finalSingleItem.queryId);
1265+
} else if (finalSingleItem.t === 'variant-analysis') {
1266+
await commands.executeCommand('codeQL.copyVariantAnalysisRepoList', finalSingleItem.variantAnalysis.id);
1267+
}
12641268
}
12651269

12661270
async handleExportResults(): Promise<void> {

extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as path from 'path';
22

33
import * as ghApiClient from './gh-api/gh-api-client';
4-
import { CancellationToken, commands, EventEmitter, ExtensionContext, Uri, window } from 'vscode';
4+
import { CancellationToken, commands, env, EventEmitter, ExtensionContext, Uri, window } from 'vscode';
55
import { DisposableObject } from '../pure/disposable-object';
66
import { Credentials } from '../authentication';
77
import { VariantAnalysisMonitor } from './variant-analysis-monitor';
@@ -28,6 +28,7 @@ import {
2828
import PQueue from 'p-queue';
2929
import { createTimestampFile, showAndLogErrorMessage, showAndLogInformationMessage } from '../helpers';
3030
import * as fs from 'fs-extra';
31+
import * as os from 'os';
3132
import { cancelVariantAnalysis } from './gh-api/gh-actions-api-client';
3233
import { ProgressCallback, UserCancellationException } from '../commandRunner';
3334
import { CodeQLCliServer } from '../cli';
@@ -367,6 +368,27 @@ export class VariantAnalysisManager extends DisposableObject implements VariantA
367368
await cancelVariantAnalysis(credentials, variantAnalysis);
368369
}
369370

371+
public async copyRepoListToClipboard(variantAnalysisId: number) {
372+
const variantAnalysis = this.variantAnalyses.get(variantAnalysisId);
373+
if (!variantAnalysis) {
374+
throw new Error(`No variant analysis with id: ${variantAnalysisId}`);
375+
}
376+
377+
const fullNames = variantAnalysis.scannedRepos?.filter(a => a.resultCount && a.resultCount > 0).map(a => a.repository.fullName);
378+
if (!fullNames || fullNames.length === 0) {
379+
return;
380+
}
381+
382+
const text = [
383+
'"new-repo-list": [',
384+
...fullNames.slice(0, -1).map(repo => ` "${repo}",`),
385+
` "${fullNames[fullNames.length - 1]}"`,
386+
']'
387+
];
388+
389+
await env.clipboard.writeText(text.join(os.EOL));
390+
}
391+
370392
private getRepoStatesStoragePath(variantAnalysisId: number): string {
371393
return path.join(
372394
this.getVariantAnalysisStorageLocation(variantAnalysisId),

extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as sinon from 'sinon';
22
import { assert, expect } from 'chai';
3-
import { CancellationTokenSource, commands, extensions, QuickPickItem, Uri, window } from 'vscode';
3+
import { CancellationTokenSource, commands, env, extensions, QuickPickItem, Uri, window } from 'vscode';
44
import { CodeQLExtensionInterface } from '../../../extension';
55
import { logger } from '../../../logging';
66
import * as config from '../../../config';
@@ -16,7 +16,10 @@ import { storagePath } from '../global.helper';
1616
import { VariantAnalysisResultsManager } from '../../../remote-queries/variant-analysis-results-manager';
1717
import { createMockVariantAnalysis } from '../../factories/remote-queries/shared/variant-analysis';
1818
import * as VariantAnalysisModule from '../../../remote-queries/shared/variant-analysis';
19-
import { createMockScannedRepos } from '../../factories/remote-queries/shared/scanned-repositories';
19+
import {
20+
createMockScannedRepo,
21+
createMockScannedRepos
22+
} from '../../factories/remote-queries/shared/scanned-repositories';
2023
import {
2124
VariantAnalysis,
2225
VariantAnalysisScannedRepository,
@@ -252,7 +255,9 @@ describe('Variant Analysis Manager', async function() {
252255
});
253256

254257
describe('when credentials are invalid', async () => {
255-
beforeEach(async () => { sandbox.stub(Credentials, 'initialize').resolves(undefined); });
258+
beforeEach(async () => {
259+
sandbox.stub(Credentials, 'initialize').resolves(undefined);
260+
});
256261

257262
it('should return early if credentials are wrong', async () => {
258263
try {
@@ -695,4 +700,121 @@ describe('Variant Analysis Manager', async function() {
695700
});
696701
});
697702
});
703+
704+
describe('copyRepoListToClipboard', async () => {
705+
let variantAnalysis: VariantAnalysis;
706+
let variantAnalysisStorageLocation: string;
707+
708+
let writeTextStub: sinon.SinonStub;
709+
710+
beforeEach(async () => {
711+
variantAnalysis = createMockVariantAnalysis({});
712+
713+
variantAnalysisStorageLocation = variantAnalysisManager.getVariantAnalysisStorageLocation(variantAnalysis.id);
714+
await createTimestampFile(variantAnalysisStorageLocation);
715+
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
716+
717+
writeTextStub = sinon.stub();
718+
sinon.stub(env, 'clipboard').value({
719+
writeText: writeTextStub,
720+
});
721+
});
722+
723+
afterEach(() => {
724+
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
725+
});
726+
727+
describe('when the variant analysis does not have any repositories', () => {
728+
beforeEach(async () => {
729+
await variantAnalysisManager.rehydrateVariantAnalysis({
730+
...variantAnalysis,
731+
scannedRepos: [],
732+
});
733+
});
734+
735+
it('should not copy any text', async () => {
736+
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysis.id);
737+
738+
expect(writeTextStub).not.to.have.been.called;
739+
});
740+
});
741+
742+
describe('when the variant analysis does not have any repositories with results', () => {
743+
beforeEach(async () => {
744+
await variantAnalysisManager.rehydrateVariantAnalysis({
745+
...variantAnalysis,
746+
scannedRepos: [
747+
{
748+
...createMockScannedRepo(),
749+
resultCount: 0,
750+
},
751+
{
752+
...createMockScannedRepo(),
753+
resultCount: undefined,
754+
}
755+
],
756+
});
757+
});
758+
759+
it('should not copy any text', async () => {
760+
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysis.id);
761+
762+
expect(writeTextStub).not.to.have.been.called;
763+
});
764+
});
765+
766+
describe('when the variant analysis has repositories with results', () => {
767+
const scannedRepos = [
768+
{
769+
...createMockScannedRepo(),
770+
resultCount: 100,
771+
},
772+
{
773+
...createMockScannedRepo(),
774+
resultCount: 0,
775+
},
776+
{
777+
...createMockScannedRepo(),
778+
resultCount: 200,
779+
},
780+
{
781+
...createMockScannedRepo(),
782+
resultCount: undefined,
783+
},
784+
{
785+
...createMockScannedRepo(),
786+
resultCount: 5,
787+
},
788+
];
789+
790+
beforeEach(async () => {
791+
await variantAnalysisManager.rehydrateVariantAnalysis({
792+
...variantAnalysis,
793+
scannedRepos,
794+
});
795+
});
796+
797+
it('should copy text', async () => {
798+
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysis.id);
799+
800+
expect(writeTextStub).to.have.been.calledOnce;
801+
});
802+
803+
it('should be valid JSON when put in object', async () => {
804+
await variantAnalysisManager.copyRepoListToClipboard(variantAnalysis.id);
805+
806+
const text = writeTextStub.getCalls()[0].lastArg;
807+
808+
const parsed = JSON.parse('{' + text + '}');
809+
810+
expect(parsed).to.deep.eq({
811+
'new-repo-list': [
812+
scannedRepos[0].repository.fullName,
813+
scannedRepos[2].repository.fullName,
814+
scannedRepos[4].repository.fullName,
815+
],
816+
});
817+
});
818+
});
819+
});
698820
});

0 commit comments

Comments
 (0)