From e58855e636667e4f0cc59db3f77deb71fd18c386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=98=84=EC=84=B1=ED=95=84?= Date: Sat, 25 Jan 2025 22:10:27 +0900 Subject: [PATCH] =?UTF-8?q?PropertyTable=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TestMasterUI/src/components/APITester.jsx | 12 +- .../api-tester/DraggableApiItem.jsx | 2 +- .../components/api-tester/VariableModal.jsx | 114 ++++++++++++++++++ .../client-views/HTTPClientView.jsx | 94 +++++++++------ .../src/components/shared/EditableInput.jsx | 8 ++ .../src/components/shared/PropertyTable.jsx | 22 ++-- TestMasterUI/src/providers/APIProvider.jsx | 3 - TestMasterUI/src/services/APIClient.js | 10 +- 8 files changed, 214 insertions(+), 51 deletions(-) create mode 100644 TestMasterUI/src/components/api-tester/VariableModal.jsx diff --git a/TestMasterUI/src/components/APITester.jsx b/TestMasterUI/src/components/APITester.jsx index a9b5dcd..d8c1cf1 100644 --- a/TestMasterUI/src/components/APITester.jsx +++ b/TestMasterUI/src/components/APITester.jsx @@ -6,6 +6,7 @@ import AddCollectionDialog from '@/components/api-tester/AddCollectionDialog.jsx import AddAPIDialog from '@/components/api-tester/AddAPIDialog.jsx'; import AddScenarioDialog from '@/components/api-tester/AddScenarioDialog.jsx'; import {useLoadTest} from '@/providers/LoadTestProvider.jsx'; +import VariableModal from '@/components/api-tester/VariableModal.jsx'; const menuConfig = [ { @@ -22,7 +23,8 @@ const menuConfig = [ items: [ {id: 'collection-add', label: '컬렉션 추가', action: 'collection-add'}, {id: 'request-add', label: '요청 추가', action: 'request-add'}, - {id: 'scenario-add', label: '시나리오 추가', action: 'scenario-add'} + {id: 'scenario-add', label: '시나리오 추가', action: 'scenario-add'}, + {id: 'variables-show', label: '변수 보기', action: 'variables-show'}, ] }, { @@ -81,6 +83,7 @@ const APITester = () => { const [showAddCollection, setShowAddCollection] = useState(false); const [showAddApi, setShowAddApi] = useState(false); const [showAddScenario, setShowAddScenario] = useState(false); + const [showVariableModal, setShowVariableModal] = useState(false); const { handleLogout, createCollection, createAPIRequest } = useAPI(); const {createScenario} = useLoadTest(); @@ -131,6 +134,9 @@ const APITester = () => { case 'scenario-add': setShowAddScenario(true); break; + case 'variables-show': + setShowVariableModal(true); + break; case 'logout': const success = await handleLogout(); if (success) { @@ -197,6 +203,10 @@ const APITester = () => { onOpenChange={setShowAddScenario} onSubmit={handleAddScenario} /> + ); }; diff --git a/TestMasterUI/src/components/api-tester/DraggableApiItem.jsx b/TestMasterUI/src/components/api-tester/DraggableApiItem.jsx index ea05b7c..f37ed81 100644 --- a/TestMasterUI/src/components/api-tester/DraggableApiItem.jsx +++ b/TestMasterUI/src/components/api-tester/DraggableApiItem.jsx @@ -53,7 +53,7 @@ const DraggableApiItem = ({api, collectionId, onApiAction}) => { - s + {api.name} diff --git a/TestMasterUI/src/components/api-tester/VariableModal.jsx b/TestMasterUI/src/components/api-tester/VariableModal.jsx new file mode 100644 index 0000000..2c12b86 --- /dev/null +++ b/TestMasterUI/src/components/api-tester/VariableModal.jsx @@ -0,0 +1,114 @@ +import React, { useState, useEffect } from 'react'; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Trash } from 'lucide-react'; + +const VariableModal = ({ open, onOpenChange }) => { + const [variables, setVariables] = useState([]); + + useEffect(() => { + if (open) { + const globals = window.globals || {}; + setVariables(Object.entries(globals).map(([key, value]) => ({ key, value }))); + } + }, [open]); + + const handleKeyChange = (index, newKey) => { + const newVariables = [...variables]; + const oldKey = newVariables[index].key; + newVariables[index].key = newKey; + setVariables(newVariables); + + const globals = window.globals || {}; + const value = globals[oldKey]; + delete globals[oldKey]; + globals[newKey] = value; + window.globals = globals; + }; + + const handleValueChange = (index, newValue) => { + const newVariables = [...variables]; + newVariables[index].value = newValue; + setVariables(newVariables); + + const globals = window.globals || {}; + globals[newVariables[index].key] = newValue; + window.globals = globals; + }; + + const handleDelete = (index) => { + const key = variables[index].key; + const newVariables = variables.filter((_, i) => i !== index); + setVariables(newVariables); + + const globals = window.globals || {}; + delete globals[key]; + window.globals = globals; + }; + + const handleAdd = () => { + setVariables([...variables, { key: '', value: '' }]); + }; + + return ( + + + + 환경 변수 + +
+
Globals
+ + + + + + + + + + + + {variables.map((variable, index) => ( + + + handleKeyChange(index, e.target.value)} + /> + + + handleValueChange(index, e.target.value)} + /> + + + + + + ))} + +
+
+ + + +
+
+ ); +}; + +export default VariableModal; diff --git a/TestMasterUI/src/components/client-views/HTTPClientView.jsx b/TestMasterUI/src/components/client-views/HTTPClientView.jsx index 86154e7..4efea0b 100644 --- a/TestMasterUI/src/components/client-views/HTTPClientView.jsx +++ b/TestMasterUI/src/components/client-views/HTTPClientView.jsx @@ -3,7 +3,6 @@ import {Button} from '@/components/ui/button'; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select'; import {Input} from '@/components/ui/input'; import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/components/ui/tabs'; -import {Label} from '@/components/ui/label'; import EditableInput from '../shared/EditableInput'; import PropertyTable from '../shared/PropertyTable'; import ServerManager from '@/components/ServerManager.jsx'; @@ -27,6 +26,19 @@ export const HTTPClientView = ({model, index}) => { const [basePath, setBasePath] = useState(''); const [currentTab, setCurrentTab] = useState('body'); const [currentResponseTab, setCurrentResponseTab] = useState('response-body'); + + useEffect(() => { + setCurrentModel(prev => ({ + ...prev, + queryParams: model.queryParams || [], + headers: model.headers || [], + path: model.path || '', + requestBody: model.requestBody || '', + method: model.method || 'GET', + contentType: model.contentType || 'json' + })); + }, [model]); + const [responseData, setResponseData] = useState({ headers: [], status: '', @@ -52,16 +64,10 @@ export const HTTPClientView = ({model, index}) => { }, [currentModel.server]); const handleServerChange = (serverId) => { - // const selectedServer = getServer(serverId); - setCurrentModel(prev => ({ ...prev, server: serverId })); - - // if (selectedServer) { - // setBasePath(selectedServer.basePath || ''); - // } }; const handleHeadersChange = (headers) => { @@ -71,7 +77,16 @@ export const HTTPClientView = ({model, index}) => { })); }; - const handlePathChange = (path) => { + const handlePathChange = (e) => { + const path = e.target.value; + if (!path) { + setCurrentModel(prev => ({ + ...prev, + path: '', + queryParams: [] + })); + return; + } const queryParams = parseQueryParams(path); setCurrentModel(prev => ({ ...prev, @@ -80,40 +95,50 @@ export const HTTPClientView = ({model, index}) => { })); }; + + const buildUrlFromPathAndParams = (path, queryParams) => { + const baseUrl = path.split('?')[0]; + const enabledParams = queryParams.filter(p => p.enabled && p.key); + + if (!enabledParams.length) return baseUrl; + + const queryString = enabledParams + .map(p => `${p.key}=${p.value || ''}`) + .join('&'); + + return `${baseUrl}?${queryString}`; + }; + + const handleQueryParamsChange = (newParams) => { + const newPath = buildUrlFromPathAndParams(currentModel.path.split('?')[0], newParams); + setCurrentModel(prev => ({ + ...prev, + path: newPath, + queryParams: newParams + })); + }; + + const parseQueryParams = (path) => { const queryParams = []; - const queryString = path.split('?')[1]; + const [, queryString] = path.split('?'); if (queryString) { queryString.split('&').forEach(param => { - const [key, value] = param.split('='); - queryParams.push({ - enabled: true, - key, - value: value || '' - }); + const [key, value] = param.split('=').map(decodeURIComponent); + if (key) { + queryParams.push({ + enabled: true, + key, + value: value || '' + }); + } }); } return queryParams; }; - const renderRequestVariables = () => ( -
-

Available Variables

-
-
- -
Request object containing headers, body, etc.
-
-
- -
Current environment variables
-
-
-
- ); - const handleSendRequest = async () => { if (isLoading) { return; @@ -237,6 +262,7 @@ export const HTTPClientView = ({model, index}) => { value={currentModel.path} onChange={handlePathChange} className="w-full" + inputMode="text" />
@@ -298,15 +324,15 @@ export const HTTPClientView = ({model, index}) => { {/* Headers Tab Content */} diff --git a/TestMasterUI/src/components/shared/EditableInput.jsx b/TestMasterUI/src/components/shared/EditableInput.jsx index 37c36d9..29ade5e 100644 --- a/TestMasterUI/src/components/shared/EditableInput.jsx +++ b/TestMasterUI/src/components/shared/EditableInput.jsx @@ -106,10 +106,18 @@ const EditableInput = ({ return parts; }; + // useEffect(() => { + // if (value !== internalValue) { + // setInternalValue(value); + // } + // }, [value]); + const handleInput = (e) => { + console.log('handleInput', e); const newValue = e.target.textContent; const newSelection = saveSelection(); + console.log(newValue); setInternalValue(newValue); setSelection(newSelection); onChange(newValue); diff --git a/TestMasterUI/src/components/shared/PropertyTable.jsx b/TestMasterUI/src/components/shared/PropertyTable.jsx index dcc75b3..a3c1b99 100644 --- a/TestMasterUI/src/components/shared/PropertyTable.jsx +++ b/TestMasterUI/src/components/shared/PropertyTable.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, {useEffect, useState} from 'react'; import { Table, TableBody, @@ -20,6 +20,10 @@ const PropertyTable = ({ }) => { const [localProperties, setLocalProperties] = useState(properties); + useEffect(() => { + setLocalProperties(properties); + }, [properties]); + // Handle property changes without causing infinite loops const handlePropertyChange = (index, field, value) => { const updatedProperties = localProperties.map((prop, i) => { @@ -29,7 +33,9 @@ const PropertyTable = ({ return prop; }); setLocalProperties(updatedProperties); - onChange?.(updatedProperties); + requestAnimationFrame(() => { + onChange?.(updatedProperties); + }); }; // Add new property @@ -62,8 +68,8 @@ const PropertyTable = ({ Enable - Key - Value + Key + Value