mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
console: fix headers for mutation input validation
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9898 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> GitOrigin-RevId: 4e1a36b498c65a6ad9bcbe98a1dd9e87f288ac06
This commit is contained in:
parent
f0441a3d61
commit
059c3e3876
Binary file not shown.
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 82 KiB |
@ -77,8 +77,8 @@ describe('Create a insert type table permission with input validation', () => {
|
|||||||
cy.findByText('Forward client headers to webhook').click();
|
cy.findByText('Forward client headers to webhook').click();
|
||||||
cy.get('[name="definition.timeout"]').clear().type('40');
|
cy.get('[name="definition.timeout"]').clear().type('40');
|
||||||
cy.findByRole('button', { name: 'Add Additional Headers' }).click();
|
cy.findByRole('button', { name: 'Add Additional Headers' }).click();
|
||||||
cy.findByPlaceholderText('Key...').type('x-hasura-user-id');
|
cy.findByPlaceholderText('Key').type('x-hasura-user-id');
|
||||||
cy.findByPlaceholderText('Value...').type('1234');
|
cy.findByPlaceholderText('Value or {{Environment_Variable}}').type('1234');
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
cy.log('**--- Click to Save Permission');
|
cy.log('**--- Click to Save Permission');
|
||||||
@ -174,8 +174,8 @@ describe('Create a update type table permission with input validation', () => {
|
|||||||
cy.findByText('Forward client headers to webhook').click();
|
cy.findByText('Forward client headers to webhook').click();
|
||||||
cy.get('[name="definition.timeout"]').clear().type('40');
|
cy.get('[name="definition.timeout"]').clear().type('40');
|
||||||
cy.findByRole('button', { name: 'Add Additional Headers' }).click();
|
cy.findByRole('button', { name: 'Add Additional Headers' }).click();
|
||||||
cy.findByPlaceholderText('Key...').type('x-hasura-user-id');
|
cy.findByPlaceholderText('Key').type('x-hasura-user-id');
|
||||||
cy.findByPlaceholderText('Value...').type('1234');
|
cy.findByPlaceholderText('Value or {{Environment_Variable}}').type('1234');
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
cy.log('**--- Click to Save Permission');
|
cy.log('**--- Click to Save Permission');
|
||||||
@ -271,8 +271,8 @@ describe('Create a delete type table permission with input validation', () => {
|
|||||||
cy.findByText('Forward client headers to webhook').click();
|
cy.findByText('Forward client headers to webhook').click();
|
||||||
cy.get('[name="definition.timeout"]').clear().type('40');
|
cy.get('[name="definition.timeout"]').clear().type('40');
|
||||||
cy.findByRole('button', { name: 'Add Additional Headers' }).click();
|
cy.findByRole('button', { name: 'Add Additional Headers' }).click();
|
||||||
cy.findByPlaceholderText('Key...').type('x-hasura-user-id');
|
cy.findByPlaceholderText('Key').type('x-hasura-user-id');
|
||||||
cy.findByPlaceholderText('Value...').type('1234');
|
cy.findByPlaceholderText('Value or {{Environment_Variable}}').type('1234');
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
cy.log('**--- Click to Save Permission');
|
cy.log('**--- Click to Save Permission');
|
||||||
|
@ -46,6 +46,9 @@ Base.play = async ({ canvasElement }: any) => {
|
|||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// Step 6: add headers
|
// Step 6: add headers
|
||||||
await userEvent.click(canvas.getByText('Add Additional Headers'));
|
await userEvent.click(canvas.getByText('Add Additional Headers'));
|
||||||
await userEvent.type(canvas.getByPlaceholderText('Key...'), 'x-hasura-name');
|
await userEvent.type(canvas.getByPlaceholderText('Key'), 'x-hasura-name');
|
||||||
await userEvent.type(canvas.getByPlaceholderText('Value...'), 'hasura_user');
|
await userEvent.type(
|
||||||
|
canvas.getByPlaceholderText('Value or {{Environment_Variable}}'),
|
||||||
|
'hasura_user'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,10 +4,10 @@ import { IconTooltip } from '../../../../../new-components/Tooltip';
|
|||||||
import { Switch } from '../../../../../new-components/Switch';
|
import { Switch } from '../../../../../new-components/Switch';
|
||||||
import { InputField } from '../../../../../new-components/Form';
|
import { InputField } from '../../../../../new-components/Form';
|
||||||
import { FaShieldAlt } from 'react-icons/fa';
|
import { FaShieldAlt } from 'react-icons/fa';
|
||||||
import { RequestHeadersSelector } from '../../../../../new-components/RequestHeadersSelector';
|
|
||||||
import { BooleanCheckbox } from './BooleanCheckbox';
|
import { BooleanCheckbox } from './BooleanCheckbox';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { Badge } from '../../../../../new-components/Badge';
|
import { Badge } from '../../../../../new-components/Badge';
|
||||||
|
import { RequestHeaders } from '../../../../../new-components/RequestHeader';
|
||||||
|
|
||||||
export const inputValidationSchema = z.object({
|
export const inputValidationSchema = z.object({
|
||||||
type: z.enum(['http']),
|
type: z.enum(['http']),
|
||||||
@ -70,7 +70,10 @@ export const InputValidation = () => {
|
|||||||
<InputField
|
<InputField
|
||||||
learnMoreLink="https://hasura.io/docs/latest/api-reference/syntax-defs/#webhookurl"
|
learnMoreLink="https://hasura.io/docs/latest/api-reference/syntax-defs/#webhookurl"
|
||||||
tooltipIcon={
|
tooltipIcon={
|
||||||
<FaShieldAlt className="h-4 text-muted cursor-pointer" />
|
<div className="flex items-center">
|
||||||
|
<p className="text-red-600 pr-xs">*</p>
|
||||||
|
<FaShieldAlt className="h-4 text-muted cursor-pointer" />
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
name="definition.url"
|
name="definition.url"
|
||||||
label="Webhook URL"
|
label="Webhook URL"
|
||||||
@ -79,7 +82,7 @@ export const InputValidation = () => {
|
|||||||
description="Note: Provide an URL or use an env var to template the handler URL if you have different URLs for multiple environments."
|
description="Note: Provide an URL or use an env var to template the handler URL if you have different URLs for multiple environments."
|
||||||
/>
|
/>
|
||||||
<div className="mb-xs">
|
<div className="mb-xs">
|
||||||
<label className="flex items-center text-gray-600 font-semibold mb-xs">
|
<label className="flex items-center text-gray-600 font-semibold">
|
||||||
Header
|
Header
|
||||||
<IconTooltip message="Configure headers for the request to the webhook" />
|
<IconTooltip message="Configure headers for the request to the webhook" />
|
||||||
</label>
|
</label>
|
||||||
@ -89,7 +92,7 @@ export const InputValidation = () => {
|
|||||||
text="Forward client headers to webhook"
|
text="Forward client headers to webhook"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<RequestHeadersSelector
|
<RequestHeaders
|
||||||
name="definition.headers"
|
name="definition.headers"
|
||||||
addButtonText="Add Additional Headers"
|
addButtonText="Add Additional Headers"
|
||||||
/>
|
/>
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
import React, { ReactText } from 'react';
|
||||||
|
import { useFieldArray } from 'react-hook-form';
|
||||||
|
import { FaPlusCircle, FaShieldAlt } from 'react-icons/fa';
|
||||||
|
import { Button } from '../Button';
|
||||||
|
import { RequestHeadersSchema } from './schema';
|
||||||
|
import { KeyValue } from './components/KeyValue';
|
||||||
|
import { IconTooltip } from '../Tooltip';
|
||||||
|
|
||||||
|
export interface RequestHeadersProps {
|
||||||
|
name: string;
|
||||||
|
addButtonText?: ReactText;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RequestHeaders = (props: RequestHeadersProps) => {
|
||||||
|
const { name, addButtonText = 'Add' } = props;
|
||||||
|
const { fields, append, remove } = useFieldArray<
|
||||||
|
Record<string, RequestHeadersSchema>
|
||||||
|
>({
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
const thereIsAtLeastOneField = fields.length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{thereIsAtLeastOneField ? (
|
||||||
|
<>
|
||||||
|
<div className="grid gap-3 grid-cols-2 mb-sm">
|
||||||
|
<label className="block text-gray-600 font-medium mb-xs">Key</label>
|
||||||
|
<label className="block text-gray-600 font-medium mb-xs flex">
|
||||||
|
Value
|
||||||
|
<IconTooltip
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
Value can be either static string or a template which can
|
||||||
|
reference environment variables.
|
||||||
|
<p>Example:</p>
|
||||||
|
<p>Static string: "abc"</p>
|
||||||
|
<p>
|
||||||
|
Template with environment variables:
|
||||||
|
{{ACTION_BASE_URL}}/payment" or
|
||||||
|
{{FULL_ACTION_URL}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
icon={<FaShieldAlt className="h-4 text-muted cursor-pointer" />}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<KeyValue
|
||||||
|
key={field.id}
|
||||||
|
fieldId={field.id}
|
||||||
|
fieldName={name}
|
||||||
|
rowIndex={index}
|
||||||
|
removeRow={remove}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
data-testid="add-header"
|
||||||
|
icon={<FaPlusCircle />}
|
||||||
|
onClick={() => {
|
||||||
|
append({
|
||||||
|
name: '',
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{addButtonText}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { RiCloseCircleFill } from 'react-icons/ri';
|
||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fieldId: string;
|
||||||
|
fieldName: string;
|
||||||
|
rowIndex: number;
|
||||||
|
removeRow: (index?: number | number[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const borderStyle =
|
||||||
|
'border-gray-300 hover:border-gray-400 focus:border-yellow-400';
|
||||||
|
const ringStyle = 'focus:ring-2 focus:ring-yellow-200';
|
||||||
|
|
||||||
|
export const KeyValue = (props: Props) => {
|
||||||
|
const { fieldId, fieldName, rowIndex, removeRow } = props;
|
||||||
|
const keyLabel = `${fieldName}[${rowIndex}].name`;
|
||||||
|
const valueLabel = `${fieldName}[${rowIndex}].value`;
|
||||||
|
const { register } = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid gap-4 grid-cols-2 mb-sm" key={fieldId}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className={`w-full block h-10 shadow-sm rounded ${borderStyle} ${ringStyle}`}
|
||||||
|
placeholder="Key"
|
||||||
|
{...register(keyLabel)}
|
||||||
|
aria-label={keyLabel}
|
||||||
|
data-test={`header-test${rowIndex}-key`}
|
||||||
|
/>
|
||||||
|
<div className="flex rounded">
|
||||||
|
<input
|
||||||
|
{...register(valueLabel)}
|
||||||
|
aria-label={valueLabel}
|
||||||
|
data-test={`header-test${rowIndex}-value`}
|
||||||
|
type="text"
|
||||||
|
className={`flex-1 min-w-0 h-10 shadow-sm block w-full px-3 py-2 rounded-r ${borderStyle} ${ringStyle}`}
|
||||||
|
placeholder="Value or {{Environment_Variable}}"
|
||||||
|
/>
|
||||||
|
<div className="col-span-1 flex items-center justify-center pl-1.5">
|
||||||
|
<RiCloseCircleFill
|
||||||
|
onClick={() => {
|
||||||
|
removeRow(rowIndex);
|
||||||
|
}}
|
||||||
|
className="cursor-pointer"
|
||||||
|
data-test={`delete-header-${rowIndex}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './RequestHeader';
|
||||||
|
export { requestHeadersSchema } from './schema';
|
@ -0,0 +1,10 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const requestHeadersSchema = z.array(
|
||||||
|
z.object({
|
||||||
|
name: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export type RequestHeadersSchema = z.infer<typeof requestHeadersSchema>;
|
Loading…
Reference in New Issue
Block a user