Merge pull request #1715 from github/elena/cancel-variant-analysis
Implement query history "cancel" option
This commit is contained in:
@@ -40,7 +40,7 @@ import * as fs from 'fs-extra';
|
||||
import { CliVersionConstraint } from './cli';
|
||||
import { HistoryItemLabelProvider } from './history-item-label-provider';
|
||||
import { Credentials } from './authentication';
|
||||
import { cancelRemoteQuery } from './remote-queries/gh-api/gh-actions-api-client';
|
||||
import { cancelRemoteQuery, cancelVariantAnalysis } from './remote-queries/gh-api/gh-actions-api-client';
|
||||
import { RemoteQueriesManager } from './remote-queries/remote-queries-manager';
|
||||
import { RemoteQueryHistoryItem } from './remote-queries/remote-query-history-item';
|
||||
import { ResultsView } from './interface';
|
||||
@@ -1109,6 +1109,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
const selected = finalMultiSelect || [finalSingleItem];
|
||||
|
||||
const results = selected.map(async item => {
|
||||
if (item.status === QueryStatus.InProgress) {
|
||||
if (item.t === 'local') {
|
||||
@@ -1117,6 +1118,10 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
void showAndLogInformationMessage('Cancelling variant analysis. This may take a while.');
|
||||
const credentials = await this.getCredentials();
|
||||
await cancelRemoteQuery(credentials, item.remoteQuery);
|
||||
} else if (item.t === 'variant-analysis') {
|
||||
void showAndLogInformationMessage('Cancelling variant analysis. This may take a while.');
|
||||
const credentials = await this.getCredentials();
|
||||
await cancelVariantAnalysis(credentials, item.variantAnalysis);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { RemoteQuery } from '../remote-query';
|
||||
import { RemoteQueryFailureIndexItem, RemoteQueryResultIndex, RemoteQuerySuccessIndexItem } from '../remote-query-result-index';
|
||||
import { getErrorMessage } from '../../pure/helpers-pure';
|
||||
import { unzipFile } from '../../pure/zip';
|
||||
import { VariantAnalysis } from '../shared/variant-analysis';
|
||||
|
||||
export const RESULT_INDEX_ARTIFACT_NAME = 'result-index';
|
||||
|
||||
@@ -94,6 +95,18 @@ export async function cancelRemoteQuery(
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelVariantAnalysis(
|
||||
credentials: Credentials,
|
||||
variantAnalysis: VariantAnalysis
|
||||
): Promise<void> {
|
||||
const octokit = await credentials.getOctokit();
|
||||
const { actionsWorkflowRunId, controllerRepo: { fullName } } = variantAnalysis;
|
||||
const response = await octokit.request(`POST /repos/${fullName}/actions/runs/${actionsWorkflowRunId}/cancel`);
|
||||
if (response.status >= 300) {
|
||||
throw new Error(`Error cancelling variant analysis: ${response.status} ${response?.data?.message || ''}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadArtifactFromLink(
|
||||
credentials: Credentials,
|
||||
storagePath: string,
|
||||
|
||||
@@ -47,6 +47,7 @@ export function createMockLocalQueryInfo({
|
||||
const localQuery = new LocalQueryInfo(initialQueryInfo, cancellationToken);
|
||||
|
||||
localQuery.failureReason = failureReason;
|
||||
localQuery.cancel = () => { /**/ };
|
||||
|
||||
if (queryWithResults) {
|
||||
localQuery.completeThisQuery(queryWithResults);
|
||||
|
||||
@@ -21,7 +21,7 @@ export function createMockVariantAnalysis({
|
||||
skippedRepos?: VariantAnalysisSkippedRepositories,
|
||||
executionStartTime?: number | undefined
|
||||
}): VariantAnalysis {
|
||||
const variantAnalysis: VariantAnalysis = {
|
||||
return {
|
||||
id: faker.datatype.number(),
|
||||
controllerRepo: {
|
||||
...createMockRepository(),
|
||||
@@ -46,6 +46,4 @@ export function createMockVariantAnalysis({
|
||||
scannedRepos: scannedRepos,
|
||||
skippedRepos: skippedRepos
|
||||
};
|
||||
|
||||
return variantAnalysis;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ import { createMockVariantAnalysisHistoryItem } from '../factories/remote-querie
|
||||
import { VariantAnalysisHistoryItem } from '../../remote-queries/variant-analysis-history-item';
|
||||
import { QueryStatus } from '../../query-status';
|
||||
import { VariantAnalysisStatus } from '../../remote-queries/shared/variant-analysis';
|
||||
import * as ghActionsApiClient from '../../remote-queries/gh-api/gh-actions-api-client';
|
||||
import { Credentials } from '../../authentication';
|
||||
|
||||
describe('query-history', () => {
|
||||
const mockExtensionLocation = path.join(tmpDir.name, 'mock-extension-location');
|
||||
@@ -92,10 +94,18 @@ describe('query-history', () => {
|
||||
} as any as VariantAnalysisManager;
|
||||
|
||||
localQueryHistory = [
|
||||
// completed
|
||||
createMockLocalQueryInfo({ dbName: 'a', queryWithResults: createMockQueryWithResults({ sandbox, didRunSuccessfully: true }) }),
|
||||
// completed
|
||||
createMockLocalQueryInfo({ dbName: 'b', queryWithResults: createMockQueryWithResults({ sandbox, didRunSuccessfully: true }) }),
|
||||
// failed
|
||||
createMockLocalQueryInfo({ dbName: 'a', queryWithResults: createMockQueryWithResults({ sandbox, didRunSuccessfully: false }) }),
|
||||
// completed
|
||||
createMockLocalQueryInfo({ dbName: 'a', queryWithResults: createMockQueryWithResults({ sandbox, didRunSuccessfully: true }) }),
|
||||
// in progress
|
||||
createMockLocalQueryInfo({ resultCount: 0 }),
|
||||
// in progress
|
||||
createMockLocalQueryInfo({ resultCount: 0 })
|
||||
];
|
||||
remoteQueryHistory = [
|
||||
createMockRemoteQueryHistoryItem({ status: QueryStatus.Completed }),
|
||||
@@ -325,6 +335,168 @@ describe('query-history', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleCancel', () => {
|
||||
let mockCredentials: Credentials;
|
||||
let mockCancelRemoteQuery: sinon.SinonStub;
|
||||
let mockCancelVariantAnalysis: sinon.SinonStub;
|
||||
let getOctokitStub: sinon.SinonStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockCredentials = {
|
||||
getOctokit: () => Promise.resolve({
|
||||
request: getOctokitStub
|
||||
})
|
||||
} as unknown as Credentials;
|
||||
sandbox.stub(Credentials, 'initialize').resolves(mockCredentials);
|
||||
mockCancelRemoteQuery = sandbox.stub(ghActionsApiClient, 'cancelRemoteQuery');
|
||||
mockCancelVariantAnalysis = sandbox.stub(ghActionsApiClient, 'cancelVariantAnalysis');
|
||||
});
|
||||
|
||||
describe('if the item is in progress', async () => {
|
||||
it('should cancel a single local query', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(localQueryHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const inProgress1 = localQueryHistory[4];
|
||||
const cancelSpy = sandbox.spy(inProgress1, 'cancel');
|
||||
|
||||
await queryHistoryManager.handleCancel(inProgress1, [inProgress1]);
|
||||
expect(cancelSpy).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should cancel multiple local queries', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(localQueryHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const inProgress1 = localQueryHistory[4];
|
||||
const inProgress2 = localQueryHistory[5];
|
||||
|
||||
const cancelSpy1 = sandbox.spy(inProgress1, 'cancel');
|
||||
const cancelSpy2 = sandbox.spy(inProgress2, 'cancel');
|
||||
|
||||
await queryHistoryManager.handleCancel(inProgress1, [inProgress1, inProgress2]);
|
||||
expect(cancelSpy1).to.have.been.called;
|
||||
expect(cancelSpy2).to.have.been.called;
|
||||
});
|
||||
|
||||
it('should cancel a single remote query', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const inProgress1 = remoteQueryHistory[2];
|
||||
|
||||
await queryHistoryManager.handleCancel(inProgress1, [inProgress1]);
|
||||
expect(mockCancelRemoteQuery).to.have.been.calledWith(mockCredentials, inProgress1.remoteQuery);
|
||||
});
|
||||
|
||||
it('should cancel multiple remote queries', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const inProgress1 = remoteQueryHistory[2];
|
||||
const inProgress2 = remoteQueryHistory[3];
|
||||
|
||||
await queryHistoryManager.handleCancel(inProgress1, [inProgress1, inProgress2]);
|
||||
expect(mockCancelRemoteQuery).to.have.been.calledWith(mockCredentials, inProgress1.remoteQuery);
|
||||
expect(mockCancelRemoteQuery).to.have.been.calledWith(mockCredentials, inProgress2.remoteQuery);
|
||||
});
|
||||
|
||||
it('should cancel a single variant analysis', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const inProgress1 = variantAnalysisHistory[1];
|
||||
|
||||
await queryHistoryManager.handleCancel(inProgress1, [inProgress1]);
|
||||
expect(mockCancelVariantAnalysis).to.have.been.calledWith(mockCredentials, inProgress1.variantAnalysis);
|
||||
});
|
||||
|
||||
it('should cancel multiple variant analyses', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const inProgress1 = variantAnalysisHistory[1];
|
||||
const inProgress2 = variantAnalysisHistory[3];
|
||||
|
||||
await queryHistoryManager.handleCancel(inProgress1, [inProgress1, inProgress2]);
|
||||
expect(mockCancelVariantAnalysis).to.have.been.calledWith(mockCredentials, inProgress1.variantAnalysis);
|
||||
expect(mockCancelVariantAnalysis).to.have.been.calledWith(mockCredentials, inProgress2.variantAnalysis);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if the item is not in progress', async () => {
|
||||
it('should not cancel a single local query', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(localQueryHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const completed = localQueryHistory[0];
|
||||
const cancelSpy = sandbox.spy(completed, 'cancel');
|
||||
|
||||
await queryHistoryManager.handleCancel(completed, [completed]);
|
||||
expect(cancelSpy).to.not.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not cancel multiple local queries', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(localQueryHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const completed = localQueryHistory[0];
|
||||
const failed = localQueryHistory[2];
|
||||
|
||||
const cancelSpy = sandbox.spy(completed, 'cancel');
|
||||
const cancelSpy2 = sandbox.spy(failed, 'cancel');
|
||||
|
||||
await queryHistoryManager.handleCancel(completed, [completed, failed]);
|
||||
expect(cancelSpy).to.not.have.been.calledOnce;
|
||||
expect(cancelSpy2).to.not.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not cancel a single remote query', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const completed = remoteQueryHistory[0];
|
||||
|
||||
await queryHistoryManager.handleCancel(completed, [completed]);
|
||||
expect(mockCancelRemoteQuery).to.not.have.been.calledWith(mockCredentials, completed.remoteQuery);
|
||||
});
|
||||
|
||||
it('should not cancel multiple remote queries', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const completed = remoteQueryHistory[0];
|
||||
const failed = remoteQueryHistory[1];
|
||||
|
||||
await queryHistoryManager.handleCancel(completed, [completed, failed]);
|
||||
expect(mockCancelRemoteQuery).to.not.have.been.calledWith(mockCredentials, completed.remoteQuery);
|
||||
expect(mockCancelRemoteQuery).to.not.have.been.calledWith(mockCredentials, failed.remoteQuery);
|
||||
});
|
||||
|
||||
it('should not cancel a single variant analysis', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const completedVariantAnalysis = variantAnalysisHistory[0];
|
||||
|
||||
await queryHistoryManager.handleCancel(completedVariantAnalysis, [completedVariantAnalysis]);
|
||||
expect(mockCancelVariantAnalysis).to.not.have.been.calledWith(mockCredentials, completedVariantAnalysis.variantAnalysis);
|
||||
});
|
||||
|
||||
it('should not cancel multiple variant analyses', async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
|
||||
// cancelling the selected item
|
||||
const completedVariantAnalysis = variantAnalysisHistory[0];
|
||||
const failedVariantAnalysis = variantAnalysisHistory[2];
|
||||
|
||||
await queryHistoryManager.handleCancel(completedVariantAnalysis, [completedVariantAnalysis, failedVariantAnalysis]);
|
||||
expect(mockCancelVariantAnalysis).to.not.have.been.calledWith(mockCredentials, completedVariantAnalysis.variantAnalysis);
|
||||
expect(mockCancelVariantAnalysis).to.not.have.been.calledWith(mockCredentials, failedVariantAnalysis.variantAnalysis);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('determineSelection', () => {
|
||||
const singleItem = 'a';
|
||||
const multipleItems = ['b', 'c', 'd'];
|
||||
|
||||
@@ -2,8 +2,14 @@ import { fail } from 'assert';
|
||||
import { expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { Credentials } from '../../../../authentication';
|
||||
import { cancelRemoteQuery, getRepositoriesMetadata } from '../../../../remote-queries/gh-api/gh-actions-api-client';
|
||||
import {
|
||||
cancelRemoteQuery,
|
||||
cancelVariantAnalysis,
|
||||
getRepositoriesMetadata
|
||||
} from '../../../../remote-queries/gh-api/gh-actions-api-client';
|
||||
import { RemoteQuery } from '../../../../remote-queries/remote-query';
|
||||
import { createMockVariantAnalysis } from '../../../factories/remote-queries/shared/variant-analysis';
|
||||
import { VariantAnalysis } from '../../../../remote-queries/shared/variant-analysis';
|
||||
|
||||
describe('gh-actions-api-client mock responses', () => {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
@@ -50,6 +56,29 @@ describe('gh-actions-api-client mock responses', () => {
|
||||
} as unknown as RemoteQuery;
|
||||
}
|
||||
});
|
||||
|
||||
describe('cancelVariantAnalysis', () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
before(() => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
});
|
||||
|
||||
it('should cancel a variant analysis', async () => {
|
||||
mockResponse = sinon.stub().resolves({ status: 202 });
|
||||
await cancelVariantAnalysis(mockCredentials, variantAnalysis);
|
||||
|
||||
expect(mockResponse.calledOnce).to.be.true;
|
||||
expect(mockResponse.firstCall.args[0]).to.equal(`POST /repos/${variantAnalysis.controllerRepo.fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}/cancel`);
|
||||
});
|
||||
|
||||
it('should fail to cancel a variant analysis', async () => {
|
||||
mockResponse = sinon.stub().resolves({ status: 409, data: { message: 'Uh oh!' } });
|
||||
|
||||
await expect(cancelVariantAnalysis(mockCredentials, variantAnalysis)).to.be.rejectedWith(/Error cancelling variant analysis: 409 Uh oh!/);
|
||||
expect(mockResponse.calledOnce).to.be.true;
|
||||
expect(mockResponse.firstCall.args[0]).to.equal(`POST /repos/${variantAnalysis.controllerRepo.fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}/cancel`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('gh-actions-api-client real responses', function() {
|
||||
|
||||
Reference in New Issue
Block a user