Skip to content

improve: error handling for scripts #4082

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ const ContentIndicator = () => {
);
};

const ErrorIndicator = () => {
return (
<sup className="ml-[.125rem] opacity-80 font-medium text-red-500">
<DotIcon width="10" ></DotIcon>
</sup>
);
};

const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
const dispatch = useDispatch();
const tabs = useSelector((state) => state.tabs.tabs);
Expand Down Expand Up @@ -136,7 +144,11 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
</div>
<div className={getTabClassname('script')} role="tab" onClick={() => selectTab('script')}>
Script
{(script.req || script.res) && <ContentIndicator />}
{(script.req || script.res) && (
item.preScriptResponseErrorMessage || item.postResponseScriptErrorMessage ?
<ErrorIndicator /> :
<ContentIndicator />
)}
</div>
<div className={getTabClassname('assert')} role="tab" onClick={() => selectTab('assert')}>
Assert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ import classnames from 'classnames';
import { getContentType, safeStringifyJSON, safeParseXML } from 'utils/common';
import { getCodeMirrorModeBasedOnContentType } from 'utils/common/codemirror';
import QueryResultPreview from './QueryResultPreview';

import StyledWrapper from './StyledWrapper';
import { useState } from 'react';
import { useMemo } from 'react';
import { useEffect } from 'react';
import { useState, useMemo, useEffect } from 'react';
import { useTheme } from 'providers/Theme/index';
import { uuid } from 'utils/common/index';

Expand Down Expand Up @@ -62,6 +59,19 @@ const formatResponse = (data, mode, filter) => {
return safeStringifyJSON(data, true);
};

const formatErrorMessage = (error) => {
if (!error) return 'Something went wrong';

const remoteMethodError = "Error invoking remote method 'send-http-request':";

if (error.includes(remoteMethodError)) {
const parts = error.split(remoteMethodError);
return parts[1]?.trim() || error;
}

return error;
};

const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEventListener, headers, error }) => {
const contentType = getContentType(headers);
const mode = getCodeMirrorModeBasedOnContentType(contentType, data);
Expand Down Expand Up @@ -121,6 +131,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
}, [allowedPreviewModes, previewTab]);

const queryFilterEnabled = useMemo(() => mode.includes('json'), [mode]);
const hasScriptError = item.preRequestScriptErrorMessage || item.postResponseScriptErrorMessage;

return (
<StyledWrapper
Expand All @@ -133,7 +144,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
</div>
{error ? (
<div>
<div className="text-red-500">{error}</div>
{hasScriptError ? null : <div className="text-red-500">{formatErrorMessage(error)}</div>}

{error && typeof error === 'string' && error.toLowerCase().includes('self signed certificate') ? (
<div className="mt-6 muted text-xs">
Expand All @@ -143,24 +154,26 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
) : null}
</div>
) : (
<>
<QueryResultPreview
previewTab={previewTab}
data={data}
dataBuffer={dataBuffer}
formattedData={formattedData}
item={item}
contentType={contentType}
mode={mode}
collection={collection}
allowedPreviewModes={allowedPreviewModes}
disableRunEventListener={disableRunEventListener}
displayedTheme={displayedTheme}
/>
{queryFilterEnabled && (
<QueryResultFilter filter={filter} onChange={debouncedResultFilterOnChange} mode={mode} />
)}
</>
<div className="h-full flex flex-col">
<div className="flex-1 relative">
<QueryResultPreview
previewTab={previewTab}
data={data}
dataBuffer={dataBuffer}
formattedData={formattedData}
item={item}
contentType={contentType}
mode={mode}
collection={collection}
allowedPreviewModes={allowedPreviewModes}
disableRunEventListener={disableRunEventListener}
displayedTheme={displayedTheme}
/>
{queryFilterEnabled && (
<QueryResultFilter filter={filter} onChange={debouncedResultFilterOnChange} mode={mode} />
)}
</div>
</div>
)}
</StyledWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import styled from 'styled-components';

const StyledWrapper = styled.div`
border-left: 4px solid ${(props) => props.theme.colors.text.danger};
border-top: 1px solid transparent;
border-right: 1px solid transparent;
border-bottom: 1px solid transparent;
border-radius: 0.375rem;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
max-height: 200px;
min-height: 70px;
overflow-y: auto;
background-color: ${(props) => props.theme.bg === '#1e1e1e' ? 'rgba(40, 40, 40, 0.5)' : 'rgba(250, 250, 250, 0.9)'};

.error-icon-container {
margin-top: 0.125rem;
padding: 0.375rem;
border-radius: 9999px;
background-color: ${(props) => props.theme.bg === '#1e1e1e' ? 'rgba(40, 40, 40, 0.8)' : 'rgba(240, 240, 240, 0.8)'};

svg {
color: ${(props) => props.theme.colors.text.danger};
}
}

.close-button {
opacity: 0.7;
transition: opacity 0.2s;

&:hover {
opacity: 1;
}

svg {
color: ${(props) => props.theme.text};
}
}

.error-title {
font-weight: 600;
margin-bottom: 0.375rem;
color: ${(props) => props.theme.colors.text.danger};
}

.error-message {
font-family: monospace;
font-size: 0.6875rem;
line-height: 1.25rem;
white-space: pre-wrap;
word-break: break-all;
color: ${(props) => props.theme.text};
}
`;

export default StyledWrapper;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import { IconX } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';


const ScriptError = ({ item, onClose }) => {
const preRequestError = item?.preRequestScriptErrorMessage;
const postResponseError = item?.postResponseScriptErrorMessage;

if (!preRequestError && !postResponseError) return null;

const errorMessage = preRequestError || postResponseError;
const errorTitle = preRequestError ? 'Pre-Request Script Error' : 'Post-Response Script Error';

return (
<StyledWrapper className="mt-4 mb-2">
<div className="flex items-start gap-3 px-4 py-3">
<div className="flex-1 min-w-0">
<div className="error-title">
{errorTitle}
</div>
<div className="error-message">
{errorMessage}
</div>
</div>
<div
className="close-button flex-shrink-0 cursor-pointer"
onClick={onClose}
>
<IconX size={16} strokeWidth={1.5} />
</div>
</div>
</StyledWrapper>
);
};

export default ScriptError;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { IconAlertCircle } from '@tabler/icons';
import ToolHint from 'components/ToolHint';

const ScriptErrorIcon = ({ itemUid, onClick }) => {
const toolhintId = `script-error-icon-${itemUid}`;

return (
<>
<div
id={toolhintId}
className="cursor-pointer ml-2"
onClick={onClick}
>
<div className="flex items-center text-red-400">
<IconAlertCircle size={16} strokeWidth={1.5} className="stroke-current" />
</div>
</div>
<ToolHint
toolhintId={toolhintId}
text="Script execution error occurred"
place="bottom"
/>
</>
);
};

export default ScriptErrorIcon;
27 changes: 25 additions & 2 deletions packages/bruno-app/src/components/ResponsePane/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import find from 'lodash/find';
import classnames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
Expand All @@ -13,6 +13,8 @@ import ResponseSize from './ResponseSize';
import Timeline from './Timeline';
import TestResults from './TestResults';
import TestResultsLabel from './TestResultsLabel';
import ScriptError from './ScriptError';
import ScriptErrorIcon from './ScriptErrorIcon';
import StyledWrapper from './StyledWrapper';
import ResponseSave from 'src/components/ResponsePane/ResponseSave';
import ResponseClear from 'src/components/ResponsePane/ResponseClear';
Expand All @@ -22,6 +24,13 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const isLoading = ['queued', 'sending'].includes(item.requestState);
const [showScriptErrorCard, setShowScriptErrorCard] = useState(false);

useEffect(() => {
if (item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage) {
setShowScriptErrorCard(true);
}
}, [item?.preRequestScriptErrorMessage, item?.postResponseScriptErrorMessage]);

const selectTab = (tab) => {
dispatch(
Expand Down Expand Up @@ -98,6 +107,8 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
};

const responseHeadersCount = typeof response.headers === 'object' ? Object.entries(response.headers).length : 0;

const hasScriptError = item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage;

return (
<StyledWrapper className="flex flex-col h-full relative">
Expand All @@ -117,6 +128,12 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
</div>
{!isLoading ? (
<div className="flex flex-grow justify-end items-center">
{hasScriptError && !showScriptErrorCard && (
<ScriptErrorIcon
itemUid={item.uid}
onClick={() => setShowScriptErrorCard(true)}
/>
)}
<ResponseClear item={item} collection={collection} />
<ResponseSave item={item} />
<StatusCode status={response.status} />
Expand All @@ -126,9 +143,15 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
) : null}
</div>
<section
className={`flex flex-grow relative pl-3 pr-4 ${focusedTab.responsePaneTab === 'response' ? '' : 'mt-4'}`}
className={`flex flex-col flex-grow relative pl-3 pr-4 ${focusedTab.responsePaneTab === 'response' ? '' : 'mt-4'}`}
>
{isLoading ? <Overlay item={item} collection={collection} /> : null}
{hasScriptError && showScriptErrorCard && (
<ScriptError
item={item}
onClose={() => setShowScriptErrorCard(false)}
/>
)}
{getTabPanel(focusedTab.responsePaneTab)}
</section>
</StyledWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1853,12 +1853,22 @@ export const collectionsSlice = createSlice({
}
},
runRequestEvent: (state, action) => {
const { itemUid, collectionUid, type, requestUid } = action.payload;
const { itemUid, collectionUid, type, requestUid, hasError } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);

if (collection) {
const item = findItemInCollection(collection, itemUid);
if (item) {
if (type === 'pre-request-script-execution') {
item.requestUid = requestUid;
item.preRequestScriptErrorMessage = action.payload.errorMessage;
}

if(type === 'post-response-script-execution') {
item.requestUid = requestUid;
item.postResponseScriptErrorMessage = action.payload.errorMessage;
}

if (type === 'request-queued') {
const { cancelTokenUid } = action.payload;
item.requestUid = requestUid;
Expand Down
21 changes: 21 additions & 0 deletions packages/bruno-app/src/utils/codemirror/javascript-lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ if (!SERVER_RENDERED) {
}
return [];
}

// Set default options for Bruno
const defaultOptions = {
esversion: 11,
expr: true,
asi: true,
undef: true,
browser: true,
devel: true,
predef: {
'bru': false,
'req': false,
'res': false,
'test': false,
'expect': false
}
};

// Merge provided options with defaults
options = Object.assign({}, defaultOptions, options);

if (!options.indent)
// JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
options.indent = 1; // JSHint default value is 4
Expand Down
Loading