Skip to content

Commit

Permalink
fix(dash): refactor ux for adding/removing headers and query params (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
davemooreuws authored Feb 18, 2025
1 parent be60100 commit cf2c048
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 118 deletions.
4 changes: 4 additions & 0 deletions pkg/dashboard/frontend/cypress/e2e/api-explorer.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ describe('APIs spec', () => {
cy.getTestEl('query-0-key').type('firstParam')
cy.getTestEl('query-0-value').type('myValue')

cy.getTestEl('add-row-btn').click()

cy.getTestEl('query-1-key').type('secondParam')
cy.getTestEl('query-1-value').type('mySecondValue')

Expand Down Expand Up @@ -149,6 +151,8 @@ describe('APIs spec', () => {
cy.getTestEl('header-2-key').clear().type('X-First-Header')
cy.getTestEl('header-2-value').clear().type('the value')

cy.getTestEl('add-row-btn').click()

cy.getTestEl('header-3-key').clear().type('X-Second-Header')
cy.getTestEl('header-3-value').clear().type('the second value')

Expand Down
2 changes: 2 additions & 0 deletions pkg/dashboard/frontend/cypress/e2e/websockets.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ describe('Websockets Spec', () => {
cy.getTestEl('query-0-key').type('firstParam')
cy.getTestEl('query-0-value').type('myValue')

cy.getTestEl('add-row-btn').click()

cy.getTestEl('query-1-key').type('secondParam')
cy.getTestEl('query-1-value').type('mySecondValue')

Expand Down
22 changes: 15 additions & 7 deletions pkg/dashboard/frontend/src/components/apis/APIExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ const requestDefault = {
key: 'User-Agent',
value: 'Nitric Client (https://www.nitric.io)',
},
{
key: 'Content-Type',
value: 'application/json',
},
],
}

Expand Down Expand Up @@ -318,17 +322,19 @@ const APIExplorer = () => {
const url = `http://${getHost()}/api/call` + path

// Set a default content type if not set
if (!headers.find(({ key }) => key.toLowerCase() === 'content-type')) {
headers.push({
key: 'Content-Type',
value: currentBodyTab.contentType,
})
}
const shouldAddContentType = !headers.find(
({ key }) => key.toLowerCase() === 'content-type',
)

const requestOptions: RequestInit = {
method,
headers: fieldRowArrToHeaders([
...headers,
...(shouldAddContentType
? [
...headers,
{ key: 'Content-Type', value: currentBodyTab.contentType },
]
: headers),
{
key: 'X-Nitric-Local-Call-Address',
value: apiAddress || 'localhost:4001',
Expand Down Expand Up @@ -600,6 +606,7 @@ const APIExplorer = () => {
<FieldRows
rows={request.queryParams}
testId="query"
addRowLabel="Add Query Param"
setRows={(rows) => {
setRequest((prev) => ({
...prev,
Expand All @@ -615,6 +622,7 @@ const APIExplorer = () => {
<FieldRows
rows={request.headers}
testId="header"
addRowLabel="Add Header"
setRows={(rows) => {
setRequest((prev) => ({
...prev,
Expand Down
2 changes: 1 addition & 1 deletion pkg/dashboard/frontend/src/components/apis/APIHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const APIHistory: React.FC<Props> = ({
})

if (!requestHistory.length) {
return <p>There is no history.</p>
return <p className="px-2">There is no history.</p>
}

return (
Expand Down
225 changes: 116 additions & 109 deletions pkg/dashboard/frontend/src/components/shared/FieldRows.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ExclamationCircleIcon, XMarkIcon } from '@heroicons/react/20/solid'
import { cn } from '@/lib/utils'
import React, { useEffect, useId } from 'react'
import React, { useId } from 'react'
import { Input } from '../ui/input'
import { Label } from '../ui/label'
import { Button } from '../ui/button'

export interface FieldRow {
key: string
Expand All @@ -14,10 +15,10 @@ interface Props {
rows: FieldRow[]
lockKeys?: boolean
readOnly?: boolean
canClearRow?: boolean
valueRequired?: boolean
valueErrors?: Record<number, FieldRow>
setRows: (value: FieldRow[]) => void
addRowLabel?: string
}

const FieldRows: React.FC<Props> = ({
Expand All @@ -28,127 +29,133 @@ const FieldRows: React.FC<Props> = ({
setRows,
valueErrors,
valueRequired,
canClearRow = true,
addRowLabel = 'Add Row',
}) => {
const id = useId()

useEffect(() => {
if (
!lockKeys &&
(rows[rows.length - 1].key || rows[rows.length - 1].value)
) {
setRows([
...rows,
{
key: '',
value: '',
},
])
}
}, [rows])

return (
<ul className="divide-y divide-gray-200">
{rows.map((r, i) => {
const keyId = `${id}-${i}-key`
const valueId = `${id}-${i}-value`
const valueHasError = Boolean(valueErrors && valueErrors[i])
<div>
<ul className="divide-y divide-gray-200">
{rows.map((r, i) => {
const keyId = `${id}-${i}-key`
const valueId = `${id}-${i}-value`
const valueHasError = Boolean(valueErrors && valueErrors[i])

return (
<li
key={i}
className="group relative grid grid-cols-2 items-center gap-4 py-4"
>
<div>
<Label htmlFor={keyId} className="sr-only">
Key
</Label>
<div className="mt-2 sm:col-span-2 sm:mt-0">
<Input
type="text"
data-testid={`${testId}-${i}-key`}
readOnly={lockKeys || readOnly}
placeholder="Key"
className="read-only:opacity-100"
onChange={(e) => {
const updatedRow: FieldRow = { ...r, key: e.target.value }
const newArr = [...rows]
return (
<li key={i} className="group flex items-center gap-4 py-4">
<div className="w-full">
<Label htmlFor={keyId} className="sr-only">
Key
</Label>
<div className="mt-2 sm:col-span-2 sm:mt-0">
<Input
type="text"
data-testid={`${testId}-${i}-key`}
readOnly={lockKeys || readOnly}
placeholder="Key"
className="read-only:opacity-100"
onChange={(e) => {
const updatedRow: FieldRow = { ...r, key: e.target.value }
const newArr = [...rows]

newArr[i] = updatedRow
newArr[i] = updatedRow

setRows(newArr)
}}
value={r.key}
name={keyId}
id={keyId}
/>
setRows(newArr)
}}
value={r.key}
name={keyId}
id={keyId}
/>
</div>
</div>
</div>
<div className="pr-8">
<Label htmlFor={valueId} className="sr-only">
{r.value}
</Label>
<div className="relative mt-2 sm:col-span-2 sm:mt-0">
<Input
type="text"
placeholder="Value"
readOnly={readOnly}
data-testid={`${testId}-${i}-value`}
onChange={(e) => {
const updatedRow: FieldRow = {
...r,
value: e.target.value,
}
const newArr = [...rows]
<div className={cn('w-full', lockKeys && 'mr-11')}>
<Label htmlFor={valueId} className="sr-only">
{r.value}
</Label>
<div className="relative mt-2 sm:col-span-2 sm:mt-0">
<Input
type="text"
placeholder="Value"
readOnly={readOnly}
data-testid={`${testId}-${i}-value`}
onChange={(e) => {
const updatedRow: FieldRow = {
...r,
value: e.target.value,
}
const newArr = [...rows]

newArr[i] = updatedRow
newArr[i] = updatedRow

setRows(newArr)
setRows(newArr)
}}
required={valueRequired}
name={valueId}
id={valueId}
value={r.value}
className={cn(
valueHasError &&
'text-red-900 !ring-red-500 placeholder:text-red-300',
)}
/>
{valueHasError && (
<div
data-testid={`${testId}-${i}-value-error-icon`}
className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"
>
<ExclamationCircleIcon
className="h-5 w-5 text-red-500"
aria-hidden="true"
/>
</div>
)}
</div>
</div>
{!lockKeys && (
<button
type="button"
onClick={() => {
const newArray = [...rows]
newArray.splice(i, 1)
setRows(newArray)
}}
required={valueRequired}
name={valueId}
id={valueId}
value={r.value}
aria-label="Remove row"
className={cn(
valueHasError &&
'text-red-900 !ring-red-500 placeholder:text-red-300',
'rounded-full bg-gray-600 p-1 text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600',
'flex items-center opacity-30 transition-all group-hover:opacity-100',
)}
/>
{valueHasError && (
<div
data-testid={`${testId}-${i}-value-error-icon`}
className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"
>
<ExclamationCircleIcon
className="h-5 w-5 text-red-500"
aria-hidden="true"
/>
</div>
)}
</div>
</div>
{canClearRow && (
<button
type="button"
onClick={() => {
const newArray = [...rows]
newArray.splice(i, 1)
setRows(newArray)
}}
className={cn(
'absolute right-0 hidden rounded-full bg-gray-600 p-1 text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600',
rows.length > 1 && (r.key || r.value)
? 'group-hover:block'
: '',
)}
>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
)}
>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
)}
</li>
)
})}
{rows.length === 0 && (
<li className="mt-1 text-sm text-foreground">
No rows to display. Click &apos;{addRowLabel}&apos; to begin adding
data.
</li>
)
})}
</ul>
)}
</ul>
{!lockKeys && (
<Button
onClick={() => {
setRows([
...rows,
{
key: '',
value: '',
},
])
}}
data-testid="add-row-btn"
className="ml-auto mt-4 flex"
>
{addRowLabel}
</Button>
)}
</div>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ const WSExplorer = () => {
<FieldRows
rows={queryParams}
readOnly={connected}
addRowLabel="Add Query Param"
testId="query"
setRows={(rows) => {
setQueryParams(rows)
Expand Down Expand Up @@ -587,7 +588,6 @@ const WSExplorer = () => {
</SelectContent>
</Select>
<Button
size={'lg'}
className="ml-auto"
data-testid="send-message-btn"
disabled={!currentPayload || !connected}
Expand Down

0 comments on commit cf2c048

Please sign in to comment.