mirror of
https://github.com/usebruno/bruno.git
synced 2024-12-23 07:09:01 +01:00
Merge branch 'main' into feature/auto-scroll-on-collection-run
This commit is contained in:
commit
b9d50fbba0
24608
package-lock.json
generated
24608
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@ import React, { useRef, forwardRef, useState } from 'react';
|
||||
import find from 'lodash/find';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { updateEnvironmentSettingsModalVisibility } from 'providers/ReduxStore/slices/app';
|
||||
import { IconSettings, IconCaretDown, IconDatabase, IconDatabaseOff } from '@tabler/icons';
|
||||
import EnvironmentSettings from '../EnvironmentSettings';
|
||||
import toast from 'react-hot-toast';
|
||||
@ -24,6 +25,16 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
);
|
||||
});
|
||||
|
||||
const handleSettingsIconClick = () => {
|
||||
setOpenSettingsModal(true);
|
||||
dispatch(updateEnvironmentSettingsModalVisibility(true));
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setOpenSettingsModal(false);
|
||||
dispatch(updateEnvironmentSettingsModalVisibility(false));
|
||||
};
|
||||
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const onSelect = (environment) => {
|
||||
@ -66,7 +77,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
||||
<span className="ml-2">No Environment</span>
|
||||
</div>
|
||||
<div className="dropdown-item border-top" onClick={() => setOpenSettingsModal(true)}>
|
||||
<div className="dropdown-item border-top" onClick={handleSettingsIconClick}>
|
||||
<div className="pr-2 text-gray-600">
|
||||
<IconSettings size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
@ -74,7 +85,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{openSettingsModal && <EnvironmentSettings collection={collection} onClose={() => setOpenSettingsModal(false)} />}
|
||||
{openSettingsModal && <EnvironmentSettings collection={collection} onClose={handleModalClose} />}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import React, { useState, useRef, 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';
|
||||
@ -33,6 +33,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);
|
||||
@ -87,6 +88,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';
|
||||
@ -120,9 +125,16 @@ export default function RunnerResults({ collection }) {
|
||||
|
||||
return (
|
||||
<StyledWrapper ref={listWrapperRef} 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">
|
||||
@ -201,7 +213,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}>
|
||||
|
@ -18,6 +18,7 @@ export const HotkeysProvider = (props) => {
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const collections = useSelector((state) => state.collections.collections);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
const isEnvironmentSettingsModalOpen = useSelector((state) => state.app.isEnvironmentSettingsModalOpen);
|
||||
const [showSaveRequestModal, setShowSaveRequestModal] = useState(false);
|
||||
const [showEnvSettingsModal, setShowEnvSettingsModal] = useState(false);
|
||||
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
||||
@ -43,16 +44,20 @@ export const HotkeysProvider = (props) => {
|
||||
// save hotkey
|
||||
useEffect(() => {
|
||||
Mousetrap.bind(['command+s', 'ctrl+s'], (e) => {
|
||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
if (activeTab) {
|
||||
const collection = findCollectionByUid(collections, activeTab.collectionUid);
|
||||
if (collection) {
|
||||
const item = findItemInCollection(collection, activeTab.uid);
|
||||
if (item && item.uid) {
|
||||
dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
|
||||
} else {
|
||||
// todo: when ephermal requests go live
|
||||
// setShowSaveRequestModal(true);
|
||||
if (isEnvironmentSettingsModalOpen) {
|
||||
console.log('todo: save environment settings');
|
||||
} else {
|
||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
if (activeTab) {
|
||||
const collection = findCollectionByUid(collections, activeTab.collectionUid);
|
||||
if (collection) {
|
||||
const item = findItemInCollection(collection, activeTab.uid);
|
||||
if (item && item.uid) {
|
||||
dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
|
||||
} else {
|
||||
// todo: when ephermal requests go live
|
||||
// setShowSaveRequestModal(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,7 +68,7 @@ export const HotkeysProvider = (props) => {
|
||||
return () => {
|
||||
Mousetrap.unbind(['command+s', 'ctrl+s']);
|
||||
};
|
||||
}, [activeTabUid, tabs, saveRequest, collections]);
|
||||
}, [activeTabUid, tabs, saveRequest, collections, isEnvironmentSettingsModalOpen]);
|
||||
|
||||
// send request (ctrl/cmd + enter)
|
||||
useEffect(() => {
|
||||
|
@ -9,6 +9,7 @@ const initialState = {
|
||||
screenWidth: 500,
|
||||
showHomePage: false,
|
||||
showPreferences: false,
|
||||
isEnvironmentSettingsModalOpen: false,
|
||||
preferences: {
|
||||
request: {
|
||||
sslVerification: true,
|
||||
@ -42,6 +43,9 @@ export const appSlice = createSlice({
|
||||
updateIsDragging: (state, action) => {
|
||||
state.isDragging = action.payload.isDragging;
|
||||
},
|
||||
updateEnvironmentSettingsModalVisibility: (state, action) => {
|
||||
state.isEnvironmentSettingsModalOpen = action.payload;
|
||||
},
|
||||
showHomePage: (state) => {
|
||||
state.showHomePage = true;
|
||||
},
|
||||
@ -74,6 +78,7 @@ export const {
|
||||
refreshScreenWidth,
|
||||
updateLeftSidebarWidth,
|
||||
updateIsDragging,
|
||||
updateEnvironmentSettingsModalVisibility,
|
||||
showHomePage,
|
||||
hideHomePage,
|
||||
showPreferences,
|
||||
|
@ -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);
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,16 @@ const template = [
|
||||
ipcMain.emit('main:open-collection');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Open Recent',
|
||||
role: 'recentdocuments',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Clear Recent',
|
||||
role: 'clearrecentdocuments'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Preferences',
|
||||
accelerator: 'CommandOrControl+,',
|
||||
|
@ -5,6 +5,7 @@ const { BrowserWindow, app, Menu, ipcMain } = require('electron');
|
||||
const { setContentSecurityPolicy } = require('electron-util');
|
||||
|
||||
const menuTemplate = require('./app/menu-template');
|
||||
const { openCollection } = require('./app/collections');
|
||||
const LastOpenedCollections = require('./store/last-opened-collections');
|
||||
const registerNetworkIpc = require('./ipc/network');
|
||||
const registerCollectionsIpc = require('./ipc/collection');
|
||||
@ -28,13 +29,13 @@ const contentSecurityPolicy = [
|
||||
setContentSecurityPolicy(contentSecurityPolicy.join(';') + ';');
|
||||
|
||||
const menu = Menu.buildFromTemplate(menuTemplate);
|
||||
Menu.setApplicationMenu(menu);
|
||||
|
||||
let mainWindow;
|
||||
let watcher;
|
||||
|
||||
// Prepare the renderer once the app is ready
|
||||
app.on('ready', async () => {
|
||||
Menu.setApplicationMenu(menu);
|
||||
const { maximized, x, y, width, height } = loadWindowState();
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
@ -122,3 +123,8 @@ app.on('ready', async () => {
|
||||
|
||||
// Quit the app once all windows are closed
|
||||
app.on('window-all-closed', app.quit);
|
||||
|
||||
// Open collection from Recent menu (#1521)
|
||||
app.on('open-file', (event, path) => {
|
||||
openCollection(mainWindow, watcher, path);
|
||||
});
|
||||
|
@ -617,6 +617,7 @@ const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) =
|
||||
ipcMain.on('main:collection-opened', (win, pathname, uid, brunoConfig) => {
|
||||
watcher.addWatcher(win, pathname, uid, brunoConfig);
|
||||
lastOpenedCollections.add(pathname);
|
||||
app.addRecentDocument(pathname);
|
||||
});
|
||||
|
||||
// The app listen for this event and allows the user to save unsaved requests before closing the app
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
const cancelTokens = {};
|
||||
|
||||
const saveCancelToken = (uid, axiosRequest) => {
|
||||
cancelTokens[uid] = axiosRequest;
|
||||
const saveCancelToken = (uid, abortController) => {
|
||||
cancelTokens[uid] = abortController;
|
||||
};
|
||||
|
||||
const deleteCancelToken = (uid) => {
|
||||
|
Loading…
Reference in New Issue
Block a user