import { Alert, Box, BoxProps, Button, InputLabel, FormControl, MenuItem, Select, TextField, Typography, } from "@mui/material"; import { AllotmentProps, PaneProps } from "allotment/dist/types/src/allotment"; import { useRouter } from "next/router"; import React, { useEffect, useRef, useState } from "react"; import EditRequestTabs from "./EditRequestTabs"; import { KeyValuePair, sortKeyValuePairs } from "./KeyValuePair"; import Response from "./Response"; import { GetSenderRequestQuery, useCreateOrUpdateSenderRequestMutation, HttpProtocol, useGetSenderRequestQuery, useSendRequestMutation, } from "lib/graphql/generated"; import "allotment/dist/style.css"; enum HttpMethod { Get = "GET", Post = "POST", Put = "PUT", Patch = "PATCH", Delete = "DELETE", Head = "HEAD", Options = "OPTIONS", Connect = "CONNECT", Trace = "TRACE", } enum HttpProto { Http1 = "HTTP/1.1", Http2 = "HTTP/2.0", } const httpProtoMap = new Map([ [HttpProto.Http1, HttpProtocol.Http1], [HttpProto.Http2, HttpProtocol.Http2], ]); function updateKeyPairItem(key: string, value: string, idx: number, items: KeyValuePair[]): KeyValuePair[] { const updated = [...items]; updated[idx] = { key, value }; // Append an empty key-value pair if the last item in the array isn't blank // anymore. if (items.length - 1 === idx && items[idx].key === "" && items[idx].value === "") { updated.push({ key: "", value: "" }); } return updated; } function updateURLQueryParams(url: string, queryParams: KeyValuePair[]) { // Note: We don't use the `URL` interface, because we're potentially dealing // with malformed/incorrect URLs, which would yield TypeErrors when constructed // via `URL`. let newURL = url; const questionMarkIndex = url.indexOf("?"); if (questionMarkIndex !== -1) { newURL = newURL.slice(0, questionMarkIndex); } const searchParams = new URLSearchParams(); for (const { key, value } of queryParams.filter(({ key }) => key !== "")) { searchParams.append(key, value); } const rawQueryParams = decodeURI(searchParams.toString()); if (rawQueryParams == "") { return newURL; } return newURL + "?" + rawQueryParams; } function queryParamsFromURL(url: string): KeyValuePair[] { const questionMarkIndex = url.indexOf("?"); if (questionMarkIndex === -1) { return []; } const queryParams: KeyValuePair[] = []; const searchParams = new URLSearchParams(url.slice(questionMarkIndex + 1)); for (const [key, value] of searchParams) { queryParams.push({ key, value }); } return queryParams; } function EditRequest(): JSX.Element { const router = useRouter(); const reqId = router.query.id as string | undefined; const [method, setMethod] = useState(HttpMethod.Get); const [url, setURL] = useState(""); const [proto, setProto] = useState(HttpProto.Http2); const [queryParams, setQueryParams] = useState([{ key: "", value: "" }]); const [headers, setHeaders] = useState([{ key: "", value: "" }]); const [body, setBody] = useState(""); const handleQueryParamChange = (key: string, value: string, idx: number) => { setQueryParams((prev) => { const updated = updateKeyPairItem(key, value, idx, prev); setURL((prev) => updateURLQueryParams(prev, updated)); return updated; }); }; const handleQueryParamDelete = (idx: number) => { setQueryParams((prev) => { const updated = prev.slice(0, idx).concat(prev.slice(idx + 1, prev.length)); setURL((prev) => updateURLQueryParams(prev, updated)); return updated; }); }; const handleHeaderChange = (key: string, value: string, idx: number) => { setHeaders((prev) => updateKeyPairItem(key, value, idx, prev)); }; const handleHeaderDelete = (idx: number) => { setHeaders((prev) => prev.slice(0, idx).concat(prev.slice(idx + 1, prev.length))); }; const handleURLChange = (url: string) => { setURL(url); const questionMarkIndex = url.indexOf("?"); if (questionMarkIndex === -1) { setQueryParams([{ key: "", value: "" }]); return; } const newQueryParams = queryParamsFromURL(url); // Push empty row. newQueryParams.push({ key: "", value: "" }); setQueryParams(newQueryParams); }; const [response, setResponse] = useState["response"]>(null); const getReqResult = useGetSenderRequestQuery({ variables: { id: reqId as string }, skip: reqId === undefined, onCompleted: ({ senderRequest }) => { if (!senderRequest) { return; } setURL(senderRequest.url); setMethod(senderRequest.method); setBody(senderRequest.body || ""); const newQueryParams = queryParamsFromURL(senderRequest.url); // Push empty row. newQueryParams.push({ key: "", value: "" }); setQueryParams(newQueryParams); const newHeaders = sortKeyValuePairs(senderRequest.headers || []); setHeaders([...newHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]); console.log(senderRequest.response); setResponse(senderRequest.response); }, }); const [createOrUpdateRequest, createResult] = useCreateOrUpdateSenderRequestMutation(); const [sendRequest, sendResult] = useSendRequestMutation(); const createOrUpdateRequestAndSend = () => { const senderReq = getReqResult?.data?.senderRequest; createOrUpdateRequest({ variables: { request: { // Update existing sender request if it was cloned from a request log // and it doesn't have a response body yet (e.g. not sent yet). ...(senderReq && senderReq.sourceRequestLogID && !senderReq.response && { id: senderReq.id }), url, method, proto: httpProtoMap.get(proto), headers: headers.filter((kv) => kv.key !== ""), body: body || undefined, }, }, onCompleted: ({ createOrUpdateSenderRequest }) => { const { id } = createOrUpdateSenderRequest; sendRequestAndPushRoute(id); }, }); }; const sendRequestAndPushRoute = (id: string) => { sendRequest({ errorPolicy: "all", onCompleted: () => { router.push(`/sender?id=${id}`); }, variables: { id, }, }); }; const handleFormSubmit: React.FormEventHandler = (e) => { e.preventDefault(); createOrUpdateRequestAndSend(); }; const isMountedRef = useRef(false); const [Allotment, setAllotment] = useState< (React.ComponentType & { Pane: React.ComponentType }) | null >(null); useEffect(() => { isMountedRef.current = true; import("allotment") .then((mod) => { if (!isMountedRef.current) { return; } setAllotment(mod.Allotment); }) .catch((err) => console.error(err, `could not import allotment ${err.message}`)); return () => { isMountedRef.current = false; }; }, []); if (!Allotment) { return
Loading...
; } return ( {createResult.error && ( {createResult.error.message} )} {sendResult.error && ( {sendResult.error.message} )} Request ); } interface UrlBarProps extends BoxProps { method: HttpMethod; onMethodChange: (method: HttpMethod) => void; url: string; onUrlChange: (url: string) => void; proto: HttpProto; onProtoChange: (proto: HttpProto) => void; } function UrlBar(props: UrlBarProps) { const { method, onMethodChange, url, onUrlChange, proto, onProtoChange, ...other } = props; return ( Method onUrlChange(e.target.value)} required variant="outlined" InputLabelProps={{ shrink: true, }} InputProps={{ sx: { ".MuiOutlinedInput-notchedOutline": { borderRadius: 0, }, }, }} sx={{ flexGrow: 1 }} /> Protocol ); } export default EditRequest;