docs: improve API testing feature (#7311)
This commit is contained in:
1 parent
5b26f5f2cf
commit
70c4ffff8b
10 files changed
+368
-68
No files matched your search
@@ -0,0 +1,98 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react"
|
||||||
|
import { ApiRunnerParamInput, ApiRunnerParamInputProps } from "../Default"
|
||||||
|
import clsx from "clsx"
|
||||||
|
import setObjValue from "@/utils/set-obj-value"
|
||||||
|
import { Button } from "../../../.."
|
||||||
|
import { Minus, Plus } from "@medusajs/icons"
|
||||||
|
|
||||||
|
export const ApiRunnerParamArrayInput = ({
|
||||||
|
paramName,
|
||||||
|
paramValue,
|
||||||
|
objPath,
|
||||||
|
setValue,
|
||||||
|
}: ApiRunnerParamInputProps) => {
|
||||||
|
const [itemsValue, setItemsValue] = useState<typeof paramValue>(paramValue)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue((prev: unknown) => {
|
||||||
|
return typeof prev === "object"
|
||||||
|
? setObjValue({
|
||||||
|
obj: { ...prev },
|
||||||
|
value: itemsValue,
|
||||||
|
path: `${objPath.length ? `${objPath}.` : ""}${paramName}`,
|
||||||
|
})
|
||||||
|
: itemsValue
|
||||||
|
})
|
||||||
|
}, [itemsValue])
|
||||||
|
|
||||||
|
if (!Array.isArray(paramValue)) {
|
||||||
|
return (
|
||||||
|
<ApiRunnerParamInput
|
||||||
|
paramName={paramName}
|
||||||
|
paramValue={paramValue}
|
||||||
|
objPath={objPath}
|
||||||
|
setValue={setValue}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<fieldset
|
||||||
|
className={clsx(
|
||||||
|
"border border-medusa-border-strong rounded",
|
||||||
|
"p-docs_0.5"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<legend className="px-docs_0.5">
|
||||||
|
<code>{paramName}</code> Array Items
|
||||||
|
</legend>
|
||||||
|
{(itemsValue as unknown[]).map((value, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={clsx(
|
||||||
|
index > 0 &&
|
||||||
|
"flex gap-docs_0.5 items-center justify-center mt-docs_0.5"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ApiRunnerParamInput
|
||||||
|
paramName={`[${index}]`}
|
||||||
|
paramValue={value}
|
||||||
|
objPath={""}
|
||||||
|
setValue={setItemsValue}
|
||||||
|
/>
|
||||||
|
{index > 0 && (
|
||||||
|
<Button
|
||||||
|
buttonType="icon"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
setItemsValue((prev: unknown[]) => prev.splice(index, 1))
|
||||||
|
}}
|
||||||
|
className="mt-0.5"
|
||||||
|
>
|
||||||
|
<Minus />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
buttonType="icon"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
setItemsValue((prev: unknown[]) => [
|
||||||
|
...prev,
|
||||||
|
Array.isArray(prev[0])
|
||||||
|
? [...prev[0]]
|
||||||
|
: typeof prev[0] === "object"
|
||||||
|
? Object.assign({}, prev[0])
|
||||||
|
: prev[0],
|
||||||
|
])
|
||||||
|
}}
|
||||||
|
className="mt-0.5"
|
||||||
|
>
|
||||||
|
<Plus />
|
||||||
|
</Button>
|
||||||
|
</fieldset>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { InputText } from "../../../.."
|
||||||
|
import setObjValue from "../../../../utils/set-obj-value"
|
||||||
|
import { ApiRunnerParamObjectInput } from "../Object"
|
||||||
|
import { ApiRunnerParamArrayInput } from "../Array"
|
||||||
|
|
||||||
|
export type ApiRunnerParamInputProps = {
|
||||||
|
paramName: string
|
||||||
|
paramValue: unknown
|
||||||
|
objPath: string
|
||||||
|
setValue: React.Dispatch<React.SetStateAction<unknown>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ApiRunnerParamInput = ({
|
||||||
|
paramName,
|
||||||
|
paramValue,
|
||||||
|
objPath,
|
||||||
|
setValue,
|
||||||
|
}: ApiRunnerParamInputProps) => {
|
||||||
|
if (Array.isArray(paramValue)) {
|
||||||
|
return (
|
||||||
|
<ApiRunnerParamArrayInput
|
||||||
|
paramName={paramName}
|
||||||
|
paramValue={paramValue}
|
||||||
|
objPath={objPath}
|
||||||
|
setValue={setValue}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (typeof paramValue === "object") {
|
||||||
|
return (
|
||||||
|
<ApiRunnerParamObjectInput
|
||||||
|
paramName={paramName}
|
||||||
|
paramValue={paramValue}
|
||||||
|
objPath={objPath}
|
||||||
|
setValue={setValue}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputText
|
||||||
|
name={paramName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue((prev: unknown) => {
|
||||||
|
if (Array.isArray(prev)) {
|
||||||
|
// try to get index from param name
|
||||||
|
const splitPath = objPath.split(".")
|
||||||
|
// if param is in an object in the array, the index is
|
||||||
|
// the last item of the `objPath`. Otherwise, it's in the param name
|
||||||
|
const index = (
|
||||||
|
objPath.length > 0 ? splitPath[splitPath.length - 1] : paramName
|
||||||
|
)
|
||||||
|
.replace("[", "")
|
||||||
|
.replace("]", "")
|
||||||
|
const intIndex = parseInt(index)
|
||||||
|
|
||||||
|
// if we can't get the index from the param name or obj path
|
||||||
|
// just insert the value to the end of the array.
|
||||||
|
if (Number.isNaN(intIndex)) {
|
||||||
|
return [...prev, e.target.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the param is within an object, the value to be set
|
||||||
|
// is the updated value of the object. Otherwise, it's just the
|
||||||
|
// value of the item.
|
||||||
|
const transformedValue =
|
||||||
|
prev.length > 0 && typeof prev[0] === "object"
|
||||||
|
? setObjValue({
|
||||||
|
obj: { ...prev[intIndex] },
|
||||||
|
value: e.target.value,
|
||||||
|
path: paramName,
|
||||||
|
})
|
||||||
|
: e.target.value
|
||||||
|
|
||||||
|
return [
|
||||||
|
...prev.slice(0, intIndex),
|
||||||
|
transformedValue,
|
||||||
|
...prev.slice(intIndex + 1),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof prev === "object"
|
||||||
|
? setObjValue({
|
||||||
|
obj: { ...prev },
|
||||||
|
value: e.target.value,
|
||||||
|
path: `${objPath.length ? `${objPath}.` : ""}${paramName}`,
|
||||||
|
})
|
||||||
|
: e.target.value
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
placeholder={paramName}
|
||||||
|
value={
|
||||||
|
typeof paramValue === "string"
|
||||||
|
? (paramValue as string)
|
||||||
|
: typeof paramValue === "number"
|
||||||
|
? (paramValue as number)
|
||||||
|
: `${paramValue}`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { ApiRunnerParamInput, ApiRunnerParamInputProps } from "../Default"
|
||||||
|
import clsx from "clsx"
|
||||||
|
|
||||||
|
export const ApiRunnerParamObjectInput = ({
|
||||||
|
paramName,
|
||||||
|
paramValue,
|
||||||
|
objPath,
|
||||||
|
...props
|
||||||
|
}: ApiRunnerParamInputProps) => {
|
||||||
|
if (typeof paramValue !== "object") {
|
||||||
|
return (
|
||||||
|
<ApiRunnerParamInput
|
||||||
|
paramName={paramName}
|
||||||
|
paramValue={paramValue}
|
||||||
|
objPath={objPath}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<fieldset
|
||||||
|
className={clsx(
|
||||||
|
"border border-medusa-border-strong rounded",
|
||||||
|
"p-docs_0.5"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<legend className="px-docs_0.5">
|
||||||
|
<code>{paramName}</code> Properties
|
||||||
|
</legend>
|
||||||
|
{Object.entries(paramValue as Record<string, unknown>).map(
|
||||||
|
([key, value], index) => (
|
||||||
|
<ApiRunnerParamInput
|
||||||
|
paramName={key}
|
||||||
|
paramValue={value}
|
||||||
|
objPath={`${objPath.length ? `${objPath}.` : ""}${paramName}`}
|
||||||
|
key={index}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</fieldset>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { ApiRunnerParamInput } from "./Default"
|
||||||
|
|
||||||
|
export type ApiRunnerParamInputsProps = {
|
||||||
|
data: Record<string, unknown>
|
||||||
|
title: string
|
||||||
|
baseObjPath: string
|
||||||
|
setValue: React.Dispatch<React.SetStateAction<unknown>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ApiRunnerParamInputs = ({
|
||||||
|
data,
|
||||||
|
title,
|
||||||
|
baseObjPath,
|
||||||
|
setValue,
|
||||||
|
}: ApiRunnerParamInputsProps) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-docs_0.5">
|
||||||
|
<span className="text-compact-medium-plus text-medusa-fg-base">
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-docs_0.5">
|
||||||
|
{Object.keys(data).map((pathParam, index) => (
|
||||||
|
<ApiRunnerParamInput
|
||||||
|
paramName={pathParam}
|
||||||
|
paramValue={data[pathParam]}
|
||||||
|
objPath={baseObjPath}
|
||||||
|
setValue={setValue}
|
||||||
|
key={index}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+42
-64
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
import { useRequestRunner } from "../../../hooks"
|
import { useRequestRunner } from "../../hooks"
|
||||||
import { CodeBlock } from ".."
|
import { CodeBlock } from "../CodeBlock"
|
||||||
import { Card } from "../../Card"
|
import { Card } from "../Card"
|
||||||
import { Button, InputText } from "../../.."
|
import { Button } from "../.."
|
||||||
import { ApiMethod, ApiDataOptions, ApiTestingOptions } from "types"
|
import { ApiMethod, ApiTestingOptions } from "types"
|
||||||
|
import { ApiRunnerParamInputs } from "./ParamInputs"
|
||||||
|
|
||||||
type ApiRunnerProps = {
|
type ApiRunnerProps = {
|
||||||
apiMethod: ApiMethod
|
apiMethod: ApiMethod
|
||||||
@@ -72,69 +73,46 @@ export const ApiRunner = ({
|
|||||||
}
|
}
|
||||||
}, [isRunning, ran])
|
}, [isRunning, ran])
|
||||||
|
|
||||||
const getParamsElms = ({
|
|
||||||
data,
|
|
||||||
title,
|
|
||||||
nameInApiOptions,
|
|
||||||
}: {
|
|
||||||
data: ApiDataOptions
|
|
||||||
title: string
|
|
||||||
nameInApiOptions: "pathData" | "bodyData" | "queryData"
|
|
||||||
}) => (
|
|
||||||
<div className="flex flex-col gap-docs_0.5">
|
|
||||||
<span className="text-compact-medium-plus text-medusa-fg-base">
|
|
||||||
{title}
|
|
||||||
</span>
|
|
||||||
<div className="flex gap-docs_0.5">
|
|
||||||
{Object.keys(data).map((pathParam, index) => (
|
|
||||||
<InputText
|
|
||||||
name={pathParam}
|
|
||||||
onChange={(e) =>
|
|
||||||
setApiTestingOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[nameInApiOptions]: {
|
|
||||||
...prev[nameInApiOptions],
|
|
||||||
[pathParam]: e.target.value,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
key={index}
|
|
||||||
placeholder={pathParam}
|
|
||||||
value={
|
|
||||||
typeof data[pathParam] === "string"
|
|
||||||
? (data[pathParam] as string)
|
|
||||||
: typeof data[pathParam] === "number"
|
|
||||||
? (data[pathParam] as number)
|
|
||||||
: `${data[pathParam]}`
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{manualTestTrigger && (
|
{manualTestTrigger && (
|
||||||
<Card className="font-base mb-docs_1" contentClassName="gap-docs_0.5">
|
<Card className="font-base mb-docs_1" contentClassName="gap-docs_0.5">
|
||||||
{apiTestingOptions.pathData &&
|
{apiTestingOptions.pathData && (
|
||||||
getParamsElms({
|
<ApiRunnerParamInputs
|
||||||
data: apiTestingOptions.pathData,
|
data={apiTestingOptions.pathData}
|
||||||
title: "Path Parameters",
|
title="Path Parameters"
|
||||||
nameInApiOptions: "pathData",
|
baseObjPath="pathData"
|
||||||
})}
|
setValue={
|
||||||
{apiTestingOptions.bodyData &&
|
setApiTestingOptions as React.Dispatch<
|
||||||
getParamsElms({
|
React.SetStateAction<unknown>
|
||||||
data: apiTestingOptions.bodyData,
|
>
|
||||||
title: "Request Body Parameters",
|
}
|
||||||
nameInApiOptions: "bodyData",
|
/>
|
||||||
})}
|
)}
|
||||||
{apiTestingOptions.queryData &&
|
{apiTestingOptions.bodyData && (
|
||||||
getParamsElms({
|
<ApiRunnerParamInputs
|
||||||
data: apiTestingOptions.queryData,
|
data={apiTestingOptions.bodyData}
|
||||||
title: "Request Query Parameters",
|
title="Request Body Parameters"
|
||||||
nameInApiOptions: "queryData",
|
baseObjPath="bodyData"
|
||||||
})}
|
setValue={
|
||||||
|
setApiTestingOptions as React.Dispatch<
|
||||||
|
React.SetStateAction<unknown>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{apiTestingOptions.queryData && (
|
||||||
|
<ApiRunnerParamInputs
|
||||||
|
data={apiTestingOptions.queryData}
|
||||||
|
title="Request Query Parameters"
|
||||||
|
baseObjPath="queryData"
|
||||||
|
setValue={
|
||||||
|
setApiTestingOptions as React.Dispatch<
|
||||||
|
React.SetStateAction<unknown>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsRunning(true)
|
setIsRunning(true)
|
||||||
@@ -3,14 +3,13 @@
|
|||||||
import React, { useMemo, useState } from "react"
|
import React, { useMemo, useState } from "react"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { HighlightProps, Highlight, themes } from "prism-react-renderer"
|
import { HighlightProps, Highlight, themes } from "prism-react-renderer"
|
||||||
import { CopyButton, Tooltip, Link } from "@/components"
|
import { ApiRunner, CopyButton, Tooltip, Link } from "@/components"
|
||||||
import { useColorMode } from "@/providers"
|
import { useColorMode } from "@/providers"
|
||||||
import { ExclamationCircle, PlaySolid, SquareTwoStack } from "@medusajs/icons"
|
import { ExclamationCircle, PlaySolid, SquareTwoStack } from "@medusajs/icons"
|
||||||
import { CodeBlockHeader, CodeBlockHeaderMeta } from "./Header"
|
import { CodeBlockHeader, CodeBlockHeaderMeta } from "./Header"
|
||||||
import { CodeBlockLine } from "./Line"
|
import { CodeBlockLine } from "./Line"
|
||||||
import { ApiAuthType, ApiDataOptions, ApiMethod } from "types"
|
import { ApiAuthType, ApiDataOptions, ApiMethod } from "types"
|
||||||
import { CSSTransition } from "react-transition-group"
|
import { CSSTransition } from "react-transition-group"
|
||||||
import { ApiRunner } from "./ApiRunner"
|
|
||||||
import { GITHUB_ISSUES_PREFIX } from "../.."
|
import { GITHUB_ISSUES_PREFIX } from "../.."
|
||||||
|
|
||||||
export type Highlight = {
|
export type Highlight = {
|
||||||
@@ -69,7 +68,10 @@ export const CodeBlock = ({
|
|||||||
const { colorMode } = useColorMode()
|
const { colorMode } = useColorMode()
|
||||||
const [showTesting, setShowTesting] = useState(false)
|
const [showTesting, setShowTesting] = useState(false)
|
||||||
const canShowApiTesting = useMemo(
|
const canShowApiTesting = useMemo(
|
||||||
() => apiTesting && rest.testApiMethod && rest.testApiUrl,
|
() =>
|
||||||
|
apiTesting !== undefined &&
|
||||||
|
rest.testApiMethod !== undefined &&
|
||||||
|
rest.testApiUrl !== undefined,
|
||||||
[apiTesting, rest]
|
[apiTesting, rest]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from "./AiAssistant"
|
export * from "./AiAssistant"
|
||||||
export * from "./AiAssistant/CommandIcon"
|
export * from "./AiAssistant/CommandIcon"
|
||||||
|
export * from "./ApiRunner"
|
||||||
export * from "./Badge"
|
export * from "./Badge"
|
||||||
export * from "./Bannerv2"
|
export * from "./Bannerv2"
|
||||||
export * from "./Bordered"
|
export * from "./Bordered"
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ export * from "./get-scrolled-top"
|
|||||||
export * from "./is-elm-window"
|
export * from "./is-elm-window"
|
||||||
export * from "./is-in-view"
|
export * from "./is-in-view"
|
||||||
export * from "./learning-paths"
|
export * from "./learning-paths"
|
||||||
|
export * from "./set-obj-value"
|
||||||
export * from "./sidebar-attach-href-common-options"
|
export * from "./sidebar-attach-href-common-options"
|
||||||
export * from "./swr-fetcher"
|
export * from "./swr-fetcher"
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
type Params = {
|
||||||
|
obj: Record<string, unknown>
|
||||||
|
value: unknown
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function setObjValue({
|
||||||
|
obj,
|
||||||
|
value,
|
||||||
|
path,
|
||||||
|
}: Params): Record<string, unknown> {
|
||||||
|
// split path by delimiter
|
||||||
|
const splitPath = path.split(".")
|
||||||
|
const targetKey = splitPath[0]
|
||||||
|
|
||||||
|
if (!Object.hasOwn(obj, targetKey)) {
|
||||||
|
obj[targetKey] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (splitPath.length === 1) {
|
||||||
|
obj[targetKey] = value
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof obj[targetKey] !== "object") {
|
||||||
|
throw new Error(
|
||||||
|
`value of ${targetKey} is not an object, so can't set nested value`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setObjValue({
|
||||||
|
obj: obj[targetKey] as Record<string, unknown>,
|
||||||
|
value,
|
||||||
|
path: splitPath.slice(1).join("."),
|
||||||
|
})
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"tsBuildInfoFile": "./dist/.tsbuildinfo-client",
|
"tsBuildInfoFile": "./dist/.tsbuildinfo-client",
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"lib": ["dom", "ES2015", "es2021"],
|
"lib": ["dom", "ES2015", "es2022"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
|||||||
Reference in new issue
Block a user