import React, { useState, useRef, useCallback, useMemo } from 'react'; import { IconPlus, IconApi, IconBrandGraphql, IconPlugConnected, IconCode } from '@tabler/icons'; import ActionIcon from 'ui/ActionIcon/index'; import Dropdown from 'components/Dropdown'; import { newHttpRequest, newGrpcRequest, newWsRequest } from 'providers/ReduxStore/slices/collections/actions'; import { sanitizeName } from 'utils/common/regex'; import toast from 'react-hot-toast'; import { useDispatch, useSelector } from 'react-redux'; import { flattenItems, isItemARequest, isItemTransientRequest } from 'utils/collections'; import filter from 'lodash/filter'; import { get } from 'lodash'; import { formatIpcError } from 'utils/common/error'; const REQUEST_TYPE = { HTTP: 'http', GRAPHQL: 'graphql', GRPC: 'grpc', WEBSOCKET: 'websocket' }; /** * Generate a request name for transient requests in the pattern "Untitled {Count}" * @param {Object} collection - The collection object * @returns {string} A request name like "Untitled 1", "Untitled 2", etc. */ const generateTransientRequestName = (collection) => { if (!collection || !collection.items) { return 'Untitled 1'; } const allItems = flattenItems(collection.items); const transientRequests = filter(allItems, (item) => { return isItemTransientRequest(item); }); // Find the highest "Untitled X" number among transient requests let maxNumber = 0; transientRequests.forEach((item) => { const match = item.name?.match(/^Untitled (\d+)$/); if (match) { const number = parseInt(match[1], 10); if (number > maxNumber) { maxNumber = number; } } }); // Increment from the highest number found, or start at 1 if none found const count = maxNumber + 1; return `Untitled ${count}`; }; const CreateTransientRequest = ({ collectionUid }) => { const [dropdownVisible, setDropdownVisible] = useState(false); const dropdownTippyRef = useRef(); const dispatch = useDispatch(); const collections = useSelector((state) => state.collections.collections); const collection = useMemo(() => { return collections?.find((c) => c.uid === collectionUid); }, [collections, collectionUid]); const collectionPresets = useMemo(() => { return get(collection, collection?.draft?.brunoConfig ? 'draft.brunoConfig.presets' : 'brunoConfig.presets', { requestType: 'http', requestUrl: '' }); }, [collection]); const onDropdownCreate = (ref) => { dropdownTippyRef.current = ref; if (ref) { ref.setProps({ onHide: () => { setDropdownVisible(false); } }); } }; const handleLeftClick = () => { handleItemClick(collectionPresets.requestType); }; const handleRightClick = (e) => { e.preventDefault(); setDropdownVisible(true); }; const handleCreateHttpRequest = useCallback(() => { if (!collection) return; const uniqueName = generateTransientRequestName(collection); const filename = sanitizeName(uniqueName); dispatch( newHttpRequest({ requestName: uniqueName, filename: filename, requestType: 'http-request', requestUrl: collectionPresets.requestUrl, requestMethod: 'GET', collectionUid: collection.uid, itemUid: null, isTransient: true }) ).catch((err) => toast.error(formatIpcError(err) || 'An error occurred while adding the request')); }, [dispatch, collection, collectionPresets.requestUrl]); const handleCreateGraphQLRequest = useCallback(() => { if (!collection) return; const uniqueName = generateTransientRequestName(collection); const filename = sanitizeName(uniqueName); dispatch( newHttpRequest({ requestName: uniqueName, filename: filename, requestType: 'graphql-request', requestUrl: collectionPresets.requestUrl, requestMethod: 'POST', collectionUid: collection.uid, itemUid: null, isTransient: true, body: { mode: 'graphql', graphql: { query: '', variables: '' } } }) ).catch((err) => toast.error(formatIpcError(err) || 'An error occurred while adding the request')); }, [dispatch, collection, collectionPresets.requestUrl]); const handleCreateWebSocketRequest = useCallback(() => { if (!collection) return; const uniqueName = generateTransientRequestName(collection); const filename = sanitizeName(uniqueName); dispatch( newWsRequest({ requestName: uniqueName, filename: filename, requestUrl: collectionPresets.requestUrl, requestMethod: 'ws', collectionUid: collection.uid, itemUid: null, isTransient: true }) ).catch((err) => toast.error(formatIpcError(err) || 'An error occurred while adding the request')); }, [dispatch, collection, collectionPresets.requestUrl]); const handleCreateGrpcRequest = useCallback(() => { if (!collection) return; const uniqueName = generateTransientRequestName(collection); const filename = sanitizeName(uniqueName); dispatch( newGrpcRequest({ requestName: uniqueName, filename: filename, requestUrl: collectionPresets.requestUrl, collectionUid: collection.uid, itemUid: null, isTransient: true }) ).catch((err) => toast.error(formatIpcError(err) || 'An error occurred while adding the request')); }, [dispatch, collection, collectionPresets.requestUrl]); const handleItemClick = (type) => { if (dropdownTippyRef.current) { dropdownTippyRef.current.hide(); } switch (type) { case REQUEST_TYPE.HTTP: handleCreateHttpRequest(); break; case REQUEST_TYPE.GRAPHQL: handleCreateGraphQLRequest(); break; case REQUEST_TYPE.GRPC: handleCreateGrpcRequest(); break; case REQUEST_TYPE.WEBSOCKET: handleCreateWebSocketRequest(); break; } }; if (!collection) { return null; } const IconButton = ( ); return ( setDropdownVisible(false)} placement="bottom-end" >
handleItemClick(REQUEST_TYPE.HTTP)}>
HTTP
handleItemClick(REQUEST_TYPE.GRAPHQL)}>
GraphQL
handleItemClick(REQUEST_TYPE.GRPC)}>
gRPC
handleItemClick(REQUEST_TYPE.WEBSOCKET)}>
WebSocket
); }; export default CreateTransientRequest;