From 9cecdb486e0156301c191ca7ba375ff586e60a9e Mon Sep 17 00:00:00 2001 From: sibmaks Date: Mon, 24 Feb 2025 13:18:00 +0300 Subject: [PATCH 1/6] [Web App Mock] Common UI improvements resolves #53 - custom http methods - save button above examples - ms in delay in input group --- web-app-frontend/src/api/service.ts | 8 +- .../src/components/GraalVMMockContent.tsx | 1 - .../suggestive-input/SuggestiveInput.css | 23 +++ .../suggestive-input/SuggestiveInput.tsx | 153 ++++++++++++++++++ web-app-frontend/src/const/common.const.ts | 2 - .../src/pages/mock/AddEditMockPage.tsx | 4 +- web-app-frontend/src/pages/mock/MockForm.tsx | 81 ++++++---- 7 files changed, 233 insertions(+), 39 deletions(-) create mode 100644 web-app-frontend/src/components/suggestive-input/SuggestiveInput.css create mode 100644 web-app-frontend/src/components/suggestive-input/SuggestiveInput.tsx diff --git a/web-app-frontend/src/api/service.ts b/web-app-frontend/src/api/service.ts index 67b5ab0..0c62279 100644 --- a/web-app-frontend/src/api/service.ts +++ b/web-app-frontend/src/api/service.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { Method, MockType } from '../const/common.const'; +import { MockType } from '../const/common.const'; const service = axios.create({ baseURL: '/web/app/mocks/rest/api', @@ -86,7 +86,7 @@ export interface MockMeta { export interface CreateMockRq { name: string; - method: Method; + method: string; path: string; type: MockType; delay: number, @@ -102,7 +102,7 @@ export interface GetMockRs { body: { serviceId: number; mockId: number; - method: Method; + method: string; name: string; path: string; type: MockType; @@ -117,7 +117,7 @@ export const getMock = (serviceId: number, mockId: number) => service.get = ({ editorProps={{ $blockScrolling: true }} /> - ); }; diff --git a/web-app-frontend/src/components/suggestive-input/SuggestiveInput.css b/web-app-frontend/src/components/suggestive-input/SuggestiveInput.css new file mode 100644 index 0000000..3c089f6 --- /dev/null +++ b/web-app-frontend/src/components/suggestive-input/SuggestiveInput.css @@ -0,0 +1,23 @@ +.suggestions-dropdown { + position: absolute; + z-index: 1000; + background-color: white; + border-radius: 4px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + margin-top: 4px; +} + +.suggestion-item { + font-size: 0.875rem; + padding: 8px 12px; + cursor: pointer; +} + +.suggestion-text { + font-size: 0.75rem; + padding: 8px 12px; +} + +.suggestion-item:hover { + background-color: #f8f9fa; +} diff --git a/web-app-frontend/src/components/suggestive-input/SuggestiveInput.tsx b/web-app-frontend/src/components/suggestive-input/SuggestiveInput.tsx new file mode 100644 index 0000000..1c3c53f --- /dev/null +++ b/web-app-frontend/src/components/suggestive-input/SuggestiveInput.tsx @@ -0,0 +1,153 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { FormControl, ListGroup } from 'react-bootstrap'; +import './SuggestiveInput.css'; + +export interface SuggestiveItem { + key: string; + value: string; + data?: any; +} + +export interface SuggestedItem { + key?: string; + value: string; + data?: any; +} + +interface SuggestiveInputProps { + value?: string; + suggestions: SuggestiveItem[]; + maxSuggestions?: number; + mode: 'strict' | 'free'; + itemsToScroll?: number; + onFilter?: (input: string) => SuggestiveItem[]; + onChange: (value: SuggestedItem) => void; + placeholder?: string; + required: boolean; + disabled?: boolean; + clarifyText?: string; +} + +const SuggestiveInput: React.FC = ({ + value, + suggestions, + maxSuggestions = 5, + mode, + itemsToScroll = 5, + onFilter, + onChange, + placeholder, + required, + disabled, + clarifyText = 'Clarify request' + }) => { + const [inputValue, setInputValue] = useState(value ?? ''); + const [filteredSuggestions, setFilteredSuggestions] = useState( + suggestions.slice( + 0, + maxSuggestions + ) + ); + const [filteredSliced, setFilteredSliced] = useState(suggestions.length > filteredSuggestions.length); + const [showSuggestions, setShowSuggestions] = useState(false); + const inputRef = useRef(null); + const [dropdownWidth, setDropdownWidth] = useState(undefined); + + if (!onFilter) { + onFilter = it => { + const substring = it.toLowerCase(); + return suggestions.filter(it => it.value.includes(substring)); + }; + } + + useEffect(() => { + if (inputRef.current) { + setDropdownWidth(inputRef.current.offsetWidth); + } + }, [inputValue]); + + const handleValueChange = (value: string): SuggestiveItem[] => { + setInputValue(value); + + const filtered = onFilter(value); + const sliced = filtered.slice(0, maxSuggestions); + setFilteredSuggestions(sliced); + setFilteredSliced(filtered.length > sliced.length); + return sliced; + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const value = e.target.value; + const sliced = handleValueChange(value); + + setShowSuggestions(sliced.length > 0); + + if (sliced.length > 0) { + const candidate = sliced[0]; + onChange({ + key: candidate.key, + value: candidate.value, + data: candidate.data + }); + } else if (mode === 'free') { + onChange({ value: value }); + } else { + onChange({ value: '' }); + } + }; + + const handleSuggestionClick = (suggestion: SuggestiveItem) => { + handleValueChange(suggestion.value); + setShowSuggestions(false); + onChange(suggestion); + }; + + const handleBlur = () => { + setTimeout(() => setShowSuggestions(false), 200); + }; + + return ( + <> + setShowSuggestions(filteredSuggestions.length > 0)} + onBlur={handleBlur} + placeholder={placeholder} + required={required} + disabled={disabled} + /> + {showSuggestions && ( +
+ + {filteredSuggestions.map((suggestion) => ( + handleSuggestionClick(suggestion)} + className="suggestion-item" + > + {suggestion.value} + + ))} + {filteredSliced && ( + + {clarifyText} + + )} + +
+ )} + + ); +}; + +export default SuggestiveInput; diff --git a/web-app-frontend/src/const/common.const.ts b/web-app-frontend/src/const/common.const.ts index 02c7bbd..d927856 100644 --- a/web-app-frontend/src/const/common.const.ts +++ b/web-app-frontend/src/const/common.const.ts @@ -9,8 +9,6 @@ export const methods = [ 'DELETE' ] as const; -export type Method = typeof methods[number]; - export const statusCodes = new Map([ [100, 'Continue'] as const, [101, 'Switching Protocols'] as const, diff --git a/web-app-frontend/src/pages/mock/AddEditMockPage.tsx b/web-app-frontend/src/pages/mock/AddEditMockPage.tsx index e9e8726..e0f1f0c 100644 --- a/web-app-frontend/src/pages/mock/AddEditMockPage.tsx +++ b/web-app-frontend/src/pages/mock/AddEditMockPage.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { createMock, getMock, updateMock } from '../../api/service'; import { useNavigate, useParams } from 'react-router-dom'; -import { contextPath, Method, methods, MockType } from '../../const/common.const'; +import { contextPath, methods, MockType } from '../../const/common.const'; import { decodeToBuffer, encode } from '../../utils/base64'; import MockForm from './MockForm'; @@ -11,7 +11,7 @@ const AddEditMockPage: React.FC = () => { const { serviceId, mockId } = useParams(); const [mockName, setMockName] = useState(''); - const [method, setMethod] = useState(methods[0]); + const [method, setMethod] = useState(methods[0]); const [path, setPath] = useState(''); const [delay, setDelay] = useState(0); const [mockType, setMockType] = useState('STATIC'); diff --git a/web-app-frontend/src/pages/mock/MockForm.tsx b/web-app-frontend/src/pages/mock/MockForm.tsx index 1da1d1d..a757862 100644 --- a/web-app-frontend/src/pages/mock/MockForm.tsx +++ b/web-app-frontend/src/pages/mock/MockForm.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Button, Col, Container, Form, InputGroup, OverlayTrigger, Row, Table, Tooltip } from 'react-bootstrap'; -import { Method, methods, MockType, mockTypes, StatusCode, statusCodes } from '../../const/common.const'; +import { methods, MockType, mockTypes, StatusCode, statusCodes } from '../../const/common.const'; import HttpHeadersForm from '../../components/HttpHeadersForm'; import StaticMockContent from '../../components/StaticMockContent'; import GraalVMMockContent from '../../components/GraalVMMockContent'; @@ -8,18 +8,20 @@ import StaticFileMockContent from '../../components/StaticFileMockContent'; import { ArrowLeft01Icon, FloppyDiskIcon } from 'hugeicons-react'; import { Loader } from '../../components/Loader'; import './MockForm.css'; +import CodeDocumentation from './CodeDocumentation'; +import SuggestiveInput from '../../components/suggestive-input/SuggestiveInput'; type MockFormProps = { loading: boolean; mockName: string; - method: Method; + method: string; path: string; delay: number; mockType: MockType; meta: { [key: string]: string }; content: ArrayBuffer; setMockName: (name: string) => void; - setMethod: (method: Method) => void; + setMethod: (method: string) => void; setPath: (path: string) => void; setDelay: (delay: number) => void; setMockType: (type: MockType) => void; @@ -117,13 +119,20 @@ export const MockForm: React.FC = ({
{/* Mock Name Input */} - Name - setMockName(e.target.value)} - required - /> + + + Name + + + setMockName(e.target.value)} + required + /> + + {/* Method and Path Fields */} @@ -131,21 +140,22 @@ export const MockForm: React.FC = ({ HTTP Method - setMethod(methods[e.target.selectedIndex])} - required - > - {methods.map((it) => ( - - ))} - + onChange={it => setMethod(it.value)} + required={true} + suggestions={methods.map(it => { + return { key: it, value: it }; + })} + maxSuggestions={methods.length} + /> - Path + Path / = ({ > setPath(e.target.value)} placeholder="Ant pattern or path" @@ -176,8 +187,9 @@ export const MockForm: React.FC = ({ - Status + Status = ({ - Delay (ms) - setDelay(+e.target.value)} - required - /> + Delay + + setDelay(+e.target.value)} + required + /> + ms + - Type + Type = ({ )} {/* Submit Button */} - + + + {(mockType === 'JS' || mockType === 'PYTHON') && ( + + )} From d9ec648d61fd05556457d60447cec9b1e1ac76f1 Mon Sep 17 00:00:00 2001 From: sibmaks Date: Mon, 24 Feb 2025 13:34:23 +0300 Subject: [PATCH 2/6] [Web App Mock] Common UI improvements resolves #53 - custom service code --- .../suggestive-input/SuggestiveInput.tsx | 9 +- web-app-frontend/src/const/common.const.ts | 2 - web-app-frontend/src/pages/mock/MockForm.tsx | 123 ++++++++++-------- 3 files changed, 74 insertions(+), 60 deletions(-) diff --git a/web-app-frontend/src/components/suggestive-input/SuggestiveInput.tsx b/web-app-frontend/src/components/suggestive-input/SuggestiveInput.tsx index 1c3c53f..3e5ecbe 100644 --- a/web-app-frontend/src/components/suggestive-input/SuggestiveInput.tsx +++ b/web-app-frontend/src/components/suggestive-input/SuggestiveInput.tsx @@ -15,6 +15,8 @@ export interface SuggestedItem { } interface SuggestiveInputProps { + id?: string; + type?: 'text' | 'number' value?: string; suggestions: SuggestiveItem[]; maxSuggestions?: number; @@ -29,6 +31,8 @@ interface SuggestiveInputProps { } const SuggestiveInput: React.FC = ({ + id, + type = 'text', value, suggestions, maxSuggestions = 5, @@ -97,7 +101,7 @@ const SuggestiveInput: React.FC = ({ }; const handleSuggestionClick = (suggestion: SuggestiveItem) => { - handleValueChange(suggestion.value); + handleValueChange(suggestion.key); setShowSuggestions(false); onChange(suggestion); }; @@ -110,7 +114,8 @@ const SuggestiveInput: React.FC = ({ <> setShowSuggestions(filteredSuggestions.length > 0)} diff --git a/web-app-frontend/src/const/common.const.ts b/web-app-frontend/src/const/common.const.ts index d927856..3dbeacb 100644 --- a/web-app-frontend/src/const/common.const.ts +++ b/web-app-frontend/src/const/common.const.ts @@ -75,8 +75,6 @@ export const statusCodes = new Map([ export type MapKey> = T extends Map ? K : never -export type StatusCode = MapKey - export const mimeToAceModeMap = new Map([ ['text/html', 'html'] as const, ['text/css', 'css'] as const, diff --git a/web-app-frontend/src/pages/mock/MockForm.tsx b/web-app-frontend/src/pages/mock/MockForm.tsx index a757862..72dab4d 100644 --- a/web-app-frontend/src/pages/mock/MockForm.tsx +++ b/web-app-frontend/src/pages/mock/MockForm.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Button, Col, Container, Form, InputGroup, OverlayTrigger, Row, Table, Tooltip } from 'react-bootstrap'; -import { methods, MockType, mockTypes, StatusCode, statusCodes } from '../../const/common.const'; +import { methods, MockType, mockTypes, statusCodes } from '../../const/common.const'; import HttpHeadersForm from '../../components/HttpHeadersForm'; import StaticMockContent from '../../components/StaticMockContent'; import GraalVMMockContent from '../../components/GraalVMMockContent'; @@ -53,13 +53,6 @@ export const MockForm: React.FC = ({ navigateBack }) => { - const onStatusChange = (e: React.ChangeEvent) => { - const newStatusCode = +e.target.value as StatusCode; - if (newStatusCode) { - setMeta({ ...meta, STATUS_CODE: `${newStatusCode}` }); - } - }; - const onMockTypeChange = (e: React.ChangeEvent) => { setMockType(e.target.value as MockType); }; @@ -119,11 +112,11 @@ export const MockForm: React.FC = ({
{/* Mock Name Input */} - - + + Name - + = ({ {/* Method and Path Fields */} - + HTTP Method = ({ - + Path @@ -185,54 +178,72 @@ export const MockForm: React.FC = ({ {/* Status and Mock Type Fields */} - + - Status - - {Array.from(statusCodes.keys()).map((it) => ( - - ))} - + + + Status + + + setMeta({ ...meta, STATUS_CODE: `${it.key ?? it.value}` })} + required={true} + suggestions={Array.from(statusCodes).map(([key, value]) => { + return { key: `${key}`, value: `${key}: ${value}` }; + })} + maxSuggestions={10} + /> + + - - Delay - - setDelay(+e.target.value)} - required - /> - ms - + + + + Delay + + + + setDelay(+e.target.value)} + required + /> + ms + + + - + - Type - - { - Array.from(mockTypes.keys()).map( - it => ( - - ) - ) - } - + + + Type + + + + { + Array.from(mockTypes.keys()).map( + it => ( + + ) + ) + } + + + From ca0c7a4642ee1698255296c2796ffb32a91d21cd Mon Sep 17 00:00:00 2001 From: sibmaks Date: Mon, 24 Feb 2025 14:21:05 +0300 Subject: [PATCH 3/6] [Web App Mock] Common UI improvements resolves #53 - add sort by default logic --- web-app-frontend/src/components/CustomTable.tsx | 12 ++++++++++-- .../src/pages/invocations/MockInvocationListPage.tsx | 1 + .../src/pages/mocks/ServiceMocksListPage.tsx | 2 ++ .../src/pages/service/ServiceListPage.tsx | 1 + .../src/pages/share/ServiceMocksListExportPage.tsx | 2 ++ .../src/pages/share/ServiceMocksListImportPage.tsx | 2 ++ 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/web-app-frontend/src/components/CustomTable.tsx b/web-app-frontend/src/components/CustomTable.tsx index dba0bf7..003b6b5 100644 --- a/web-app-frontend/src/components/CustomTable.tsx +++ b/web-app-frontend/src/components/CustomTable.tsx @@ -40,6 +40,10 @@ export interface CustomTableProps { columns: TableColumn[]; data: Array; sortableColumns?: string[]; + sortByDefault?: { + column: string; + direction?: 'asc' | 'desc'; + }; filterableColumns?: string[]; styleProps?: StyleProps; onRowClick?: (row: Row) => void; @@ -49,6 +53,10 @@ const CustomTable: React.FC = ({ columns, data, sortableColumns = [], + sortByDefault = { + column: '', + direction: 'asc' + }, filterableColumns = [], styleProps = { centerHeaders: true, @@ -57,8 +65,8 @@ const CustomTable: React.FC = ({ onRowClick = null, }) => { const [filter, setFilter] = useState<{ [key: string]: string }>({}); - const [sortColumn, setSortColumn] = useState(''); - const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + const [sortColumn, setSortColumn] = useState(sortByDefault.column); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(sortByDefault.direction ?? 'asc'); // Handle filter changes const handleFilterChange = (event: React.ChangeEvent, columnKey: string) => { diff --git a/web-app-frontend/src/pages/invocations/MockInvocationListPage.tsx b/web-app-frontend/src/pages/invocations/MockInvocationListPage.tsx index 63f95c5..bd02509 100644 --- a/web-app-frontend/src/pages/invocations/MockInvocationListPage.tsx +++ b/web-app-frontend/src/pages/invocations/MockInvocationListPage.tsx @@ -139,6 +139,7 @@ const MockInvocationListPage: React.FC = () => { })} onRowClick={handleRowClick} sortableColumns={['method', 'path', 'timing', 'status', 'createdAt']} + sortByDefault={{column: 'createdAt', direction: 'desc'}} filterableColumns={['method', 'path', 'timing', 'status', 'createdAt']} styleProps={{ centerHeaders: true, diff --git a/web-app-frontend/src/pages/mocks/ServiceMocksListPage.tsx b/web-app-frontend/src/pages/mocks/ServiceMocksListPage.tsx index d97d3e2..7261431 100644 --- a/web-app-frontend/src/pages/mocks/ServiceMocksListPage.tsx +++ b/web-app-frontend/src/pages/mocks/ServiceMocksListPage.tsx @@ -115,6 +115,7 @@ const ServiceMocksListPage: React.FC = () => { ]} data={mocks.map(mock => { return { + mockId: mock.mockId, method: { representation: {mock.method}, value: mock.method @@ -145,6 +146,7 @@ const ServiceMocksListPage: React.FC = () => { }; })} sortableColumns={['method', 'name', 'path', 'type']} + sortByDefault={{column: 'mockId'}} filterableColumns={['method', 'name', 'path', 'type']} styleProps={{ centerHeaders: true, diff --git a/web-app-frontend/src/pages/service/ServiceListPage.tsx b/web-app-frontend/src/pages/service/ServiceListPage.tsx index 5530772..4564748 100644 --- a/web-app-frontend/src/pages/service/ServiceListPage.tsx +++ b/web-app-frontend/src/pages/service/ServiceListPage.tsx @@ -201,6 +201,7 @@ const ServiceListPage: React.FC = () => { }; })} sortableColumns={['code']} + sortByDefault={{column: 'serviceId'}} filterableColumns={['code']} styleProps={{ centerHeaders: true, diff --git a/web-app-frontend/src/pages/share/ServiceMocksListExportPage.tsx b/web-app-frontend/src/pages/share/ServiceMocksListExportPage.tsx index 6f4cf6a..cad02e7 100644 --- a/web-app-frontend/src/pages/share/ServiceMocksListExportPage.tsx +++ b/web-app-frontend/src/pages/share/ServiceMocksListExportPage.tsx @@ -80,6 +80,7 @@ const ServiceMocksListPage: React.FC = () => { ]} data={mocks.map(mock => { return { + mockId: mock.mockId, export: { representation: { }; })} sortableColumns={['method', 'name', 'path', 'type']} + sortByDefault={{column: 'mockId'}} filterableColumns={['method', 'name', 'path', 'type']} styleProps={{ centerHeaders: true, diff --git a/web-app-frontend/src/pages/share/ServiceMocksListImportPage.tsx b/web-app-frontend/src/pages/share/ServiceMocksListImportPage.tsx index ef4c621..7dfdfed 100644 --- a/web-app-frontend/src/pages/share/ServiceMocksListImportPage.tsx +++ b/web-app-frontend/src/pages/share/ServiceMocksListImportPage.tsx @@ -166,6 +166,7 @@ const ServiceMocksListImportPage: React.FC = () => { { key: 'enabled', label: 'Enabled' }, ]} data={service.mocks.map((mock, mockIndex) => ({ + mockIndex: mockIndex, import: { representation: ( { }, }))} sortableColumns={['method', 'name', 'path', 'type']} + sortByDefault={{column: 'mockIndex'}} filterableColumns={['method', 'name', 'path', 'type']} styleProps={{ centerHeaders: true, textCenterValues: true }} /> From 4548cc65653f7dfd39cd214a4a73ba4b6f6b69f2 Mon Sep 17 00:00:00 2001 From: sibmaks Date: Mon, 24 Feb 2025 14:27:02 +0300 Subject: [PATCH 4/6] [Web App Mock] Common UI improvements resolves #53 - Use responsive table on mock invocation page --- .../pages/invocations/invocation/MockInvocationPage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web-app-frontend/src/pages/invocations/invocation/MockInvocationPage.tsx b/web-app-frontend/src/pages/invocations/invocation/MockInvocationPage.tsx index 1b3cfbc..1a7603b 100644 --- a/web-app-frontend/src/pages/invocations/invocation/MockInvocationPage.tsx +++ b/web-app-frontend/src/pages/invocations/invocation/MockInvocationPage.tsx @@ -115,7 +115,7 @@ const MockInvocationPage: React.FC = () => { {!error && ( - +
@@ -149,7 +149,7 @@ const MockInvocationPage: React.FC = () => {
Remote Client Host

Query Params

- +
@@ -162,7 +162,7 @@ const MockInvocationPage: React.FC = () => {
Key

Request Headers

- +
@@ -182,7 +182,7 @@ const MockInvocationPage: React.FC = () => { />

Response Headers

-
Key
+
From f4df094d008a859dddfbd63b7216002586ec4125 Mon Sep 17 00:00:00 2001 From: sibmaks Date: Mon, 24 Feb 2025 14:48:14 +0300 Subject: [PATCH 5/6] [Web App Mock] Common UI improvements resolves #53 - Service not created on Enter click --- web-app-frontend/src/pages/service/ServiceModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-app-frontend/src/pages/service/ServiceModal.tsx b/web-app-frontend/src/pages/service/ServiceModal.tsx index bd649c8..b065d9d 100644 --- a/web-app-frontend/src/pages/service/ServiceModal.tsx +++ b/web-app-frontend/src/pages/service/ServiceModal.tsx @@ -25,7 +25,7 @@ export const ServiceModal: React.FC = ({ {modalMode === 'edit' ? 'Edit Service' : 'Add New Service'} - + {handleSave(); return false;}}> Service Code Date: Mon, 24 Feb 2025 14:48:42 +0300 Subject: [PATCH 6/6] [Web App Mock] Common UI improvements resolves #53 - mock refactoring --- .../src/pages/mock/AddEditMockPage.tsx | 76 ++++++------- web-app-frontend/src/pages/mock/MockForm.tsx | 101 ++++++++---------- 2 files changed, 82 insertions(+), 95 deletions(-) diff --git a/web-app-frontend/src/pages/mock/AddEditMockPage.tsx b/web-app-frontend/src/pages/mock/AddEditMockPage.tsx index e0f1f0c..f648401 100644 --- a/web-app-frontend/src/pages/mock/AddEditMockPage.tsx +++ b/web-app-frontend/src/pages/mock/AddEditMockPage.tsx @@ -1,22 +1,36 @@ import React, { useEffect, useState } from 'react'; -import { createMock, getMock, updateMock } from '../../api/service'; +import { createMock, getMock, MockMeta, updateMock } from '../../api/service'; import { useNavigate, useParams } from 'react-router-dom'; import { contextPath, methods, MockType } from '../../const/common.const'; import { decodeToBuffer, encode } from '../../utils/base64'; import MockForm from './MockForm'; +export interface ModifyingMock { + name: string; + method: string; + path: string; + type: MockType; + delay: number; + meta: MockMeta; + content: ArrayBuffer; +} + const AddEditMockPage: React.FC = () => { const [loading, setLoading] = useState(true); const navigate = useNavigate(); const { serviceId, mockId } = useParams(); - const [mockName, setMockName] = useState(''); - const [method, setMethod] = useState(methods[0]); - const [path, setPath] = useState(''); - const [delay, setDelay] = useState(0); - const [mockType, setMockType] = useState('STATIC'); - const [meta, setMeta] = useState<{ [key: string]: string }>({ STATUS_CODE: '200' }); - const [content, setContent] = useState(new ArrayBuffer(0)); + const [modifyingMock, setModifyingMock] = useState({ + name: '', + method: methods[0], + path: '', + type: 'STATIC', + delay: 0, + meta: { + STATUS_CODE: '200' + }, + content: new ArrayBuffer(0) + }); useEffect(() => { if (mockId) { @@ -35,13 +49,15 @@ const AddEditMockPage: React.FC = () => { try { const response = await getMock(+serviceId, +mockId); const body = response.data.body; - setMockName(body.name); - setMethod(body.method); - setPath(body.path.slice(1)); - setDelay(body.delay); - setMockType(body.type); - setMeta(body.meta); - setContent(decodeToBuffer(body.content)); + setModifyingMock({ + name: body.name, + method: body.method, + path: body.path.slice(1), + delay: body.delay, + type: body.type, + meta: body.meta, + content: decodeToBuffer(body.content) + }) } catch (error) { console.error('Failed to fetch mock:', error); } finally { @@ -58,13 +74,13 @@ const AddEditMockPage: React.FC = () => { e.preventDefault(); try { const mockData = { - name: mockName, - method, - path: '/' + path, - type: mockType, - delay: delay, - meta, - content: encode(content) + name: modifyingMock.name, + method: modifyingMock.method, + path: '/' + modifyingMock.path, + type: modifyingMock.type, + delay: modifyingMock.delay, + meta: modifyingMock.meta, + content: encode(modifyingMock.content) }; if (mockId) { await updateMock(+serviceId, +mockId, mockData); @@ -84,20 +100,8 @@ const AddEditMockPage: React.FC = () => { return ( void; - setMethod: (method: string) => void; - setPath: (path: string) => void; - setDelay: (delay: number) => void; - setMockType: (type: MockType) => void; - setMeta: (meta: { [key: string]: string }) => void; - setContent: (content: ArrayBuffer) => void; + modifyingMock: ModifyingMock; + setModifyingMock: (value: ModifyingMock) => void; onSubmit: (e: React.FormEvent) => void; isEditMode: boolean; navigateBack: () => void; @@ -34,27 +23,15 @@ type MockFormProps = { export const MockForm: React.FC = ({ loading, - mockName, - method, - path, - delay, - mockType, - meta, - content, - setMockName, - setMethod, - setPath, - setDelay, - setMockType, - setMeta, - setContent, + modifyingMock, + setModifyingMock, onSubmit, isEditMode, navigateBack }) => { const onMockTypeChange = (e: React.ChangeEvent) => { - setMockType(e.target.value as MockType); + setModifyingMock({...modifyingMock, type: e.target.value as MockType}) }; const pathTooltip = ( @@ -120,8 +97,8 @@ export const MockForm: React.FC = ({ setMockName(e.target.value)} + value={modifyingMock.name} + onChange={(e) => setModifyingMock({...modifyingMock, name: e.target.value})} required /> @@ -135,8 +112,8 @@ export const MockForm: React.FC = ({ HTTP Method setMethod(it.value)} + value={modifyingMock.method} + onChange={it => setModifyingMock({...modifyingMock, method: it.value})} required={true} suggestions={methods.map(it => { return { key: it, value: it }; @@ -159,8 +136,8 @@ export const MockForm: React.FC = ({ setPath(e.target.value)} + value={modifyingMock.path} + onChange={e => setModifyingMock({...modifyingMock, path: e.target.value})} placeholder="Ant pattern or path" required /> @@ -173,7 +150,10 @@ export const MockForm: React.FC = ({ {/* HTTP Headers */} Http Headers - + setModifyingMock({...modifyingMock, meta: it})} + /> {/* Status and Mock Type Fields */} @@ -189,8 +169,11 @@ export const MockForm: React.FC = ({ id={'statusSelect'} type={'number'} mode={'free'} - value={meta['STATUS_CODE']} - onChange={it => setMeta({ ...meta, STATUS_CODE: `${it.key ?? it.value}` })} + value={modifyingMock.meta['STATUS_CODE']} + onChange={it => setModifyingMock({...modifyingMock, meta: { + ...modifyingMock.meta, + STATUS_CODE: `${it.key ?? it.value}` + }})} required={true} suggestions={Array.from(statusCodes).map(([key, value]) => { return { key: `${key}`, value: `${key}: ${value}` }; @@ -212,8 +195,8 @@ export const MockForm: React.FC = ({ type={'number'} id={'delayInput'} min={0} - value={`${delay}`} - onChange={(e) => setDelay(+e.target.value)} + value={`${modifyingMock.delay}`} + onChange={e => setModifyingMock({...modifyingMock, delay: +e.target.value})} required /> ms @@ -222,7 +205,7 @@ export const MockForm: React.FC = ({ - + Type @@ -230,14 +213,14 @@ export const MockForm: React.FC = ({ { - Array.from(mockTypes.keys()).map( - it => ( - + Array.from(mockTypes).map( + ([key, value]) => ( + ) ) } @@ -249,26 +232,26 @@ export const MockForm: React.FC = ({ {/* Content Section Based on Mock Type */} - {mockType === 'STATIC' && ( + {modifyingMock.type === 'STATIC' && ( setModifyingMock({...modifyingMock, content: it})} + meta={modifyingMock.meta} + setMeta={it => setModifyingMock({...modifyingMock, meta: it})} creation={!isEditMode} /> )} - {mockType === 'STATIC_FILE' && setModifyingMock({...modifyingMock, content: it})} isEditMode={isEditMode} />} - {(mockType === 'JS' || mockType === 'PYTHON') && ( + {(modifyingMock.type === 'JS' || modifyingMock.type === 'PYTHON') && ( setModifyingMock({...modifyingMock, content: it})} /> )} @@ -285,8 +268,8 @@ export const MockForm: React.FC = ({ - {(mockType === 'JS' || mockType === 'PYTHON') && ( - + {(modifyingMock.type === 'JS' || modifyingMock.type === 'PYTHON') && ( + )}
Key