[Feature] Stop button for runner execution (#1580)

* First attempts
* Sending cancel token in run-folder-event
* Remove logs, update lock
* cancelTokenUid with default value
* Indentation
* Generating token in the main process side
* Removing uuid import
This commit is contained in:
Ricardo Silverio 2024-02-13 18:46:41 -03:00 committed by GitHub
parent eab50f01d7
commit 942a895ae0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 7538 additions and 17135 deletions

24608
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import path from 'path';
import { useDispatch } from 'react-redux';
import { get, cloneDeep } from 'lodash';
import { runCollectionFolder } from 'providers/ReduxStore/slices/collections/actions';
import { runCollectionFolder, cancelRunnerExecution } from 'providers/ReduxStore/slices/collections/actions';
import { resetCollectionRunner } from 'providers/ReduxStore/slices/collections';
import { findItemInCollection, getTotalRequestCountInCollection } from 'utils/collections';
import { IconRefresh, IconCircleCheck, IconCircleX, IconCheck, IconX, IconRun } from '@tabler/icons';
@ -32,6 +32,7 @@ export default function RunnerResults({ collection }) {
const collectionCopy = cloneDeep(collection);
const runnerInfo = get(collection, 'runnerResult.info', {});
const items = cloneDeep(get(collection, 'runnerResult.items', []))
.map((item) => {
const info = findItemInCollection(collectionCopy, item.uid);
@ -81,6 +82,10 @@ export default function RunnerResults({ collection }) {
);
};
const cancelExecution = () => {
dispatch(cancelRunnerExecution(runnerInfo.cancelTokenUid));
};
const totalRequestsInCollection = getTotalRequestCountInCollection(collectionCopy);
const passedRequests = items.filter((item) => {
return item.status !== 'error' && item.testStatus === 'pass' && item.assertionStatus === 'pass';
@ -114,9 +119,16 @@ export default function RunnerResults({ collection }) {
return (
<StyledWrapper className="px-4 pb-4 flex flex-grow flex-col relative">
<div className="font-medium mt-6 mb-4 title flex items-center">
Runner
<IconRun size={20} strokeWidth={1.5} className="ml-2" />
<div className="flex flex-row mt-6 mb-4">
<div className="font-medium title flex items-center">
Runner
<IconRun size={20} strokeWidth={1.5} className="ml-2" />
</div>
{runnerInfo.status !== 'ended' && runnerInfo.cancelTokenUid && (
<button className="btn ml-6 btn-sm btn-danger" onClick={cancelExecution}>
Cancel Execution
</button>
)}
</div>
<div className="flex flex-1">
<div className="flex flex-col flex-1">
@ -195,7 +207,6 @@ export default function RunnerResults({ collection }) {
</div>
);
})}
{runnerInfo.status === 'ended' ? (
<div className="mt-2 mb-4">
<button type="submit" className="submit btn btn-sm btn-secondary mt-6" onClick={runAgain}>

View File

@ -208,6 +208,10 @@ export const cancelRequest = (cancelTokenUid, item, collection) => (dispatch) =>
.catch((err) => console.log(err));
};
export const cancelRunnerExecution = (cancelTokenUid) => (dispatch) => {
cancelNetworkRequest(cancelTokenUid).catch((err) => console.log(err));
};
export const runCollectionFolder = (collectionUid, folderUid, recursive) => (dispatch, getState) => {
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);

View File

@ -1290,7 +1290,7 @@ export const collectionsSlice = createSlice({
}
},
runFolderEvent: (state, action) => {
const { collectionUid, folderUid, itemUid, type, isRecursive, error } = action.payload;
const { collectionUid, folderUid, itemUid, type, isRecursive, error, cancelTokenUid } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
if (collection) {
@ -1306,6 +1306,7 @@ export const collectionsSlice = createSlice({
info.collectionUid = collectionUid;
info.folderUid = folderUid;
info.isRecursive = isRecursive;
info.cancelTokenUid = cancelTokenUid;
info.status = 'started';
}

View File

@ -400,9 +400,9 @@ const registerNetworkIpc = (mainWindow) => {
const scriptingConfig = get(brunoConfig, 'scripts', {});
try {
const cancelToken = axios.CancelToken.source();
request.cancelToken = cancelToken.token;
saveCancelToken(cancelTokenUid, cancelToken);
const controller = new AbortController();
request.signal = controller.signal;
saveCancelToken(cancelTokenUid, controller);
await runPreRequest(
request,
@ -586,7 +586,7 @@ const registerNetworkIpc = (mainWindow) => {
ipcMain.handle('cancel-http-request', async (event, cancelTokenUid) => {
return new Promise((resolve, reject) => {
if (cancelTokenUid && cancelTokens[cancelTokenUid]) {
cancelTokens[cancelTokenUid].cancel();
cancelTokens[cancelTokenUid].abort();
deleteCancelToken(cancelTokenUid);
resolve();
} else {
@ -679,10 +679,14 @@ const registerNetworkIpc = (mainWindow) => {
const collectionUid = collection.uid;
const collectionPath = collection.pathname;
const folderUid = folder ? folder.uid : null;
const cancelTokenUid = uuid();
const brunoConfig = getBrunoConfig(collectionUid);
const scriptingConfig = get(brunoConfig, 'scripts', {});
const collectionRoot = get(collection, 'root', {});
const abortController = new AbortController();
saveCancelToken(cancelTokenUid, abortController);
if (!folder) {
folder = collection;
}
@ -691,7 +695,8 @@ const registerNetworkIpc = (mainWindow) => {
type: 'testrun-started',
isRecursive: recursive,
collectionUid,
folderUid
folderUid,
cancelTokenUid
});
try {
@ -717,6 +722,13 @@ const registerNetworkIpc = (mainWindow) => {
let currentRequestIndex = 0;
let nJumps = 0; // count the number of jumps to avoid infinite loops
while (currentRequestIndex < folderRequests.length) {
// user requested to cancel runner
if (abortController.signal.aborted) {
let error = new Error('Runner execution cancelled');
error.isCancel = true;
throw error;
}
const item = folderRequests[currentRequestIndex];
let nextRequestName;
const itemUid = item.uid;
@ -770,6 +782,7 @@ const registerNetworkIpc = (mainWindow) => {
...eventData
});
request.signal = abortController.signal;
const axiosInstance = await configureRequest(
collectionUid,
request,
@ -803,7 +816,7 @@ const registerNetworkIpc = (mainWindow) => {
...eventData
});
} catch (error) {
if (error?.response) {
if (error?.response && !axios.isCancel(error)) {
const { data, dataBuffer } = parseDataFromResponse(error.response);
error.response.data = data;
@ -928,15 +941,19 @@ const registerNetworkIpc = (mainWindow) => {
}
}
deleteCancelToken(cancelTokenUid);
mainWindow.webContents.send('main:run-folder-event', {
type: 'testrun-ended',
collectionUid,
folderUid
});
} catch (error) {
deleteCancelToken(cancelTokenUid);
mainWindow.webContents.send('main:run-folder-event', {
type: 'error',
error
type: 'testrun-ended',
collectionUid,
folderUid,
error: error && !error.isCancel ? error : null
});
}
}

View File

@ -1,7 +1,7 @@
const cancelTokens = {};
const saveCancelToken = (uid, axiosRequest) => {
cancelTokens[uid] = axiosRequest;
const saveCancelToken = (uid, abortController) => {
cancelTokens[uid] = abortController;
};
const deleteCancelToken = (uid) => {