mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
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:
parent
605f0281dc
commit
1d91f633c3
@ -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 => {
|
||||
|
@ -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 */}
|
||||
|
@ -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({
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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 }))}
|
||||
|
@ -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>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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';
|
||||
|
||||
|
@ -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}
|
Loading…
Reference in New Issue
Block a user