Skip to content

Commit

Permalink
Clear URL button, query params, request body
Browse files Browse the repository at this point in the history
  • Loading branch information
blonde-mike committed Dec 18, 2024
1 parent ad15ccf commit 80ef706
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { PewPewHeader, PewPewQueryParam } from "../../util/yamlwriter";
import { PewPewQueryParamStringType } from ".";
import React from "react";

export interface QueryParamsViewProps {
id: string;
queryParamList: PewPewQueryParam[];
removeParam: (param: PewPewQueryParam) => void;
changeParam: (paramIndex: number, type: PewPewQueryParamStringType, value: string) => void;
addParam: () => void;
}

function QueryParamsView ({ id, queryParamList, removeParam, changeParam, addParam }: QueryParamsViewProps): JSX.Element {
const styles: Record<string, React.CSSProperties> = {
headersDisplay: {
marginTop: "10px",
maxHeight: "300px",
overflow: "auto"
},
gridContainer: {
display: "grid",
gap: "10px"
},
gridHeader: {
display: "grid",
gridTemplateColumns: "auto 1fr 2fr",
gap: "10px",
fontWeight: "bold",
height: "20px"
},
gridRows: {
display: "grid",
gridTemplateColumns: "auto 1fr 2fr",
gap: "10px",
alignItems: "stretch"
},
input: {
boxSizing: "border-box"
},
button: {
boxSizing: "border-box",
whiteSpace: "nowrap"
}
};
return (
<React.Fragment>
<div style={styles.headersDisplay}>
<div style={styles.gridContainer}>
<div style={styles.gridHeader}>
<span><button name={id} onClick={() => addParam()}>+</button></span>
<span>Name</span>
<span>Value</span>
</div>
{queryParamList.length === 0 && <span>No Query Params yet, click "+" to create one</span>}
{queryParamList.map((param: PewPewHeader, index: number) => (
<div key={index} style={styles.gridRows}>
<button style={styles.button} onClick={() => removeParam(param)}>X</button>
<input style={styles.input} id={`urlHeaderKey@${index}`} name={id} value={param.name} onChange={(event) => changeParam(index, "name", event.target.value)} />
<input style={styles.input} id={`urlHeaderValue@${index}`} name={id} value={param.value} onChange={(event) => changeParam(index, "value", event.target.value)} />
</div>
))}
</div>
</div>
</React.Fragment>
);
}

export default QueryParamsView;
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from "react";

export interface RequestBodyViewProps {
requestBody: object | undefined;
updateRequestBody: (newRequestBody: object) => void;
}

function RequestBodyView ({ requestBody, updateRequestBody }: RequestBodyViewProps): JSX.Element {
const [requestBodyInEdit, setRequestBodyInEdit] = React.useState<string>(JSON.stringify(requestBody, null, 2).replace(/\\n/g, "\n").replace(/^"|"$/g, ""));

const requestBodyDisplayStyle: React.CSSProperties = {
fontFamily: "monospace",
width: "100%",
height: "270px",
overflow: "auto",
border: "1px solid #ccc",
padding: "10px",
backgroundColor: "#2e3438",
resize: "none",
tabSize: 2,
boxSizing: "border-box"
};

const handleEditorInput = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
const newRequestBody = event.target.value;
setRequestBodyInEdit(newRequestBody);
try {
updateRequestBody(JSON.parse(newRequestBody));
} catch (error) {
console.error(error);

Check warning on line 30 in guide/results-viewer-react/src/components/YamlUrls/RequestBodyView.tsx

View workflow job for this annotation

GitHub Actions / Build guide (20.x)

Unexpected console statement

Check warning on line 30 in guide/results-viewer-react/src/components/YamlUrls/RequestBodyView.tsx

View workflow job for this annotation

GitHub Actions / Build guide (22.x)

Unexpected console statement
}
};

return (
<React.Fragment>
<textarea
value={requestBodyInEdit}
onChange={handleEditorInput}
style={requestBodyDisplayStyle}
/>
</React.Fragment>
);
}

export default RequestBodyView;
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import HeadersView, { HeadersViewProps } from "./HeadersView";
import QueryParamsView, { QueryParamsViewProps } from "./QueryParamsView";
import RequestBodyView, { RequestBodyViewProps } from "./RequestBodyView";
import ResponseView, { ResponseViewProps } from "./ResponseView";
import React from "react";
import { TabType } from ".";

interface RequestDetailsTabsProps extends HeadersViewProps, ResponseViewProps {
interface RequestDetailsTabsProps extends HeadersViewProps, ResponseViewProps, QueryParamsViewProps, RequestBodyViewProps {
activeTab: TabType;
handleChangeTab: (tab: TabType) => void;
}

function RequestDetailsTabs ({ id, headersList, removeHeader, changeHeader, addHeader, response, error, activeTab, handleChangeTab }: RequestDetailsTabsProps): JSX.Element {
const tabs: TabType[] = ["Headers", "Response"];
function RequestDetailsTabs ({
id,
headersList,
removeHeader,
changeHeader,
addHeader,
queryParamList,
removeParam,
changeParam,
addParam,
response,
error,
activeTab,
handleChangeTab,
requestBody,
updateRequestBody
}: RequestDetailsTabsProps): JSX.Element {
const tabs: TabType[] = ["Headers", "Query Params", "Request Body", "Response"];

return (
<div>
Expand All @@ -33,6 +51,8 @@ interface RequestDetailsTabsProps extends HeadersViewProps, ResponseViewProps {
<div role="tabpanel" id={`tabpanel-${activeTab}`} aria-labelledby={`tab-${activeTab}`}>
{activeTab === "Headers" && <HeadersView id={id} headersList={headersList} removeHeader={removeHeader} changeHeader={changeHeader} addHeader={addHeader} />}
{activeTab === "Response" && <ResponseView response={response} error={error} />}
{activeTab === "Query Params" && <QueryParamsView id={id} queryParamList={queryParamList} removeParam={removeParam} changeParam={changeParam} addParam={addParam} />}
{activeTab === "Request Body" && <RequestBodyView requestBody={requestBody} updateRequestBody={updateRequestBody} />}
</div>
</div>
);
Expand Down
125 changes: 109 additions & 16 deletions guide/results-viewer-react/src/components/YamlUrls/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Div, Label, Span } from "../YamlStyles";
import { LogLevel, log } from "../../util/log";
import { Modal, ModalObject, useEffectModal } from "../Modal";
import { PewPewAPI, PewPewHeader } from "../../util/yamlwriter";
import { PewPewAPI, PewPewHeader, PewPewQueryParam } from "../../util/yamlwriter";
import React, { useEffect, useMemo, useRef, useState } from "react";
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, Method } from "axios";
import QuestionBubble from "../YamlQuestionBubble";
Expand Down Expand Up @@ -54,7 +54,8 @@ const CONTENT_TYPE = "contentType";
export type HeaderType = "defaultHeaders" | "authenticated" | "acceptLanguage" | "contentType";
export type PewPewApiStringType = "url" | "method" | "hitRate";
export type PewPewHeaderStringType = "name" | "value";
export type TabType = "Headers" | "Response"
export type PewPewQueryParamStringType = "name" | "value";
export type TabType = "Headers" | "Query Params" | "Request Body" | "Response";

export const newHeader = () => ({ id: uniqueId(), name: "", value: "" });
export const getAuthorizationHeader = (): PewPewHeader => ({ id: AUTHENTICATED, name: "Authorization", value: "Bearer ${sessionId}" });
Expand Down Expand Up @@ -96,13 +97,25 @@ export function isValidUrl (url: string): boolean {
}
}

export function Urls ({ data: { headers, ...data }, ...props }: UrlProps) {
export function Urls ({ data: { headers, requestBody, ...data }, ...props }: UrlProps) {
const defaultState: UrlState = {
passed: false
};
const [url, setUrl] = useState(data.url);
const [method, setMethod] = useState(data.method as Method);
const [headersList, setHeadersList] = useState<PewPewHeader[]>(headers);
const queryParamsFromUrl = useMemo<PewPewQueryParam[]>(() => {
const queryParamsText = data.url.split("?")[1];
if (queryParamsText) {
return queryParamsText.split("&").map((param) => {
const [name, value] = param.split("=");
return { id: uniqueId(), name, value };
});
}
return [];
}, [data.url]);
const [queryParamsList, setQueryParamsList] = useState<PewPewQueryParam[]>(queryParamsFromUrl);
const [requestBodyObject, setRequestBodyObject] = useState<object>(requestBody || {});
const [lastResponse, setLastResponse] = useState<AxiosResponse>();
const [errorMessage, setErrorMessage] = useState<string>();
const [activeTab, setActiveTab] = useState<TabType>("Headers");
Expand Down Expand Up @@ -153,8 +166,8 @@ export function Urls ({ data: { headers, ...data }, ...props }: UrlProps) {
headers: headersList.reduce((acc, header) => {
acc[header.name] = header.value;
return acc;
}, {} as Record<string, string>)
// validateStatus: () => true
}, {} as Record<string, string>),
data: requestBodyObject
};
try {
const response = await axios(request);
Expand All @@ -177,6 +190,29 @@ export function Urls ({ data: { headers, ...data }, ...props }: UrlProps) {
}
}

const changeUrlObject = (type: PewPewApiStringType, value: string) => {
data[type] = value;
props.changeUrl({ ...data, headers });
};

const changeUrl = (event: React.ChangeEvent<HTMLInputElement>) => {
const urlText = event.target.value;
const queryParamsText = urlText.split("?")[1];
if (queryParamsText) {
const queries = queryParamsText.split("&").map((param) => {
const [name, value] = param.split("=");
return { id: uniqueId(), name, value };
});
setQueryParamsList(queries);
}
setUrl(urlText);
};

const clearUrl = () => {
setUrl("");
setQueryParamsList([]);
};

// Adds header to array in given url
// Url found by id
const addHeader = (headerType?: HeaderType) => {
Expand Down Expand Up @@ -204,11 +240,6 @@ export function Urls ({ data: { headers, ...data }, ...props }: UrlProps) {
}
};

const changeUrl = (type: PewPewApiStringType, value: string) => {
data[type] = value;
props.changeUrl({ ...data, headers });
};

const changeHeader = (headerIndex: number, type: PewPewHeaderStringType, value: string) => {
setHeadersList((prevHeadersList) => {
const newHeadersList = [...prevHeadersList];
Expand All @@ -217,13 +248,58 @@ export function Urls ({ data: { headers, ...data }, ...props }: UrlProps) {
});
};

const addQueryParam = () => {
setQueryParamsList((prevQueryParamsList) => prevQueryParamsList.concat({ id: uniqueId(), name: "", value: "" }));
};

const removeQueryParam = (param: PewPewQueryParam) => {
setUrl((prevUrl) => prevUrl.replace(`&${param.name}=${param.value}`, ""));
setQueryParamsList((prevQueryParamsList) => prevQueryParamsList.filter((p) => p.id !== param.id));
};

const changeQueryParam = (paramIndex: number, type: PewPewQueryParamStringType, value: string) => {
const paramToReplace = `&${queryParamsList[paramIndex].name}=${queryParamsList[paramIndex].value}`;
if (type === "name") {
const replacementParam = `&${value}=${queryParamsList[paramIndex].value}`;
setUrl((prevUrl) => {
if (!prevUrl.includes(paramToReplace)) {return `${prevUrl}${!prevUrl.includes("?") ? "?" : "" }${replacementParam}`;}
else {return prevUrl.replace(paramToReplace, replacementParam);}
});
} else if (type === "value") {
const replacementParam = `&${queryParamsList[paramIndex].name}=${value}`;
setUrl((prevUrl) => {
if (!prevUrl.includes(paramToReplace)) {return `${prevUrl}${!prevUrl.includes("?") ? "?" : "" }${replacementParam}`;}
else {return prevUrl.replace(paramToReplace, replacementParam);}
});
}
setQueryParamsList((prevQueryParamsList) => {
const newQueryParamsList = [...prevQueryParamsList];
newQueryParamsList[paramIndex][type] = value;
return newQueryParamsList;
});
};

const updateRequestBody = (newRequestBody: object) => {
setRequestBodyObject(newRequestBody);
};

const updateEndpointHandler = () => {
changeUrl("method", method);
changeUrl("url", url);
changeUrlObject("method", method);
changeUrlObject("url", url);
props.changeUrl({ ...data, headers: headersList });
return Promise.resolve();
};

const resetModalState = () => {
setUrl(data.url);
setMethod(data.method as Method);
setHeadersList(headers);
setQueryParamsList(queryParamsFromUrl);
setLastResponse(undefined);
setErrorMessage(undefined);
setActiveTab("Headers");
};

const handleChangeTab = (tab: TabType) => setActiveTab(tab);

const checked = state.passed ? " Passed" : "";
Expand All @@ -244,6 +320,7 @@ export function Urls ({ data: { headers, ...data }, ...props }: UrlProps) {
closeText="Close"
submitText="Update Endpoint"
onSubmit={updateEndpointHandler}
onClose={resetModalState}
isReady={enableSubmit}
scrollable={false}
initialDisplay={true}
Expand All @@ -267,10 +344,10 @@ export function Urls ({ data: { headers, ...data }, ...props }: UrlProps) {
<p style={{ fontSize: "10px", margin: 0 }}> (must be in the form "https://www.[url]" or "http://www.[url]")</p>
</Row>
<Row style={{ position: "relative"}}>
<input style={{ ...urlStyle, width: "100%" }} onChange={(event) => setUrl(event.target.value)} title={urlTitle} name={data.id} value={url} id="urlUrl" type="text" />
<input style={{ ...urlStyle, width: "100%" }} onChange={changeUrl} title={urlTitle} name={data.id} value={url} id="urlUrl" type="text" />
{url && (
<button
onClick={() => setUrl("")}
onClick={clearUrl}
style={{
position: "absolute",
right: "45px",
Expand All @@ -291,11 +368,27 @@ export function Urls ({ data: { headers, ...data }, ...props }: UrlProps) {
<Label htmlFor="urlHitrate" style={{ fontSize: "14px" }}> Hit Rate </Label>
<QuestionBubble text="Required | Number, then hph, hpm, or hps"></QuestionBubble>
</Row>
<input style={{ ...getHitRateStyle(invalidHitRate), width: "75px" }} onChange={(event) => changeUrl("hitRate", event.target.value)} name={data.id} value={data.hitRate} id="urlHitrate" title={getHitRateTitle(invalidHitRate)} />
<input style={{ ...getHitRateStyle(invalidHitRate), width: "75px" }} onChange={(event) => changeUrlObject("hitRate", event.target.value)} name={data.id} value={data.hitRate} id="urlHitrate" title={getHitRateTitle(invalidHitRate)} />
</ModalInput>
</Span>
</Row>
<RequestDetailsTabs id={data.id} headersList={headersList} removeHeader={removeHeader} changeHeader={changeHeader} addHeader={addHeader} response={lastResponse} error={errorMessage} activeTab={activeTab} handleChangeTab={handleChangeTab} />
<RequestDetailsTabs
id={data.id}
headersList={headersList}
removeHeader={removeHeader}
changeHeader={changeHeader}
addHeader={addHeader}
response={lastResponse}
error={errorMessage}
activeTab={activeTab}
handleChangeTab={handleChangeTab}
queryParamList={queryParamsList}
removeParam={removeQueryParam}
changeParam={changeQueryParam}
addParam={addQueryParam}
requestBody={requestBodyObject}
updateRequestBody={updateRequestBody}
/>
</Modal>
</Div>
);
Expand Down
Loading

0 comments on commit 80ef706

Please sign in to comment.