console: create suggested relationship form

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4920
Co-authored-by: mattwhaleblue <97981123+mattwhaleblue@users.noreply.github.com>
Co-authored-by: Vijay Prasanna <11921040+vijayprasanna13@users.noreply.github.com>
GitOrigin-RevId: 5afb347bd5f3bdd2f14e47e279ee96b9bd928d00
This commit is contained in:
Matt Hardman 2022-08-16 06:46:18 +01:00 committed by hasura-bot
parent c6e7dff526
commit 0d34a5b16b
6 changed files with 247 additions and 40 deletions

View File

@ -1,6 +1,8 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { within, userEvent } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
import { DataTarget } from '@/features/Datasources';
import { handlers } from '../../hooks/mocks/handlers.mock';
@ -30,3 +32,23 @@ export const Primary: Story<SuggestedRelationshipProps> = args => (
Primary.args = {
target,
};
export const WithInteraction: Story<SuggestedRelationshipProps> = args => (
<SuggestedRelationships {...args} />
);
WithInteraction.args = Primary.args;
WithInteraction.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = await canvas.findByRole('button', { name: 'Add' });
userEvent.click(button);
const label = await canvas.findByLabelText('Relationship Name');
userEvent.type(label, 's');
expect(label).toBeInTheDocument();
const submitButton = await canvas.findByRole('button', {
name: 'Add Relationship',
});
userEvent.click(submitButton);
};

View File

@ -5,6 +5,7 @@ import { Button } from '@/new-components/Button';
import { DataTarget } from '@/features/Datasources';
import { SuggestedRelationshipForm } from './components';
import { useSuggestedRelationships } from './hooks';
export interface SuggestedRelationshipProps {
@ -15,6 +16,7 @@ export const SuggestedRelationships = ({
target,
}: SuggestedRelationshipProps) => {
const { data, isLoading, isError } = useSuggestedRelationships(target);
const [open, setOpen] = React.useState<string | null>(null);
if (isError) {
return (
@ -45,46 +47,51 @@ export const SuggestedRelationships = ({
</CardedTable.TableHead>
<CardedTable.TableBody>
{data?.map(relationship => (
<CardedTable.TableBodyRow
key={`${relationship.to}-${relationship.from}`}
>
<CardedTable.TableBodyCell>
<Button
size="sm"
onClick={() =>
console.log(
'in the future ill open a form, but now i nothing :('
)
}
>
Add
</Button>
</CardedTable.TableBodyCell>
<CardedTable.TableBodyCell>
<FaTable className="text-sm text-muted mr-xs" />
Local Relation
</CardedTable.TableBodyCell>
<CardedTable.TableBodyCell>
<span className="capitalize">{relationship.type}</span>
</CardedTable.TableBodyCell>
<CardedTable.TableBodyCell>
<FaTable className="text-sm text-muted mr-xs" />
{relationship.from.table}&nbsp;/&nbsp;
<FaColumns className="text-sm text-muted mr-xs" />
{relationship.from.column}
</CardedTable.TableBodyCell>
<CardedTable.TableBodyCell>
<FaArrowRight className="fill-current text-sm text-muted" />
</CardedTable.TableBodyCell>
<CardedTable.TableBodyCell>
<FaTable className="text-sm text-muted mr-xs" />
{relationship.to.table}&nbsp;/&nbsp;
<FaColumns className="text-sm text-muted mr-xs" />
{relationship.to.column}
</CardedTable.TableBodyCell>
</CardedTable.TableBodyRow>
))}
{data.map(relationship => {
// create a unique key
const key = `${relationship.to.table}-${relationship.to.column}-${relationship.from.table}-${relationship.from.column}`;
return (
<CardedTable.TableBodyRow key={key}>
<CardedTable.TableBodyCell>
{open === key ? (
<SuggestedRelationshipForm
key={key}
target={target}
relationship={relationship}
close={() => setOpen(null)}
/>
) : (
<Button size="sm" onClick={() => setOpen(key)}>
Add
</Button>
)}
</CardedTable.TableBodyCell>
<CardedTable.TableBodyCell>
<FaTable className="text-sm text-muted mr-xs" />
Local Relation
</CardedTable.TableBodyCell>
<CardedTable.TableBodyCell>
<span className="capitalize">{relationship.type}</span>
</CardedTable.TableBodyCell>
<CardedTable.TableBodyCell>
<FaTable className="text-sm text-muted mr-xs" />
{relationship.from.table}&nbsp;/&nbsp;
<FaColumns className="text-sm text-muted mr-xs" />
{relationship.from.column}
</CardedTable.TableBodyCell>
<CardedTable.TableBodyCell>
<FaArrowRight className="fill-current text-sm text-muted" />
</CardedTable.TableBodyCell>
<CardedTable.TableBodyCell>
<FaTable className="text-sm text-muted mr-xs" />
{relationship.to.table}&nbsp;/&nbsp;
<FaColumns className="text-sm text-muted mr-xs" />
{relationship.to.column}
</CardedTable.TableBodyCell>
</CardedTable.TableBodyRow>
);
})}
</CardedTable.TableBody>
</CardedTable.Table>
</div>

View File

@ -0,0 +1,89 @@
import React from 'react';
import { Button } from '@/new-components/Button';
import { Form } from '@/new-components/Form';
import { DataTarget } from '@/features/Datasources';
import { TableRelationship } from '@/features/MetadataAPI';
import { schema, useSubmit } from '../hooks';
export interface SuggestedRelationshipProps {
target: DataTarget;
}
interface SuggestedRelationshipFormProps {
target: DataTarget;
relationship: Omit<TableRelationship, 'name' | 'comment'>;
close: () => void;
}
const CloseIcon = () => (
<svg
className="fill-current cursor-pointer w-4 h-4 text-muted hover:text-gray-900"
stroke="currentColor"
fill="currentColor"
strokeWidth="0"
viewBox="0 0 512 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z" />
</svg>
);
export const SuggestedRelationshipForm = ({
target,
relationship,
close,
}: SuggestedRelationshipFormProps) => {
const { submit, isLoading } = useSubmit();
const handleSubmit = async ({
relationshipName,
}: Record<string, unknown>) => {
try {
await submit({
relationshipName: relationshipName as string,
target,
relationship,
});
close();
} catch (error) {
console.error('Error while adding the relationship', error);
}
};
return (
<Form
onSubmit={handleSubmit}
schema={schema}
options={{
defaultValues: {
relationshipName: relationship.to.table,
},
}}
className="p-0"
>
{options => (
<div className="flex items-center space-x-1.5 bg-white">
<label htmlFor="relationshipName" className="sr-only">
Relationship Name
</label>
<input
id="relationshipName"
type="text"
className="block w-full 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="Relationship Name..."
{...options.register('relationshipName')}
/>
<Button type="submit" mode="primary" isLoading={isLoading}>
Add Relationship
</Button>
<button onClick={close} aria-label="close">
<CloseIcon />
</button>
</div>
)}
</Form>
);
};

View File

@ -0,0 +1 @@
export * from './AddSuggestedRelationshipForm';

View File

@ -1 +1,2 @@
export * from './useSuggestedRelationships';
export * from './useSubmitForm';

View File

@ -0,0 +1,87 @@
import { z } from 'zod';
import { useFireNotification } from '@/new-components/Notifications';
import { DataTarget } from '@/features/Datasources';
import {
allowedMetadataTypes,
TableRelationship,
useMetadataMigration,
} from '@/features/MetadataAPI';
export const schema = z.object({
relationshipName: z
.string()
.min(3, 'Relationship name must be at least 3 characters long'),
});
export type Schema = z.infer<typeof schema>;
export interface SuggestedRelationshipProps {
target: DataTarget;
}
interface UseSubmitArgs {
relationshipName: string;
target: DataTarget;
relationship: Omit<TableRelationship, 'name' | 'comment'>;
}
export const useSubmit = () => {
const { fireNotification } = useFireNotification();
const mutation = useMetadataMigration({
onSuccess: () => {
fireNotification({
title: 'Success!',
message: 'Relationship added successfully',
type: 'success',
});
},
onError: () => {
fireNotification({
title: 'Error',
message: 'Error while adding the relationship',
type: 'error',
});
},
});
const submit = async ({
relationshipName,
target,
relationship,
}: UseSubmitArgs) => {
if (relationship.type === 'object') {
const query = {
type: 'pg_create_object_relationship' as allowedMetadataTypes,
args: {
table: target.table,
name: relationshipName,
source: target.database,
using: {
foreign_key_constraint_on: relationship.from.column,
},
},
};
return mutation.mutate({ query });
}
const query = {
type: 'pg_create_array_relationship' as allowedMetadataTypes,
args: {
table: target.table,
name: relationshipName,
source: target.database,
using: {
foreign_key_constraint_on: {
table: relationship.to.table,
columns: relationship.to.column,
},
},
},
};
return mutation.mutate({ query });
};
return { submit, ...mutation };
};