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'));
userEvent.click(c.getByText('Save'));
await userEvent.click(c.getByText('Add Parameter'));
await userEvent.click(c.getByText('Save'));
const errorMessages = [
'Native Query Name is required',
@ -49,35 +49,35 @@ const fillAndSubmitForm = async ({
}
// 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'),
'my_native_query'
);
userEvent.type(
await userEvent.type(
c.getByPlaceholderText('A description of this logical model'),
'a description'
);
//select postgres from the database dropdown
userEvent.selectOptions(
await userEvent.selectOptions(
await c.findByLabelText('Database', undefined, { timeout: 3000 }),
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');
userEvent.type(c.getByPlaceholderText('Default Value'), 'default');
userEvent.click(c.getByTestId('required-switch'));
await userEvent.type(c.getByPlaceholderText('Parameter Name'), 'param1');
await userEvent.type(c.getByPlaceholderText('Default Value'), 'default');
await userEvent.click(c.getByTestId('required-switch'));
userEvent.selectOptions(
await userEvent.selectOptions(
await c.findByLabelText('Query Return Type', undefined, { timeout: 3000 }),
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 => {

View File

@ -8,17 +8,21 @@ import {
useConsoleForm,
} from '../../../../new-components/Form';
// 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 { Feature } from '../../../DataSource';
import { useMetadata } from '../../../hasura-metadata-api';
import { useSupportedDataTypes } from '../../hooks/useSupportedDataTypes';
import { useTrackNativeQuery } from '../../hooks/useTrackNativeQuery';
import { LogicalModelWidget } from '../LogicalModelWidget/LogicalModelWidget';
import { ArgumentsField } from './components/ArgumentsField';
import { PageWrapper } from './components/PageWrapper';
import { SqlEditorField } from './components/SqlEditorField';
import { schema } from './schema';
import { NativeQueryForm } from './types';
import { transformFormOutputToMetadata } from './utils';
import { LogicalModelWidget } from '../LogicalModelWidget/LogicalModelWidget';
import { Driver, drivers } from '../../../../dataSources';
type AddNativeQueryProps = {
defaultFormValues?: Partial<NativeQueryForm>;
@ -38,7 +42,11 @@ export const AddNativeQuery = ({
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));
});
@ -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 (
<PageWrapper pathname={pathname} push={push}>
<Form onSubmit={handleFormSubmit}>
@ -123,7 +157,14 @@ export const AddNativeQuery = ({
placeholder="Select a database..."
/>
</div>
<ArgumentsField />
{isIntrospectionLoading ? (
<div>
<Skeleton />
<Skeleton />
</div>
) : (
<ArgumentsField types={typeOptions} />
)}
<SqlEditorField />
<div className="flex w-full">
{/* Logical Model Dropdown */}

View File

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

View File

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

View File

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

View File

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