console: fix data types issues in native queries forms

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9198
GitOrigin-RevId: 07932129503b4941c21e99bf11d5eac32ea6fa11
This commit is contained in:
Matthew Goodwin 2023-05-17 12:12:16 -05:00 committed by hasura-bot
parent 605f0281dc
commit 1d91f633c3
8 changed files with 76 additions and 95 deletions

View File

@ -34,8 +34,8 @@ const fillAndSubmitForm = async ({
* *
*/ */
userEvent.click(c.getByText('Add Parameter')); await userEvent.click(c.getByText('Add Parameter'));
userEvent.click(c.getByText('Save')); await userEvent.click(c.getByText('Save'));
const errorMessages = [ const errorMessages = [
'Native Query Name is required', 'Native Query Name is required',
@ -49,35 +49,35 @@ const fillAndSubmitForm = async ({
} }
// remove param added for error testing // remove param added for error testing
userEvent.click(c.getByText('Remove')); await userEvent.click(c.getByText('Remove'));
userEvent.type( await userEvent.type(
c.getByPlaceholderText('Name that exposes this model in GraphQL API'), c.getByPlaceholderText('Name that exposes this model in GraphQL API'),
'my_native_query' 'my_native_query'
); );
userEvent.type( await userEvent.type(
c.getByPlaceholderText('A description of this logical model'), c.getByPlaceholderText('A description of this logical model'),
'a description' 'a description'
); );
//select postgres from the database dropdown //select postgres from the database dropdown
userEvent.selectOptions( await userEvent.selectOptions(
await c.findByLabelText('Database', undefined, { timeout: 3000 }), await c.findByLabelText('Database', undefined, { timeout: 3000 }),
await c.findByRole('option', { name: 'postgres' }) await c.findByRole('option', { name: 'postgres' })
); );
userEvent.click(c.getByText('Add Parameter')); await userEvent.click(await c.findByText('Add Parameter'));
userEvent.type(c.getByPlaceholderText('Parameter Name'), 'param1'); await userEvent.type(c.getByPlaceholderText('Parameter Name'), 'param1');
userEvent.type(c.getByPlaceholderText('Default Value'), 'default'); await userEvent.type(c.getByPlaceholderText('Default Value'), 'default');
userEvent.click(c.getByTestId('required-switch')); await userEvent.click(c.getByTestId('required-switch'));
userEvent.selectOptions( await userEvent.selectOptions(
await c.findByLabelText('Query Return Type', undefined, { timeout: 3000 }), await c.findByLabelText('Query Return Type', undefined, { timeout: 3000 }),
await c.findByRole('option', { name: 'hello_world' }) await c.findByRole('option', { name: 'hello_world' })
); );
userEvent.click(c.getByText('Save')); await userEvent.click(c.getByText('Save'));
}; };
export const HappyPath: ComponentStory<typeof AddNativeQuery> = args => { export const HappyPath: ComponentStory<typeof AddNativeQuery> = args => {

View File

@ -8,17 +8,21 @@ import {
useConsoleForm, useConsoleForm,
} from '../../../../new-components/Form'; } from '../../../../new-components/Form';
// import { FormDebugWindow } from '../../../../new-components/Form/dev-components/FormDebugWindow'; // import { FormDebugWindow } from '../../../../new-components/Form/dev-components/FormDebugWindow';
import Skeleton from 'react-loading-skeleton';
import { Driver, drivers } from '../../../../dataSources';
import { IndicatorCard } from '../../../../new-components/IndicatorCard';
import { hasuraToast } from '../../../../new-components/Toasts'; import { hasuraToast } from '../../../../new-components/Toasts';
import { Feature } from '../../../DataSource';
import { useMetadata } from '../../../hasura-metadata-api'; import { useMetadata } from '../../../hasura-metadata-api';
import { useSupportedDataTypes } from '../../hooks/useSupportedDataTypes';
import { useTrackNativeQuery } from '../../hooks/useTrackNativeQuery'; import { useTrackNativeQuery } from '../../hooks/useTrackNativeQuery';
import { LogicalModelWidget } from '../LogicalModelWidget/LogicalModelWidget';
import { ArgumentsField } from './components/ArgumentsField'; import { ArgumentsField } from './components/ArgumentsField';
import { PageWrapper } from './components/PageWrapper'; import { PageWrapper } from './components/PageWrapper';
import { SqlEditorField } from './components/SqlEditorField'; import { SqlEditorField } from './components/SqlEditorField';
import { schema } from './schema'; import { schema } from './schema';
import { NativeQueryForm } from './types'; import { NativeQueryForm } from './types';
import { transformFormOutputToMetadata } from './utils'; import { transformFormOutputToMetadata } from './utils';
import { LogicalModelWidget } from '../LogicalModelWidget/LogicalModelWidget';
import { Driver, drivers } from '../../../../dataSources';
type AddNativeQueryProps = { type AddNativeQueryProps = {
defaultFormValues?: Partial<NativeQueryForm>; defaultFormValues?: Partial<NativeQueryForm>;
@ -38,7 +42,11 @@ export const AddNativeQuery = ({
options: { defaultValues: defaultFormValues }, options: { defaultValues: defaultFormValues },
}); });
const { data: sources, isLoading: isSourcesLoading } = useMetadata(s => { const {
data: sources,
isLoading: isSourcesLoading,
error: sourcesError,
} = useMetadata(s => {
return s.metadata.sources.filter(s => drivers.includes(s.kind as Driver)); return s.metadata.sources.filter(s => drivers.includes(s.kind as Driver));
}); });
@ -94,6 +102,32 @@ export const AddNativeQuery = ({
} }
}; };
/**
* Options for the data source types
*/
const {
data: typeOptions = [],
error: typeOptionError,
isLoading: isIntrospectionLoading,
} = useSupportedDataTypes({
dataSourceName: selectedSource,
select: values => {
if (values === Feature.NotImplemented) return [];
return Object.values(values).flat();
},
options: {
enabled: !!selectedSource,
},
});
if (sourcesError || typeOptionError)
return (
<IndicatorCard status="negative" headline="Internal Error">
<div>{sourcesError}</div>
<div> {typeOptionError?.message}</div>
</IndicatorCard>
);
return ( return (
<PageWrapper pathname={pathname} push={push}> <PageWrapper pathname={pathname} push={push}>
<Form onSubmit={handleFormSubmit}> <Form onSubmit={handleFormSubmit}>
@ -123,7 +157,14 @@ export const AddNativeQuery = ({
placeholder="Select a database..." placeholder="Select a database..."
/> />
</div> </div>
<ArgumentsField /> {isIntrospectionLoading ? (
<div>
<Skeleton />
<Skeleton />
</div>
) : (
<ArgumentsField types={typeOptions} />
)}
<SqlEditorField /> <SqlEditorField />
<div className="flex w-full"> <div className="flex w-full">
{/* Logical Model Dropdown */} {/* Logical Model Dropdown */}

View File

@ -5,23 +5,22 @@ import {
} from '@tanstack/react-table'; } from '@tanstack/react-table';
import clsx from 'clsx'; import clsx from 'clsx';
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form'; import { useFieldArray, useFormContext } from 'react-hook-form';
import { FaPlusCircle } from 'react-icons/fa'; import { FaPlusCircle } from 'react-icons/fa';
import { Button } from '../../../../../new-components/Button'; import { Button } from '../../../../../new-components/Button';
import { import {
FieldWrapper,
GraphQLSanitizedInputField, GraphQLSanitizedInputField,
InputField, InputField,
Select, Select,
fieldLabelStyles, fieldLabelStyles,
} from '../../../../../new-components/Form'; } from '../../../../../new-components/Form';
import { Switch } from '../../../../../new-components/Switch'; import { BooleanInput } from '../../components/BooleanInput';
import { useCardedTableFromReactTableWithRef } from '../../components/CardedTableFromReactTable'; import { useCardedTableFromReactTableWithRef } from '../../components/CardedTableFromReactTable';
import { NativeQueryArgumentNormalized, NativeQueryForm } from '../types'; import { NativeQueryArgumentNormalized, NativeQueryForm } from '../types';
const columnHelper = createColumnHelper<NativeQueryArgumentNormalized>(); const columnHelper = createColumnHelper<NativeQueryArgumentNormalized>();
export const ArgumentsField = () => { export const ArgumentsField = ({ types }: { types: string[] }) => {
const { control } = useFormContext<NativeQueryForm>(); const { control } = useFormContext<NativeQueryForm>();
const { append, remove, fields } = useFieldArray({ const { append, remove, fields } = useFieldArray({
@ -53,10 +52,7 @@ export const ArgumentsField = () => {
// saving prop for future upgrade // saving prop for future upgrade
//menuPortalTarget={tableRef.current} //menuPortalTarget={tableRef.current}
name={`arguments.${row.index}.type`} name={`arguments.${row.index}.type`}
options={[ options={types.map(t => ({ label: t, value: t }))}
{ value: 'string', label: 'string' },
{ value: 'int', label: 'int' },
]}
/> />
), ),
header: 'Type', header: 'Type',
@ -75,22 +71,7 @@ export const ArgumentsField = () => {
columnHelper.accessor('required', { columnHelper.accessor('required', {
id: 'required', id: 'required',
cell: ({ row }) => ( cell: ({ row }) => (
<Controller <BooleanInput name={`arguments.${row.index}.required`} />
name={`arguments.${row.index}.required`}
render={({ field: { value, onChange }, fieldState }) => (
<FieldWrapper
id={`arguments.${row.index}.required`}
noErrorPlaceholder
error={fieldState.error}
>
<Switch
data-testid="required-switch"
checked={value}
onCheckedChange={onChange}
/>
</FieldWrapper>
)}
/>
), ),
header: 'Required', header: 'Required',
}), }),
@ -106,7 +87,7 @@ export const ArgumentsField = () => {
), ),
}), }),
], ],
[control, remove] [remove, types]
); );
const argumentsTable = useReactTable({ const argumentsTable = useReactTable({

View File

@ -47,6 +47,8 @@ export const LogicalModelWidget = (props: AddLogicalModelDialogProps) => {
}, },
}); });
const selectedDataSource = watch('dataSourceName');
/** /**
* Options for the data sources * Options for the data sources
*/ */
@ -71,13 +73,13 @@ export const LogicalModelWidget = (props: AddLogicalModelDialogProps) => {
error: typeOptionError, error: typeOptionError,
isLoading: isIntrospectionLoading, isLoading: isIntrospectionLoading,
} = useSupportedDataTypes({ } = useSupportedDataTypes({
dataSourceName: watch('dataSourceName'), dataSourceName: selectedDataSource,
select: values => { select: values => {
if (values === Feature.NotImplemented) return []; if (values === Feature.NotImplemented) return [];
return Object.values(values).flat(); return Object.values(values).flat();
}, },
options: { options: {
enabled: !!watch('dataSourceName'), enabled: !!selectedDataSource,
}, },
}); });

View File

@ -1,11 +1,10 @@
import { useFieldArray, useFormContext } from 'react-hook-form';
import clsx from 'clsx'; import clsx from 'clsx';
import { BooleanInput } from './BooleanInput'; import { useFieldArray, useFormContext } from 'react-hook-form';
import { TypeInput } from './TypeInput';
import { FiTrash2 } from 'react-icons/fi'; import { FiTrash2 } from 'react-icons/fi';
import { CardedTable } from '../../../../../new-components/CardedTable';
import { InputField } from '../../../../../new-components/Form';
import { Button } from '../../../../../new-components/Button'; import { Button } from '../../../../../new-components/Button';
import { CardedTable } from '../../../../../new-components/CardedTable';
import { InputField, Select } from '../../../../../new-components/Form';
import { BooleanInput } from '../../components/BooleanInput';
export const FieldsInput = ({ export const FieldsInput = ({
name, name,
@ -52,7 +51,7 @@ export const FieldsInput = ({
/> />
</CardedTable.TableBodyCell> </CardedTable.TableBodyCell>
<CardedTable.TableBodyCell> <CardedTable.TableBodyCell>
<TypeInput <Select
name={`${name}[${index}].type`} name={`${name}[${index}].type`}
label="" label=""
options={types.map(t => ({ label: t, value: t }))} options={types.map(t => ({ label: t, value: t }))}

View File

@ -1,43 +0,0 @@
import { Controller } from 'react-hook-form';
import {
FieldWrapper,
FieldWrapperPassThroughProps,
MultiSelectItem,
} from '../../../../../new-components/Form';
type TypeInputProps = FieldWrapperPassThroughProps & {
name: string;
options: MultiSelectItem[];
disabled?: boolean;
};
export const TypeInput = ({
name,
options,
disabled,
...wrapperProps
}: TypeInputProps) => {
return (
<Controller
name={name}
render={({ field: { value }, fieldState }) => (
<FieldWrapper id={name} {...wrapperProps} error={fieldState.error}>
<select
data-testid={name}
className={
'block w-full h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-yellow-200 focus-visible:border-yellow-400 text-gray-500'
}
value={value}
disabled={disabled}
>
{options.map(({ label, value }) => (
<option key={`${name}-${value}`} value={value}>
{label}
</option>
))}
</select>
</FieldWrapper>
)}
/>
);
};

View File

@ -2,7 +2,7 @@ import clsx from 'clsx';
import { CardedTable } from '../../../../../new-components/CardedTable'; import { CardedTable } from '../../../../../new-components/CardedTable';
import { InputField, Select } from '../../../../../new-components/Form'; import { InputField, Select } from '../../../../../new-components/Form';
import { useFieldArray, useFormContext } from 'react-hook-form'; import { useFieldArray, useFormContext } from 'react-hook-form';
import { BooleanInput } from '../../LogicalModelWidget/parts/BooleanInput'; import { BooleanInput } from '../../components/BooleanInput';
import { Button } from '../../../../../new-components/Button'; import { Button } from '../../../../../new-components/Button';
import { FiTrash2 } from 'react-icons/fi'; import { FiTrash2 } from 'react-icons/fi';

View File

@ -1,8 +1,8 @@
import { import {
FieldWrapper, FieldWrapper,
FieldWrapperPassThroughProps, FieldWrapperPassThroughProps,
} from '../../../../../new-components/Form'; } from '../../../../new-components/Form';
import { Switch } from '../../../../../new-components/Switch'; import { Switch } from '../../../../new-components/Switch';
import { Controller } from 'react-hook-form'; import { Controller } from 'react-hook-form';
type BooleanInputProps = FieldWrapperPassThroughProps & { type BooleanInputProps = FieldWrapperPassThroughProps & {
@ -21,6 +21,7 @@ export const BooleanInput = ({
render={({ field: { onChange, value }, fieldState }) => ( render={({ field: { onChange, value }, fieldState }) => (
<FieldWrapper id={name} {...wrapperProps} error={fieldState.error}> <FieldWrapper id={name} {...wrapperProps} error={fieldState.error}>
<Switch <Switch
data-testid="required-switch"
checked={value} checked={value}
onCheckedChange={onChange} onCheckedChange={onChange}
disabled={disabled} disabled={disabled}