diff --git a/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/Rest/utils.ts b/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/Rest/utils.ts index dad13214642..8d1ed62503c 100644 --- a/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/Rest/utils.ts +++ b/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/Rest/utils.ts @@ -280,7 +280,7 @@ export const supportedNumericTypes = [ 'decimal', ]; -const getValueWithType = (variableData: VariableState) => { +export const getValueWithType = (variableData: VariableState) => { if (variableData.type === 'Boolean') { if (variableData.value.trim().toLowerCase() === 'false') { return false; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/QueryCollections/hooks/useOperationsFromQueryCollection/useOperationsFromQueryCollection.test.ts b/frontend/libs/console/legacy-ce/src/lib/features/QueryCollections/hooks/useOperationsFromQueryCollection/useOperationsFromQueryCollection.test.ts index 0568e2f12a9..72b52171b46 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/QueryCollections/hooks/useOperationsFromQueryCollection/useOperationsFromQueryCollection.test.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/QueryCollections/hooks/useOperationsFromQueryCollection/useOperationsFromQueryCollection.test.ts @@ -41,7 +41,20 @@ describe('useOperationsFromQueryCollection with no query collections', () => { const operations = result.current.data!; - expect(operations[0].query).toEqual('query MyQuery { user { email name}}'); + expect(operations[0].query) + .toEqual(`mutation update_user_by_pk($id: Int!, $object: user_set_input!) { + update_user_by_pk(pk_columns: {id: $id}, _set: $object) { + address + bool + count + date + email + id + name + uuid + } +} +`); expect(operations).toHaveLength(3); }); diff --git a/frontend/libs/console/legacy-ce/src/lib/features/QueryCollections/mocks/metadata.mock.ts b/frontend/libs/console/legacy-ce/src/lib/features/QueryCollections/mocks/metadata.mock.ts index 647ac14465b..b0cc4cb0f5d 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/QueryCollections/mocks/metadata.mock.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/QueryCollections/mocks/metadata.mock.ts @@ -12,7 +12,22 @@ export const queryCollectionInitialData: Partial = { name: 'allowed-queries', definition: { queries: [ - { name: 'MyQuery', query: 'query MyQuery { user { email name}}' }, + { + name: 'MyQuery', + query: `mutation update_user_by_pk($id: Int!, $object: user_set_input!) { + update_user_by_pk(pk_columns: {id: $id}, _set: $object) { + address + bool + count + date + email + id + name + uuid + } +} +`, + }, { name: 'MyQuery2', query: 'query MyQuery2 { user { email name}}' }, { name: 'MyQuery3', query: 'query MyQuery3 { user { email name}}' }, ], diff --git a/frontend/libs/console/legacy-ce/src/lib/features/RestEndpoints/components/RestEndpointDetails/RequestHeaders.tsx b/frontend/libs/console/legacy-ce/src/lib/features/RestEndpoints/components/RestEndpointDetails/RequestHeaders.tsx new file mode 100644 index 00000000000..3b062ddfae4 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/RestEndpoints/components/RestEndpointDetails/RequestHeaders.tsx @@ -0,0 +1,122 @@ +import { FaPlus } from 'react-icons/fa'; +import { Button } from '../../../../new-components/Button'; +import { Collapsible } from '../../../../new-components/Collapsible'; +import { Header } from './RestEndpointDetails'; +import { CardedTable } from '../../../../new-components/CardedTable'; +import { Checkbox } from '../../../../new-components/Form'; + +type RequestHeadersProps = { + headers: Header[]; + setHeaders: (headers: Header[]) => void; +}; + +export const RequestHeaders = (props: RequestHeadersProps) => { + const { headers, setHeaders } = props; + return ( + Request Headers + } + > +
+
+ +
+
Headers List
+ header.selected)} + onCheckedChange={checked => + setHeaders( + headers.map(header => ({ + ...header, + selected: !!checked, + })) + ) + } + />, + 'Name', + 'Value', + ]} + data={headers.map((header, i) => [ + + setHeaders( + headers.map(h => ({ + ...h, + selected: h.name === header.name ? !!checked : h.selected, + })) + ) + } + />, + + setHeaders( + headers.map(h => ({ + ...h, + name: h.name === header.name ? e.target.value : h.name, + })) + ) + } + />, + + setHeaders( + headers.map(h => ({ + ...h, + value: h.name === header.name ? e.target.value : h.value, + })) + ) + } + />, + + (headers.length > 1 || + headers?.[0]?.name || + headers?.[0]?.value) && ( + + ), + ])} + /> +
+
+ ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/RestEndpoints/components/RestEndpointDetails/RestEndpointDetails.stories.tsx b/frontend/libs/console/legacy-ce/src/lib/features/RestEndpoints/components/RestEndpointDetails/RestEndpointDetails.stories.tsx new file mode 100644 index 00000000000..5f06b577143 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/RestEndpoints/components/RestEndpointDetails/RestEndpointDetails.stories.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { StoryObj, Meta } from '@storybook/react'; +import { within, userEvent, waitFor } from '@storybook/testing-library'; +import { expect } from '@storybook/jest'; +import { + RestEndpointDetails, + RestEndpointDetailsProps, +} from './RestEndpointDetails'; +import { ReactQueryDecorator } from '../../../../storybook/decorators/react-query'; +import { handlers } from '../../../../mocks/metadata.mock'; +import { rest } from 'msw'; + +const meta = { + title: 'Features/REST endpoints/Rest Endpoint Details', + component: RestEndpointDetails, + decorators: [ReactQueryDecorator()], + parameters: { + msw: [ + ...handlers({ delay: 0 }), + rest.post('**/api/rest/test/1', (req, res, ctx) => + res( + ctx.delay(0), + ctx.json({ + data: { + update_user_by_pk: { + id: 1, + name: 'Hasura', + }, + }, + }) + ) + ), + ], + }, + argTypes: {}, +} satisfies Meta; + +export default meta; + +export const Default: StoryObj = { + render: args => { + return ; + }, +}; + +Default.play = async ({ canvasElement }) => { + const canvas = within(canvasElement); + + await waitFor(() => { + expect(canvas.queryByText('Run Request')).toBeEnabled(); + }); + + // await expect(screen.queryByText('user')).toBeInTheDocument(); + + // // toggle the user permission and check the success notification + // await userEvent.click(canvas.getByTestId('user')); + // await expect( + // await canvas.findByText(`Allow list permissions updated`) + // ).toBeInTheDocument(); + + // Add new role + await userEvent.type(canvas.getByTestId('header-name-0'), 'Authorization'); + + await userEvent.type(canvas.getByTestId('header-value-0'), 'Bearer 123'); + await userEvent.click(canvas.getByText('Add Header')); + + await userEvent.type(canvas.getByTestId('variable-id'), '1'); + + await userEvent.type( + canvas.getByTestId('variable-object'), + '{"object": {"name": "Hasura"}}' + ); + + await userEvent.click(canvas.getByText('Run Request')); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/RestEndpoints/components/RestEndpointDetails/RestEndpointDetails.tsx b/frontend/libs/console/legacy-ce/src/lib/features/RestEndpoints/components/RestEndpointDetails/RestEndpointDetails.tsx new file mode 100644 index 00000000000..e6efa0528a5 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/RestEndpoints/components/RestEndpointDetails/RestEndpointDetails.tsx @@ -0,0 +1,198 @@ +import React, { useEffect } from 'react'; +import { z } from 'zod'; +import { + CheckboxesField, + CodeEditorField, + InputField, + Textarea, + useConsoleForm, +} from '../../../../new-components/Form'; +import { Button } from '../../../../new-components/Button'; +import { FaArrowRight, FaPlay } from 'react-icons/fa'; +import { useRestEndpoint } from '../../hooks/useRestEndpoint'; +import { getSessionVarsFromLS } from '../../../../components/Common/ConfigureTransformation/utils'; +import { parseQueryVariables } from '../../../../components/Services/ApiExplorer/Rest/utils'; +import { useRestEndpointRequest } from '../../hooks/useRestEndpointRequest'; +import { IndicatorCard } from '../../../../new-components/IndicatorCard'; +import { RequestHeaders } from './RequestHeaders'; +import { Variables } from './Variables'; +import { AllowedRESTMethods } from '../../../../metadata/types'; + +export type Variable = Exclude< + ReturnType, + undefined +>[0] & { + value: string; +}; + +export type Header = { + name: string; + value: string; + selected: boolean; +}; + +export type RestEndpointDetailsProps = { + name: string; +}; + +const commonEditorOptions = { + showLineNumbers: true, + useSoftTabs: true, + showPrintMargin: false, + showGutter: true, + wrap: true, +}; + +const requestEditorOptions = { + ...commonEditorOptions, + minLines: 10, + maxLines: 10, +}; + +const responseEditorOptions = { + ...commonEditorOptions, + minLines: 50, + maxLines: 50, +}; + +const validationSchema = z.object({ + name: z.string().min(1, { message: 'Please add a name' }), + comment: z.union([z.string(), z.null()]), + url: z.string().min(1, { message: 'Please add a location' }), + methods: z + .enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) + .array() + .nonempty({ message: 'Choose at least one method' }), + request: z.string().min(1, { message: 'Please add a GraphQL query' }), +}); + +export const RestEndpointDetails = (props: RestEndpointDetailsProps) => { + const endpoint = useRestEndpoint(props.name); + + const initialHeaders = getSessionVarsFromLS(); + + const [headers, setHeaders] = React.useState( + initialHeaders.map(header => ({ + ...header, + selected: true, + })) + ); + + const [variables, setVariables] = React.useState([]); + + const { data, refetch, isFetching, error } = useRestEndpointRequest( + endpoint?.endpoint, + headers, + variables + ); + + const { + Form, + methods: { setValue }, + } = useConsoleForm({ + schema: validationSchema, + }); + + useEffect(() => { + if (endpoint?.query?.query) { + const parsedVariables = parseQueryVariables(endpoint.query.query); + setVariables(parsedVariables?.map(v => ({ ...v, value: '' })) ?? []); + } + + if (endpoint) { + setValue('name', endpoint.endpoint?.name); + setValue('comment', endpoint?.endpoint?.comment); + setValue('url', endpoint?.endpoint?.url); + setValue('methods', endpoint?.endpoint?.methods); + setValue('request', endpoint?.query?.query); + } + }, [endpoint?.query?.query, endpoint?.endpoint]); + + useEffect(() => { + setValue('response', JSON.stringify(data, null, 2)); + }, [data]); + + if (!endpoint) { + return null; + } + + return ( +
{}}> +
+
+ +