mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: replace send button with Send/Cancel buttons on request url (#7675)
* feat: replace request send icon with Send/Cancel buttons --------- Co-authored-by: naman-bruno <naman@usebruno.com>
This commit is contained in:
@@ -324,7 +324,7 @@ test('should create and execute HTTP request', async ({ page, createTmpDir }) =>
|
|||||||
await page.getByRole('button', { name: 'Create' }).click();
|
await page.getByRole('button', { name: 'Create' }).click();
|
||||||
|
|
||||||
// Execute request
|
// Execute request
|
||||||
await page.locator('#send-request').getByRole('img').nth(2).click();
|
await page.getByTestId('send-arrow-icon').click();
|
||||||
|
|
||||||
// Verify response
|
// Verify response
|
||||||
await expect(page.getByRole('main')).toContainText('200 OK');
|
await expect(page.getByRole('main')).toContainText('200 OK');
|
||||||
|
|||||||
@@ -2,9 +2,13 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
height: 2.1rem;
|
height: 2.1rem;
|
||||||
border: ${(props) => props.theme.requestTabPanel.url.border};
|
|
||||||
border-radius: ${(props) => props.theme.border.radius.base};
|
|
||||||
|
|
||||||
|
.url-input-group {
|
||||||
|
border: ${(props) => props.theme.requestTabPanel.url.border};
|
||||||
|
border-radius: ${(props) => props.theme.border.radius.base};
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.infotip {
|
.infotip {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -49,6 +53,7 @@ const Wrapper = styled.div`
|
|||||||
.shortcut {
|
.shortcut {
|
||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default Wrapper;
|
export default Wrapper;
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ import { saveRequest, cancelRequest } from 'providers/ReduxStore/slices/collecti
|
|||||||
import { getRequestFromCurlCommand } from 'utils/curl';
|
import { getRequestFromCurlCommand } from 'utils/curl';
|
||||||
import HttpMethodSelector from './HttpMethodSelector';
|
import HttpMethodSelector from './HttpMethodSelector';
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
import { IconDeviceFloppy, IconArrowRight, IconCode, IconSquareRoundedX } from '@tabler/icons';
|
import { IconDeviceFloppy, IconCode } from '@tabler/icons';
|
||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import SendButton from 'components/RequestPane/SendButton';
|
||||||
import { isMacOS } from 'utils/common/platform';
|
import { isMacOS } from 'utils/common/platform';
|
||||||
import { hasRequestChanges } from 'utils/collections';
|
import { hasRequestChanges } from 'utils/collections';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
@@ -384,76 +385,67 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex items-center w-full">
|
<StyledWrapper className="flex items-center w-full">
|
||||||
<div className="flex items-center h-full min-w-fit">
|
<div className="flex items-center h-full url-input-group">
|
||||||
<HttpMethodSelector method={method} onMethodSelect={onMethodSelect} />
|
<div className="flex items-center h-full min-w-fit">
|
||||||
</div>
|
<HttpMethodSelector method={method} onMethodSelect={onMethodSelect} />
|
||||||
<div
|
|
||||||
id="request-url"
|
|
||||||
className="h-full w-full flex flex-row input-container overflow-auto"
|
|
||||||
>
|
|
||||||
<SingleLineEditor
|
|
||||||
ref={editorRef}
|
|
||||||
value={url}
|
|
||||||
placeholder="Enter URL or paste a cURL request"
|
|
||||||
onSave={(finalValue) => onSave(finalValue)}
|
|
||||||
theme={storedTheme}
|
|
||||||
onChange={(newValue) => onUrlChange(newValue)}
|
|
||||||
onRun={handleRun}
|
|
||||||
onPaste={item.type === 'http-request' ? handleHttpPaste : item.type === 'graphql-request' ? handleGraphqlPaste : null}
|
|
||||||
collection={collection}
|
|
||||||
highlightPathParams={true}
|
|
||||||
item={item}
|
|
||||||
showNewlineArrow={true}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center h-full mx-2 gap-3 cursor-pointer" id="send-request" onClick={handleRun}>
|
|
||||||
<div
|
|
||||||
title="Generate Code"
|
|
||||||
className="infotip"
|
|
||||||
onClick={(e) => {
|
|
||||||
handleGenerateCode(e);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconCode color={theme.requestTabs.icon.color} strokeWidth={1.5} size={20} className="cursor-pointer" />
|
|
||||||
<span className="infotiptext text-xs">Generate Code</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
title="Save Request"
|
id="request-url"
|
||||||
className="infotip"
|
className="h-full w-full flex flex-row items-center input-container overflow-auto"
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (!hasChanges) return;
|
|
||||||
onSave();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<IconDeviceFloppy
|
<SingleLineEditor
|
||||||
color={hasChanges ? theme.draftColor : theme.requestTabs.icon.color}
|
ref={editorRef}
|
||||||
strokeWidth={1.5}
|
value={url}
|
||||||
size={20}
|
placeholder="Enter URL or paste a cURL request"
|
||||||
className={`${hasChanges ? 'cursor-pointer' : 'cursor-default'}`}
|
onSave={(finalValue) => onSave(finalValue)}
|
||||||
|
theme={storedTheme}
|
||||||
|
onChange={(newValue) => onUrlChange(newValue)}
|
||||||
|
onRun={handleRun}
|
||||||
|
onPaste={item.type === 'http-request' ? handleHttpPaste : item.type === 'graphql-request' ? handleGraphqlPaste : null}
|
||||||
|
collection={collection}
|
||||||
|
highlightPathParams={true}
|
||||||
|
item={item}
|
||||||
|
showNewlineArrow={true}
|
||||||
/>
|
/>
|
||||||
<span className="infotiptext text-xs">
|
<div className="flex items-center h-full mx-2 gap-3" id="request-actions">
|
||||||
Save <span className="shortcut">({saveShortcut})</span>
|
<div
|
||||||
</span>
|
title="Generate Code"
|
||||||
|
className="infotip"
|
||||||
|
onClick={(e) => {
|
||||||
|
handleGenerateCode(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconCode color={theme.requestTabs.icon.color} strokeWidth={1.5} size={20} className="cursor-pointer" />
|
||||||
|
<span className="infotiptext text-xs">Generate Code</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
title="Save Request"
|
||||||
|
className="infotip"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!hasChanges) return;
|
||||||
|
onSave();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconDeviceFloppy
|
||||||
|
color={hasChanges ? theme.draftColor : theme.requestTabs.icon.color}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
size={20}
|
||||||
|
className={`${hasChanges ? 'cursor-pointer' : 'cursor-default'}`}
|
||||||
|
/>
|
||||||
|
<span className="infotiptext text-xs">
|
||||||
|
Save <span className="shortcut">({saveShortcut})</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isLoading || item.response?.stream?.running ? (
|
|
||||||
<IconSquareRoundedX
|
|
||||||
color={theme.requestTabPanel.url.iconDanger}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
size={20}
|
|
||||||
data-testid="cancel-request-icon"
|
|
||||||
onClick={handleCancelRequest}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<IconArrowRight
|
|
||||||
color={theme.requestTabPanel.url.icon}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
size={20}
|
|
||||||
data-testid="send-arrow-icon"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
<SendButton
|
||||||
|
isLoading={isLoading || item.response?.stream?.running}
|
||||||
|
onSend={handleRun}
|
||||||
|
onCancel={handleCancelRequest}
|
||||||
|
testId="send-arrow-icon"
|
||||||
|
/>
|
||||||
{generateCodeItemModalOpen && (
|
{generateCodeItemModalOpen && (
|
||||||
<GenerateCodeItem
|
<GenerateCodeItem
|
||||||
collectionUid={collection.uid}
|
collectionUid={collection.uid}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
min-width: 4.1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Button from 'ui/Button';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const SendButton = ({ isLoading = false, onSend, onCancel, testId = 'send-request-btn' }) => {
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="ml-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant={isLoading ? 'outline' : 'filled'}
|
||||||
|
color="primary"
|
||||||
|
data-testid={testId}
|
||||||
|
data-action={isLoading ? 'cancel' : 'send'}
|
||||||
|
onClick={isLoading ? onCancel : onSend}
|
||||||
|
>
|
||||||
|
{isLoading ? 'Cancel' : 'Send'}
|
||||||
|
</Button>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SendButton;
|
||||||
@@ -3,12 +3,12 @@ import styled from 'styled-components';
|
|||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
height: 2.1rem;
|
height: 2.1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
border: ${(props) => props.theme.requestTabPanel.url.border};
|
|
||||||
border-radius: ${(props) => props.theme.border.radius.base};
|
|
||||||
|
|
||||||
.input-container {
|
.input-container {
|
||||||
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
||||||
|
border: ${(props) => props.theme.requestTabPanel.url.border};
|
||||||
border-radius: ${(props) => props.theme.border.radius.base};
|
border-radius: ${(props) => props.theme.border.radius.base};
|
||||||
|
position: relative;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
||||||
@@ -99,6 +99,7 @@ const StyledWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default StyledWrapper;
|
export default StyledWrapper;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { IconArrowRight, IconDeviceFloppy, IconPlugConnected, IconPlugConnectedX } from '@tabler/icons';
|
import { IconDeviceFloppy, IconPlugConnected, IconPlugConnectedX } from '@tabler/icons';
|
||||||
|
import SendButton from 'components/RequestPane/SendButton';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import SingleLineEditor from 'components/SingleLineEditor/index';
|
import SingleLineEditor from 'components/SingleLineEditor/index';
|
||||||
import { requestUrlChanged } from 'providers/ReduxStore/slices/collections';
|
import { requestUrlChanged } from 'providers/ReduxStore/slices/collections';
|
||||||
@@ -123,7 +124,7 @@ const WsQueryUrl = ({ item, collection, handleRun }) => {
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<div className="flex items-center h-full">
|
<div className="flex items-center h-full">
|
||||||
<div className="flex items-center input-container flex-1 w-full h-full relative">
|
<div className="flex items-center input-container flex-1 min-w-0 h-full relative">
|
||||||
<div className="flex items-center justify-center px-[10px]">
|
<div className="flex items-center justify-center px-[10px]">
|
||||||
<span className="text-xs font-medium method-ws">WS</span>
|
<span className="text-xs font-medium method-ws">WS</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -187,15 +188,14 @@ const WsQueryUrl = ({ item, collection, handleRun }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div data-testid="run-button" className="cursor-pointer" onClick={handleRunClick}>
|
|
||||||
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={20} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{connectionStatus === CONNECTION_STATUS.CONNECTED && <div className="connection-status-strip"></div>}
|
||||||
</div>
|
</div>
|
||||||
|
<SendButton
|
||||||
|
onSend={handleRunClick}
|
||||||
|
testId="run-button"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{connectionStatus === CONNECTION_STATUS.CONNECTED && <div className="connection-status-strip"></div>}
|
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ test.describe('Create collection', () => {
|
|||||||
// Set the URL
|
// Set the URL
|
||||||
await page.locator('#request-url .CodeMirror').click();
|
await page.locator('#request-url .CodeMirror').click();
|
||||||
await page.locator('#request-url').locator('textarea').fill('http://localhost:8081');
|
await page.locator('#request-url').locator('textarea').fill('http://localhost:8081');
|
||||||
await page.locator('#send-request').getByTitle('Save Request').click();
|
await page.locator('#request-actions').getByTitle('Save Request').click();
|
||||||
|
|
||||||
// Send a request
|
// Send a request
|
||||||
await page.locator('#request-url .CodeMirror').click();
|
await page.locator('#request-url .CodeMirror').click();
|
||||||
await page.locator('#request-url').locator('textarea').fill('/ping');
|
await page.locator('#request-url').locator('textarea').fill('/ping');
|
||||||
await page.locator('#send-request').getByTitle('Save Request').click();
|
await page.locator('#request-actions').getByTitle('Save Request').click();
|
||||||
await page.locator('#send-request').getByRole('img').nth(2).click();
|
await page.getByTestId('send-arrow-icon').click();
|
||||||
|
|
||||||
// Verify the response
|
// Verify the response
|
||||||
await expect(page.getByRole('main')).toContainText('200 OK');
|
await expect(page.getByRole('main')).toContainText('200 OK');
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ test.describe('Multiline Variables - Read Environment Test', () => {
|
|||||||
await expect(page.locator('.current-environment').filter({ hasText: /Test/ })).toBeVisible();
|
await expect(page.locator('.current-environment').filter({ hasText: /Test/ })).toBeVisible();
|
||||||
|
|
||||||
// send request
|
// send request
|
||||||
const sendButton = page.locator('#send-request').getByRole('img').nth(2);
|
const sendButton = page.getByTestId('send-arrow-icon');
|
||||||
await expect(sendButton).toBeVisible();
|
await expect(sendButton).toBeVisible();
|
||||||
await sendButton.click();
|
await sendButton.click();
|
||||||
await expect(page.locator('.response-status-code.text-ok')).toBeVisible();
|
await expect(page.locator('.response-status-code.text-ok')).toBeVisible();
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ test.describe.serial('Create and Delete Response Examples', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Test form reset', async () => {
|
await test.step('Test form reset', async () => {
|
||||||
await page.locator('#send-request').getByRole('img').nth(2).click();
|
await page.getByTestId('send-arrow-icon').click();
|
||||||
await clickResponseAction(page, 'response-bookmark-btn');
|
await clickResponseAction(page, 'response-bookmark-btn');
|
||||||
|
|
||||||
await page.getByTestId('create-example-name-input').fill('Test Name');
|
await page.getByTestId('create-example-name-input').fill('Test Name');
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ const createUntitledRequest = async (
|
|||||||
if (url) {
|
if (url) {
|
||||||
await page.locator('#request-url .CodeMirror').click();
|
await page.locator('#request-url .CodeMirror').click();
|
||||||
await page.locator('#request-url textarea').fill(url);
|
await page.locator('#request-url textarea').fill(url);
|
||||||
await page.locator('#send-request').getByTitle('Save Request').click();
|
await page.locator('#request-actions').getByTitle('Save Request').click();
|
||||||
await page.waitForTimeout(200);
|
await page.waitForTimeout(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export const buildCommonLocators = (page: Page) => ({
|
|||||||
newRequestUrl: () => page.locator('#new-request-url .CodeMirror'),
|
newRequestUrl: () => page.locator('#new-request-url .CodeMirror'),
|
||||||
requestNameInput: () => page.getByPlaceholder('Request Name'),
|
requestNameInput: () => page.getByPlaceholder('Request Name'),
|
||||||
requestTestId: () => page.getByTestId('request-name'),
|
requestTestId: () => page.getByTestId('request-name'),
|
||||||
generateCodeButton: () => page.locator('#send-request .infotip').first(),
|
generateCodeButton: () => page.locator('#request-actions .infotip').first(),
|
||||||
bodyModeSelector: () => page.getByTestId('request-body-mode-selector'),
|
bodyModeSelector: () => page.getByTestId('request-body-mode-selector'),
|
||||||
bodyEditor: () => page.getByTestId('request-body-editor')
|
bodyEditor: () => page.getByTestId('request-body-editor')
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user