mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-11-10 10:29:12 +03:00
console: new "add remote schema" page (with GQL customization)
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4428 GitOrigin-RevId: d8f82ffe86fd59abb43afa195b40c2fdafabb847
This commit is contained in:
parent
f7b4d32941
commit
db07dcf60d
@ -17,6 +17,7 @@
|
|||||||
- console: add remote database relationships for views
|
- console: add remote database relationships for views
|
||||||
- console: bug fixes for RS-to-RS relationships
|
- console: bug fixes for RS-to-RS relationships
|
||||||
- console: allow users to remove prefix / suffix / root field namespace from a remote schema
|
- console: allow users to remove prefix / suffix / root field namespace from a remote schema
|
||||||
|
- console: new "add remote schema" page (with GQL customization)
|
||||||
- cli: avoid exporting hasura-specific schemas during hasura init (#8352)
|
- cli: avoid exporting hasura-specific schemas during hasura init (#8352)
|
||||||
- cli: fix performance regression in `migrate status` command (fix #8398)
|
- cli: fix performance regression in `migrate status` command (fix #8398)
|
||||||
|
|
||||||
|
14
console/package-lock.json
generated
14
console/package-lock.json
generated
@ -21566,6 +21566,15 @@
|
|||||||
"@types/lodash": "*"
|
"@types/lodash": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/lodash.pickby": {
|
||||||
|
"version": "4.6.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.pickby/-/lodash.pickby-4.6.7.tgz",
|
||||||
|
"integrity": "sha512-4ebXRusuLflfscbD0PUX4eVknDHD9Yf+uMtBIvA/hrnTqeAzbuHuDjvnYriLjUrI9YrhCPVKUf4wkRSXJQ6gig==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/mdast": {
|
"@types/mdast": {
|
||||||
"version": "3.0.10",
|
"version": "3.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
|
||||||
@ -35839,6 +35848,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
|
"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
|
||||||
},
|
},
|
||||||
|
"lodash.pickby": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8="
|
||||||
|
},
|
||||||
"lodash.restparam": {
|
"lodash.restparam": {
|
||||||
"version": "3.6.1",
|
"version": "3.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
|
||||||
|
@ -88,6 +88,7 @@
|
|||||||
"less": "3.11.1",
|
"less": "3.11.1",
|
||||||
"lodash.get": "4.4.2",
|
"lodash.get": "4.4.2",
|
||||||
"lodash.merge": "4.6.2",
|
"lodash.merge": "4.6.2",
|
||||||
|
"lodash.pickby": "^4.6.0",
|
||||||
"moment": "^2.26.0",
|
"moment": "^2.26.0",
|
||||||
"piping": "0.3.2",
|
"piping": "0.3.2",
|
||||||
"prop-types": "15.7.2",
|
"prop-types": "15.7.2",
|
||||||
@ -170,6 +171,7 @@
|
|||||||
"@types/jwt-decode": "2.2.1",
|
"@types/jwt-decode": "2.2.1",
|
||||||
"@types/lodash": "^4.14.159",
|
"@types/lodash": "^4.14.159",
|
||||||
"@types/lodash.merge": "^4.6.6",
|
"@types/lodash.merge": "^4.6.6",
|
||||||
|
"@types/lodash.pickby": "^4.6.7",
|
||||||
"@types/mini-css-extract-plugin": "0.9.1",
|
"@types/mini-css-extract-plugin": "0.9.1",
|
||||||
"@types/optimize-css-assets-webpack-plugin": "5.0.1",
|
"@types/optimize-css-assets-webpack-plugin": "5.0.1",
|
||||||
"@types/react": "17.0.39",
|
"@types/react": "17.0.39",
|
||||||
|
@ -1,52 +1,75 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import Common from '../Common/Common';
|
import Common from '../Common/Common';
|
||||||
|
|
||||||
import { addRemoteSchema, RESET } from './addRemoteSchemaReducer';
|
import { addRemoteSchema, RESET } from './addRemoteSchemaReducer';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import Button from '../../../Common/Button/Button';
|
import Button from '../../../Common/Button/Button';
|
||||||
|
import { RemoteSchema } from '@/features/RemoteSchema';
|
||||||
|
import { appPrefix, pageTitle } from '../constants';
|
||||||
|
import {
|
||||||
|
availableFeatureFlagIds,
|
||||||
|
FeatureFlagToast,
|
||||||
|
useIsFeatureFlagEnabled,
|
||||||
|
} from '@/features/FeatureFlags';
|
||||||
|
import { exportMetadata } from '@/metadata/actions';
|
||||||
|
import _push from '../../Data/push';
|
||||||
|
|
||||||
import { pageTitle } from '../constants';
|
const Add = ({ isRequesting, dispatch, ...props }) => {
|
||||||
|
const styles = require('../RemoteSchema.scss');
|
||||||
|
|
||||||
class Add extends React.Component {
|
useEffect(() => {
|
||||||
componentWillUnmount() {
|
return () => {
|
||||||
this.props.dispatch({ type: RESET });
|
dispatch({ type: RESET });
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { isLoading, enabled } = useIsFeatureFlagEnabled(
|
||||||
|
availableFeatureFlagIds.addRemoteSchemaId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return 'Loading...';
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
if (enabled) {
|
||||||
const styles = require('../RemoteSchema.scss');
|
|
||||||
|
|
||||||
const { isRequesting, dispatch } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.addWrapper}>
|
<RemoteSchema.Create
|
||||||
<Helmet title={`Add ${pageTitle} - ${pageTitle}s | Hasura`} />
|
onSuccess={remoteSchemaName => {
|
||||||
<div className={styles.heading_text}>Add a new remote schema</div>
|
// This only exists right now because the sidebar is reading from redux state
|
||||||
<form
|
dispatch(exportMetadata()).then(() => {
|
||||||
onSubmit={e => {
|
dispatch(_push(`${appPrefix}/manage/${remoteSchemaName}/details`));
|
||||||
e.preventDefault();
|
});
|
||||||
dispatch(addRemoteSchema());
|
}}
|
||||||
}}
|
/>
|
||||||
>
|
|
||||||
<Common isNew {...this.props} />
|
|
||||||
<div className={styles.commonBtn}>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
color="yellow"
|
|
||||||
size="sm"
|
|
||||||
// disabled={isRequesting} // TODO
|
|
||||||
data-test="add-remote-schema-submit"
|
|
||||||
>
|
|
||||||
{isRequesting ? 'Adding...' : 'Add Remote Schema'}
|
|
||||||
</Button>
|
|
||||||
{/*
|
|
||||||
<button className={styles.default_button}>Cancel</button>
|
|
||||||
*/}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<div className={styles.addWrapper}>
|
||||||
|
<Helmet title={`Add ${pageTitle} - ${pageTitle}s | Hasura`} />
|
||||||
|
<div className={styles.heading_text}>Add a new remote schema</div>
|
||||||
|
<form
|
||||||
|
onSubmit={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch(addRemoteSchema());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Common isNew {...props} dispatch={dispatch} />
|
||||||
|
<div className={styles.commonBtn}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
color="yellow"
|
||||||
|
size="sm"
|
||||||
|
data-test="add-remote-schema-submit"
|
||||||
|
>
|
||||||
|
{isRequesting ? 'Adding...' : 'Add Remote Schema'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<FeatureFlagToast flagId={availableFeatureFlagIds.addRemoteSchemaId} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { FeatureFlagDefinition } from './types';
|
import { FeatureFlagDefinition } from './types';
|
||||||
|
|
||||||
const relationshipTabTablesId = '0bea35ff-d3e9-45e9-af1b-59923bf82fa9';
|
const relationshipTabTablesId = '0bea35ff-d3e9-45e9-af1b-59923bf82fa9';
|
||||||
|
const addRemoteSchemaId = 'bf57c2ba-cab2-11ec-9d64-0242ac120002';
|
||||||
|
|
||||||
export const availableFeatureFlagIds = {
|
export const availableFeatureFlagIds = {
|
||||||
relationshipTabTablesId,
|
relationshipTabTablesId,
|
||||||
|
addRemoteSchemaId,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const availableFeatureFlags: FeatureFlagDefinition[] = [
|
export const availableFeatureFlags: FeatureFlagDefinition[] = [
|
||||||
@ -18,4 +20,14 @@ export const availableFeatureFlags: FeatureFlagDefinition[] = [
|
|||||||
// defaultValue: false,
|
// defaultValue: false,
|
||||||
// discussionUrl: '',
|
// discussionUrl: '',
|
||||||
// },
|
// },
|
||||||
|
{
|
||||||
|
id: addRemoteSchemaId,
|
||||||
|
title: 'New create remote schema page',
|
||||||
|
description:
|
||||||
|
'Try out the new Add Remote Schema page that supports GraphQL customization',
|
||||||
|
section: 'remote schemas',
|
||||||
|
status: 'alpha',
|
||||||
|
defaultValue: false,
|
||||||
|
discussionUrl: '',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
@ -35,6 +35,7 @@ export const allowedMetadataTypesArr = [
|
|||||||
'create_remote_schema_remote_relationship',
|
'create_remote_schema_remote_relationship',
|
||||||
'update_remote_schema_remote_relationship',
|
'update_remote_schema_remote_relationship',
|
||||||
'delete_remote_schema_remote_relationship',
|
'delete_remote_schema_remote_relationship',
|
||||||
|
'add_remote_schema',
|
||||||
'bulk',
|
'bulk',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
import React, { useReducer } from 'react';
|
||||||
|
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||||
|
import { ComponentMeta, Story } from '@storybook/react';
|
||||||
|
import { RemoteSchema } from '@/features/RemoteSchema';
|
||||||
|
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
|
||||||
|
import { within, userEvent } from '@storybook/testing-library';
|
||||||
|
import { expect } from '@storybook/jest';
|
||||||
|
import { handlers } from './mocks/handlers.mock';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'RemoteSchema/components/Create',
|
||||||
|
component: RemoteSchema.Create,
|
||||||
|
decorators: [ReactQueryDecorator(), ReduxDecorator({})],
|
||||||
|
parameters: {
|
||||||
|
msw: handlers(),
|
||||||
|
},
|
||||||
|
} as ComponentMeta<typeof RemoteSchema.Create>;
|
||||||
|
|
||||||
|
export const Playground: Story = () => {
|
||||||
|
const [formSuccess, toggle] = useReducer(s => !s, false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RemoteSchema.Create onSuccess={() => toggle()} />
|
||||||
|
{formSuccess ? (
|
||||||
|
<p data-testid="form-result">Form saved succesfully!</p>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Playground.play = async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
userEvent.click(await canvas.findByTestId('submit'));
|
||||||
|
|
||||||
|
// expect error messages
|
||||||
|
expect(
|
||||||
|
await canvas.findByText('Remote Schema name is a required field!')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(await canvas.findByText('Invalid url')).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Fill up the fields
|
||||||
|
// const nameInput = await canvas.findByLabelText('Name');
|
||||||
|
userEvent.type(await canvas.findByTestId('name'), 'test');
|
||||||
|
userEvent.type(await canvas.findByTestId('url'), 'http://example.com');
|
||||||
|
userEvent.type(await canvas.findByTestId('timeout_seconds'), '180');
|
||||||
|
userEvent.click(await canvas.findByTestId('forward_client_headers'));
|
||||||
|
|
||||||
|
userEvent.click(await canvas.findByTestId('open_customization'));
|
||||||
|
|
||||||
|
userEvent.type(
|
||||||
|
await canvas.findByTestId('customization.root_fields_namespace'),
|
||||||
|
'root_field_namespace_example'
|
||||||
|
);
|
||||||
|
userEvent.type(
|
||||||
|
await canvas.findByTestId('customization.type_prefix'),
|
||||||
|
'type_prefix_example_'
|
||||||
|
);
|
||||||
|
userEvent.type(
|
||||||
|
await canvas.findByTestId('customization.type_suffix'),
|
||||||
|
'_type_suffix_example'
|
||||||
|
);
|
||||||
|
userEvent.type(
|
||||||
|
await canvas.findByTestId('customization.query_root.parent_type'),
|
||||||
|
'query_root_parent_type_example_'
|
||||||
|
);
|
||||||
|
userEvent.type(
|
||||||
|
await canvas.findByTestId('customization.query_root.prefix'),
|
||||||
|
'query_root_prefix_example_'
|
||||||
|
);
|
||||||
|
userEvent.type(
|
||||||
|
await canvas.findByTestId('customization.query_root.suffix'),
|
||||||
|
'_query_root_suffix_example'
|
||||||
|
);
|
||||||
|
userEvent.type(
|
||||||
|
await canvas.findByTestId('customization.mutation_root.parent_type'),
|
||||||
|
'mutation_root_parent_type_example_'
|
||||||
|
);
|
||||||
|
userEvent.type(
|
||||||
|
await canvas.findByTestId('customization.mutation_root.prefix'),
|
||||||
|
'mutation_root_prefix_example_'
|
||||||
|
);
|
||||||
|
userEvent.type(
|
||||||
|
await canvas.findByTestId('customization.mutation_root.suffix'),
|
||||||
|
'_mutation_root_suffix_example'
|
||||||
|
);
|
||||||
|
|
||||||
|
userEvent.click(await canvas.findByTestId('submit'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await canvas.findByText('Form saved succesfully!')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
};
|
416
console/src/features/RemoteSchema/components/Create/Create.tsx
Normal file
416
console/src/features/RemoteSchema/components/Create/Create.tsx
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
import { useMetadataMigration } from '@/features/MetadataAPI';
|
||||||
|
import { Button } from '@/new-components/Button';
|
||||||
|
import { FieldError } from 'react-hook-form';
|
||||||
|
import { Form, InputField } from '@/new-components/Form';
|
||||||
|
import { fireNotification } from '@/new-components/Notifications';
|
||||||
|
import { ToolTip } from '@/new-components/Tooltip';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
import { APIError } from '@/hooks/error';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { FaExclamationCircle, FaPlusCircle } from 'react-icons/fa';
|
||||||
|
import { Headers } from '../Headers';
|
||||||
|
import { schema, Schema } from './schema';
|
||||||
|
import { transformFormData } from './utils';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onSuccess?: (remoteSchemaName?: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Create = ({ onSuccess }: Props) => {
|
||||||
|
const [remoteSchemaName, setRemoteSchemaName] = useState('');
|
||||||
|
|
||||||
|
const mutation = useMetadataMigration(
|
||||||
|
{
|
||||||
|
onError: (error: APIError) => {
|
||||||
|
fireNotification({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: error?.message ?? 'Unable to create Remote Schema',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
fireNotification({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Success!',
|
||||||
|
message: 'Remote Schema created successfully',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (onSuccess) onSuccess(remoteSchemaName);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSubmit = (values: Schema) => {
|
||||||
|
const args = transformFormData(values);
|
||||||
|
setRemoteSchemaName(values.name);
|
||||||
|
|
||||||
|
mutation.mutate({
|
||||||
|
source: '',
|
||||||
|
query: { type: 'add_remote_schema', args },
|
||||||
|
migrationName: 'createRemoteSchema',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const defaultValues: Schema = {
|
||||||
|
name: '',
|
||||||
|
url: '',
|
||||||
|
headers: [],
|
||||||
|
forward_client_headers: false,
|
||||||
|
timeout_seconds: '',
|
||||||
|
comment: '',
|
||||||
|
customization: {
|
||||||
|
root_fields_namespace: '',
|
||||||
|
type_prefix: '',
|
||||||
|
type_suffix: '',
|
||||||
|
query_root: {
|
||||||
|
parent_type: '',
|
||||||
|
prefix: '',
|
||||||
|
suffix: '',
|
||||||
|
},
|
||||||
|
mutation_root: {
|
||||||
|
parent_type: '',
|
||||||
|
prefix: '',
|
||||||
|
suffix: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const [openCustomizationWidget, setOpenCustomizationWidget] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
schema={schema}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
options={{ defaultValues }}
|
||||||
|
className="overflow-y-hidden p-4"
|
||||||
|
>
|
||||||
|
{options => {
|
||||||
|
const queryRootError = get(
|
||||||
|
options.formState.errors,
|
||||||
|
'customization.query_root'
|
||||||
|
) as FieldError | undefined;
|
||||||
|
|
||||||
|
const mutationRootError = get(
|
||||||
|
options.formState.errors,
|
||||||
|
'customization.mutation_root'
|
||||||
|
) as FieldError | undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-6xl">
|
||||||
|
<h1 className="text-xl leading-6 font-semibold mb-lg">
|
||||||
|
Add Remote Schema
|
||||||
|
</h1>
|
||||||
|
<div className="mb-md w-6/12">
|
||||||
|
<InputField
|
||||||
|
name="name"
|
||||||
|
label="Remote Schema Name"
|
||||||
|
placeholder="Name..."
|
||||||
|
tooltip="give this GraphQL schema a friendly name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-md w-6/12">
|
||||||
|
<InputField
|
||||||
|
name="comment"
|
||||||
|
label="Comment / Description"
|
||||||
|
placeholder="Comment / Description..."
|
||||||
|
tooltip="A statement to help describe the remote schema in brief"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-md w-6/12">
|
||||||
|
<InputField
|
||||||
|
name="url"
|
||||||
|
label="GraphQL Service URL"
|
||||||
|
placeholder="https://myservice.com/graphql"
|
||||||
|
description="Note: Specifying the server URL via an environmental variable is
|
||||||
|
recommended if you have different URLs for multiple
|
||||||
|
environments."
|
||||||
|
tooltip="Remote GraphQL server’s URL. E.g. https://my-domain/v1/graphql"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-lg w-4/12">
|
||||||
|
<label className="block flex items-center text-gray-600 font-semibold mb-xs">
|
||||||
|
GraphQL Server Timeout
|
||||||
|
<ToolTip message="Configure timeout for your remote GraphQL server. Defaults to 60 seconds." />
|
||||||
|
</label>
|
||||||
|
<div className="relative w-full">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="block w-full shadow-sm rounded pr-10 border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="60"
|
||||||
|
{...options.register('timeout_seconds')}
|
||||||
|
data-testid="timeout_seconds"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-y-0 right-0 pr-3 flex text-gray-400 items-center pointer-events-none">
|
||||||
|
Seconds
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-lg w-8/12">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-600 ">Headers</h2>
|
||||||
|
|
||||||
|
<div className="items-center mr-sm mb-sm my-sm flex">
|
||||||
|
<input
|
||||||
|
{...options.register('forward_client_headers')}
|
||||||
|
type="checkbox"
|
||||||
|
className="mr-sm border-gray-400 rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-400"
|
||||||
|
value="true"
|
||||||
|
data-testid="forward_client_headers"
|
||||||
|
/>
|
||||||
|
<label className="pl-3 flex items-center">
|
||||||
|
Forward all headers from client
|
||||||
|
<ToolTip message="Custom headers to be sent to the remote GraphQL server" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Headers name="headers" />
|
||||||
|
</div>
|
||||||
|
<div className="mb-lg w-8/12">
|
||||||
|
<h2 className="text-lg font-semibold flex items-center">
|
||||||
|
GraphQL Customizations
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-gray-600 mb-sm">
|
||||||
|
Individual Types and Fields will be editable after saving.
|
||||||
|
<br />
|
||||||
|
<a href="https://spec.graphql.org/June2018/#example-e2969">
|
||||||
|
Read more
|
||||||
|
</a>{' '}
|
||||||
|
about Type and Field naming conventions in the official GraphQL
|
||||||
|
spec
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{openCustomizationWidget ? (
|
||||||
|
<div className="w-full rounded border bg-white border-gray-300 p-4">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setOpenCustomizationWidget(false)}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="grid gap-3 grid-cols-12 mb-md">
|
||||||
|
<div className="flex items-center col-span-4">
|
||||||
|
<label className="block text-gray-600 font-medium">
|
||||||
|
Root Field Namespace
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="namespace_"
|
||||||
|
{...options.register(
|
||||||
|
'customization.root_fields_namespace'
|
||||||
|
)}
|
||||||
|
data-testid="customization.root_fields_namespace"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-lg font-semibold mb-xs items-center flex">
|
||||||
|
Types
|
||||||
|
<ToolTip message="add a prefix / suffix to all types of the remote schema" />
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid gap-3 grid-cols-12 mb-md">
|
||||||
|
<div className="flex items-center col-span-4">
|
||||||
|
<label className="block text-gray-600 font-medium">
|
||||||
|
Prefix
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="prefix_"
|
||||||
|
{...options.register('customization.type_prefix')}
|
||||||
|
data-testid="customization.type_prefix"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-3 grid-cols-12 mb-md">
|
||||||
|
<div className="flex items-center col-span-4">
|
||||||
|
<label className="block text-gray-600 font-medium">
|
||||||
|
Suffix
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="_suffix"
|
||||||
|
{...options.register('customization.type_suffix')}
|
||||||
|
data-testid="customization.type_suffix"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-lg font-semibold mb-xs flex items-center">
|
||||||
|
Fields
|
||||||
|
<ToolTip message="add a prefix / suffix to the fields of the query / mutation root fields" />
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<h3 className="font-semibold mb-xs text-gray-600 text-lg">
|
||||||
|
Query root
|
||||||
|
</h3>
|
||||||
|
{queryRootError?.message && (
|
||||||
|
<div
|
||||||
|
role="alert"
|
||||||
|
aria-label={queryRootError.message}
|
||||||
|
className="mt-xs text-red-600 flex items-center"
|
||||||
|
>
|
||||||
|
<FaExclamationCircle className="fill-current h-4 mr-xs" />
|
||||||
|
{queryRootError.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="grid gap-3 grid-cols-12 mb-md">
|
||||||
|
<div className="flex items-center col-span-4">
|
||||||
|
<label className="block text-gray-600 font-medium">
|
||||||
|
Type Name
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="Query/query_root"
|
||||||
|
{...options.register(
|
||||||
|
'customization.query_root.parent_type'
|
||||||
|
)}
|
||||||
|
data-testid="customization.query_root.parent_type"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 grid-cols-12 mb-md">
|
||||||
|
<div className="flex items-center col-span-4">
|
||||||
|
<label className="block text-gray-600 font-medium">
|
||||||
|
Prefix
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="prefix_"
|
||||||
|
{...options.register('customization.query_root.prefix')}
|
||||||
|
data-testid="customization.query_root.prefix"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 grid-cols-12 mb-md">
|
||||||
|
<div className="flex items-center col-span-4">
|
||||||
|
<label className="block text-gray-600 font-medium">
|
||||||
|
Suffix
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="_suffix"
|
||||||
|
{...options.register('customization.query_root.suffix')}
|
||||||
|
data-testid="customization.query_root.suffix"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="font-semibold mb-xs text-gray-600 text-lg">
|
||||||
|
Mutation root
|
||||||
|
</h3>
|
||||||
|
{mutationRootError?.message && (
|
||||||
|
<div
|
||||||
|
role="alert"
|
||||||
|
aria-label={mutationRootError.message}
|
||||||
|
className="mt-xs text-red-600 flex items-center"
|
||||||
|
>
|
||||||
|
<FaExclamationCircle className="fill-current h-4 mr-xs" />
|
||||||
|
{mutationRootError.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="grid gap-3 grid-cols-12 mb-md">
|
||||||
|
<div className="flex items-center col-span-4">
|
||||||
|
<label className="block text-gray-600 font-medium">
|
||||||
|
Type Name
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="Mutation/mutation_root"
|
||||||
|
{...options.register(
|
||||||
|
'customization.mutation_root.parent_type'
|
||||||
|
)}
|
||||||
|
data-testid="customization.mutation_root.parent_type"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 grid-cols-12 mb-md">
|
||||||
|
<div className="flex items-center col-span-4">
|
||||||
|
<label className="block text-gray-600 font-medium">
|
||||||
|
Prefix
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="prefix_"
|
||||||
|
{...options.register(
|
||||||
|
'customization.mutation_root.prefix'
|
||||||
|
)}
|
||||||
|
data-testid="customization.mutation_root.prefix"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-3 grid-cols-12">
|
||||||
|
<div className="flex items-center col-span-4">
|
||||||
|
<label className="block text-gray-600 font-medium">
|
||||||
|
Suffix
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="_suffix"
|
||||||
|
{...options.register(
|
||||||
|
'customization.mutation_root.suffix'
|
||||||
|
)}
|
||||||
|
data-testid="customization.mutation_root.suffix"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
icon={<FaPlusCircle />}
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setOpenCustomizationWidget(true)}
|
||||||
|
data-testid="open_customization"
|
||||||
|
>
|
||||||
|
Add GQL Customization
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center mb-lg">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
data-testid="submit"
|
||||||
|
mode="primary"
|
||||||
|
isLoading={mutation.isLoading}
|
||||||
|
>
|
||||||
|
Add Remote Schema
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export { Create } from './Create';
|
@ -0,0 +1,7 @@
|
|||||||
|
import { rest } from 'msw';
|
||||||
|
|
||||||
|
export const handlers = () => [
|
||||||
|
rest.post('http://localhost:8080/v1/metadata', (req, res, ctx) => {
|
||||||
|
return res(ctx.json({ message: 'success' }));
|
||||||
|
}),
|
||||||
|
];
|
@ -0,0 +1,51 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { headersSchema } from '../Headers';
|
||||||
|
|
||||||
|
export const schema = z.object({
|
||||||
|
name: z.string().min(1, 'Remote Schema name is a required field!'),
|
||||||
|
url: z.string().url().min(1, 'URL is a required field!'),
|
||||||
|
headers: headersSchema,
|
||||||
|
forward_client_headers: z.preprocess(val => {
|
||||||
|
if (val === 'true') return true;
|
||||||
|
return false;
|
||||||
|
}, z.boolean()),
|
||||||
|
timeout_seconds: z.string().optional(),
|
||||||
|
customization: z.object({
|
||||||
|
root_fields_namespace: z.string(),
|
||||||
|
type_prefix: z.string(),
|
||||||
|
type_suffix: z.string(),
|
||||||
|
query_root: z
|
||||||
|
.object({
|
||||||
|
parent_type: z.string(),
|
||||||
|
prefix: z.string(),
|
||||||
|
suffix: z.string(),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
data => {
|
||||||
|
if ((data.prefix || data.suffix) && !data.parent_type) return false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Query type name cannot be empty!',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
mutation_root: z
|
||||||
|
.object({
|
||||||
|
parent_type: z.string(),
|
||||||
|
prefix: z.string(),
|
||||||
|
suffix: z.string(),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
data => {
|
||||||
|
if ((data.prefix || data.suffix) && !data.parent_type) return false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Mutation type name cannot be empty!',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
comment: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Schema = z.infer<typeof schema>;
|
84
console/src/features/RemoteSchema/components/Create/utils.ts
Normal file
84
console/src/features/RemoteSchema/components/Create/utils.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import pickBy from 'lodash.pickby';
|
||||||
|
import { Schema } from './schema';
|
||||||
|
|
||||||
|
export const transformFormData = (values: Schema) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
headers,
|
||||||
|
forward_client_headers,
|
||||||
|
comment,
|
||||||
|
timeout_seconds,
|
||||||
|
customization: {
|
||||||
|
root_fields_namespace,
|
||||||
|
type_prefix,
|
||||||
|
type_suffix,
|
||||||
|
query_root,
|
||||||
|
mutation_root,
|
||||||
|
},
|
||||||
|
} = values;
|
||||||
|
|
||||||
|
const customization: Record<string, any> = {};
|
||||||
|
|
||||||
|
/* if root field namespace is present */
|
||||||
|
if (root_fields_namespace)
|
||||||
|
customization.root_fields_namespace = root_fields_namespace;
|
||||||
|
|
||||||
|
/* if type prefix & suffix are present */
|
||||||
|
if (type_prefix || type_suffix)
|
||||||
|
customization.type_names = pickBy(
|
||||||
|
{
|
||||||
|
prefix: type_prefix,
|
||||||
|
suffix: type_suffix,
|
||||||
|
},
|
||||||
|
value => value.length > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/* if Query root customization is present */
|
||||||
|
if (query_root.parent_type && (query_root.prefix || query_root.suffix)) {
|
||||||
|
customization.field_names = [
|
||||||
|
...(customization.field_names ?? []),
|
||||||
|
pickBy(
|
||||||
|
{
|
||||||
|
parent_type: query_root.parent_type,
|
||||||
|
prefix: query_root.prefix,
|
||||||
|
suffix: query_root.suffix,
|
||||||
|
},
|
||||||
|
value => value.length > 0
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if Mutation root customization is present */
|
||||||
|
if (
|
||||||
|
mutation_root.parent_type &&
|
||||||
|
(mutation_root.prefix || mutation_root.suffix)
|
||||||
|
) {
|
||||||
|
customization.field_names = [
|
||||||
|
...(customization.field_names ?? []),
|
||||||
|
pickBy(
|
||||||
|
{
|
||||||
|
parent_type: mutation_root.parent_type,
|
||||||
|
prefix: mutation_root.prefix,
|
||||||
|
suffix: mutation_root.suffix,
|
||||||
|
},
|
||||||
|
value => value.length > 0
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const definition: Record<string, any> = {
|
||||||
|
url,
|
||||||
|
forward_client_headers,
|
||||||
|
comment,
|
||||||
|
headers: headers.map(header => {
|
||||||
|
if (header.type === 'from_env')
|
||||||
|
return { name: header.name, value_from_env: header.value };
|
||||||
|
return { name: header.name, value: header.value };
|
||||||
|
}),
|
||||||
|
timeout_seconds: timeout_seconds ? parseInt(timeout_seconds, 10) : 60,
|
||||||
|
customization,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { name, definition };
|
||||||
|
};
|
@ -0,0 +1,83 @@
|
|||||||
|
import { Button } from '@/new-components/Button';
|
||||||
|
import React from 'react';
|
||||||
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
|
import { FaPlusCircle } from 'react-icons/fa';
|
||||||
|
import { RiCloseCircleFill } from 'react-icons/ri';
|
||||||
|
import { THeaders } from './schema';
|
||||||
|
|
||||||
|
export const Headers = ({ name }: { name: string }) => {
|
||||||
|
const { register } = useFormContext();
|
||||||
|
|
||||||
|
const { fields, append, remove } = useFieldArray<Record<string, THeaders>>({
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{fields.length ? (
|
||||||
|
<div className="grid gap-3 grid-cols-2 mb-sm">
|
||||||
|
<label
|
||||||
|
htmlFor="table_name"
|
||||||
|
className="block text-gray-600 font-medium mb-xs"
|
||||||
|
>
|
||||||
|
Key
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label
|
||||||
|
htmlFor="table_name"
|
||||||
|
className="block text-gray-600 font-medium mb-xs"
|
||||||
|
>
|
||||||
|
Value
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{fields.map((field, index) => {
|
||||||
|
return (
|
||||||
|
<div className="grid gap-4 grid-cols-2 mb-sm" key={field.id}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="Key..."
|
||||||
|
{...register(`${name}[${index}].name`)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex shadow-sm rounded">
|
||||||
|
<select
|
||||||
|
{...register(`${name}[${index}].type`)}
|
||||||
|
className="inline-flex rounded-l border border-r-0 border-gray-300 bg-white hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
>
|
||||||
|
<option value="from_value">Value</option>
|
||||||
|
<option value="from_env">Env Var</option>
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
{...register(`${name}[${index}].value`)}
|
||||||
|
type="text"
|
||||||
|
className="flex-1 min-w-0 block w-full px-3 py-2 rounded-r border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||||
|
placeholder="user-id"
|
||||||
|
/>
|
||||||
|
<div className="col-span-1 flex items-center justify-center pl-1.5">
|
||||||
|
<RiCloseCircleFill
|
||||||
|
onClick={() => {
|
||||||
|
remove(index);
|
||||||
|
}}
|
||||||
|
className="cursor-pointer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<Button
|
||||||
|
data-testid={`${name}_add_new_row`}
|
||||||
|
icon={<FaPlusCircle />}
|
||||||
|
onClick={() => {
|
||||||
|
append({ name: '', value: '', type: 'from_value' });
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
Add additonal header
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './Headers';
|
||||||
|
export { headersSchema } from './schema';
|
@ -0,0 +1,11 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const headersSchema = z.array(
|
||||||
|
z.object({
|
||||||
|
name: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
type: z.literal('from_env').or(z.literal('from_value')),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export type THeaders = z.infer<typeof headersSchema>;
|
5
console/src/features/RemoteSchema/index.ts
Normal file
5
console/src/features/RemoteSchema/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Create } from './components/Create';
|
||||||
|
|
||||||
|
export const RemoteSchema = {
|
||||||
|
Create,
|
||||||
|
};
|
@ -25,7 +25,7 @@ const buttonModesStyles: Record<ButtonModes, string> = {
|
|||||||
destructive:
|
destructive:
|
||||||
'text-red-600 bg-gray-50 from-transparent to-white border-gray-300 hover:border-gray-400 disabled:border-gray-300 focus:from-bg-gray-50 focus:to-bg-gray-50',
|
'text-red-600 bg-gray-50 from-transparent to-white border-gray-300 hover:border-gray-400 disabled:border-gray-300 focus:from-bg-gray-50 focus:to-bg-gray-50',
|
||||||
primary:
|
primary:
|
||||||
'text-gray-600 bg-primary from-primary to-primary-light border-primary-dark hover:border-primary-darker focus:from-primary focus:to-primary disabled:border-primary-dark',
|
'text-gray-600 from-primary to-primary-light border-primary-dark hover:border-primary-darker focus:from-primary focus:to-primary disabled:border-primary-dark',
|
||||||
};
|
};
|
||||||
|
|
||||||
const sharedButtonStyle =
|
const sharedButtonStyle =
|
||||||
|
@ -2,7 +2,6 @@ import React, { ReactElement } from 'react';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash.get';
|
import get from 'lodash.get';
|
||||||
import { FieldError, useFormContext } from 'react-hook-form';
|
import { FieldError, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
import { FieldWrapper, FieldWrapperPassThroughProps } from './FieldWrapper';
|
import { FieldWrapper, FieldWrapperPassThroughProps } from './FieldWrapper';
|
||||||
|
|
||||||
export type InputFieldProps = FieldWrapperPassThroughProps & {
|
export type InputFieldProps = FieldWrapperPassThroughProps & {
|
||||||
@ -33,7 +32,6 @@ export const InputField = ({
|
|||||||
} = useFormContext();
|
} = useFormContext();
|
||||||
|
|
||||||
const maybeError = get(errors, name) as FieldError | undefined;
|
const maybeError = get(errors, name) as FieldError | undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldWrapper id={name} {...wrapperProps} error={maybeError}>
|
<FieldWrapper id={name} {...wrapperProps} error={maybeError}>
|
||||||
<div
|
<div
|
||||||
@ -58,8 +56,8 @@ export const InputField = ({
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
'block w-full max-w-xl h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus:outline-0 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400 placeholder-gray-500',
|
'block w-full max-w-xl h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus:outline-0 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400 placeholder-gray-500',
|
||||||
maybeError
|
maybeError
|
||||||
? 'border-red-600 hover:border-red-700 placeholder-red-600 '
|
? 'border-red-600 hover:border-red-700'
|
||||||
: 'border-gray-300 placeholder-gray-600',
|
: 'border-gray-300',
|
||||||
disabled
|
disabled
|
||||||
? 'cursor-not-allowed bg-gray-100 border-gray-100'
|
? 'cursor-not-allowed bg-gray-100 border-gray-100'
|
||||||
: 'hover:border-gray-400',
|
: 'hover:border-gray-400',
|
||||||
@ -71,6 +69,7 @@ export const InputField = ({
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
{...register(name)}
|
{...register(name)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
data-testid={name}
|
||||||
/>
|
/>
|
||||||
{iconPosition === 'end' && icon ? (
|
{iconPosition === 'end' && icon ? (
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user