console: storybook components for list native query and list logical models

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8858
GitOrigin-RevId: 9d659f1eb5af52d8776f318f7c843680102c8ede
This commit is contained in:
Matthew Goodwin 2023-04-24 17:18:53 -05:00 committed by hasura-bot
parent 059fe14154
commit aab5a2082b
8 changed files with 547 additions and 0 deletions

View File

@ -0,0 +1,37 @@
import { Table, flexRender } from '@tanstack/react-table';
import React from 'react';
import { CardedTable } from '../../../new-components/CardedTable';
export function CardedTableFromReactTable<T>({ table }: { table: Table<T> }) {
return (
<CardedTable.Table>
<CardedTable.TableHead>
{table.getHeaderGroups().map(headerGroup => (
<CardedTable.TableHeadRow key={headerGroup.id}>
{headerGroup.headers.map(header => (
<CardedTable.TableHeadCell key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</CardedTable.TableHeadCell>
))}
</CardedTable.TableHeadRow>
))}
</CardedTable.TableHead>
<CardedTable.TableBody>
{table.getRowModel().rows.map(row => (
<CardedTable.TableBodyRow key={row.id}>
{row.getVisibleCells().map(cell => (
<CardedTable.TableBodyCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</CardedTable.TableBodyCell>
))}
</CardedTable.TableBodyRow>
))}
</CardedTable.TableBody>
</CardedTable.Table>
);
}

View File

@ -0,0 +1,21 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { ReactQueryDecorator } from '../../../storybook/decorators/react-query';
import { handlers } from '../mocks';
import { ListLogicalModels } from './ListLogicalModels';
export default {
component: ListLogicalModels,
decorators: [ReactQueryDecorator()],
parameters: {
msw: handlers(),
},
argTypes: {
dataSourceName: { defaultValue: 'postgres' },
onEditClick: { action: 'onEdit' },
onRemoveClick: { action: 'onRemove' },
},
} as ComponentMeta<typeof ListLogicalModels>;
export const Basic: ComponentStory<typeof ListLogicalModels> = args => {
return <ListLogicalModels {...args} />;
};

View File

@ -0,0 +1,65 @@
import {
createColumnHelper,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table';
import React from 'react';
import { Button } from '../../../new-components/Button';
import { useMetadata } from '../../hasura-metadata-api';
import { LogicalModel } from '../../hasura-metadata-types';
import { CardedTableFromReactTable } from './CardedTableFromReactTable';
import Skeleton from 'react-loading-skeleton';
const columnHelper = createColumnHelper<LogicalModel>();
export const ListLogicalModels = ({
dataSourceName,
onEditClick,
onRemoveClick,
}: {
dataSourceName: string;
onEditClick: (model: LogicalModel) => void;
onRemoveClick: (model: LogicalModel) => void;
}) => {
const { data: logicalModels, isLoading } = useMetadata(
m => m.metadata.sources.find(s => s.name === dataSourceName)?.logical_models
);
const columns = React.useCallback(
() => [
columnHelper.accessor('name', {
id: 'name',
cell: info => <span>{info.getValue()}</span>,
header: info => <span>Name</span>,
}),
columnHelper.display({
id: 'actions',
header: 'Actions',
cell: ({ cell, row }) => (
<div className="flex flex-row gap-2">
<Button onClick={() => onEditClick(row.original)}>Edit</Button>
<Button
mode="destructive"
onClick={() => onRemoveClick(row.original)}
>
Remove
</Button>
</div>
),
}),
],
[]
);
const table = useReactTable({
data: logicalModels ?? [],
columns: columns(),
getCoreRowModel: getCoreRowModel(),
});
if (isLoading) {
return <Skeleton count={5} height={30} />;
}
return <CardedTableFromReactTable table={table} />;
};

View File

@ -0,0 +1,22 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { ReactQueryDecorator } from '../../../storybook/decorators/react-query';
import { handlers } from '../mocks';
import { ListNativeQueries } from './ListNativeQueries';
export default {
component: ListNativeQueries,
decorators: [ReactQueryDecorator()],
parameters: {
msw: handlers(),
},
argTypes: {
dataSourceName: { defaultValue: 'postgres' },
onEditClick: { action: 'onEdit' },
onRemoveClick: { action: 'onRemove' },
},
} as ComponentMeta<typeof ListNativeQueries>;
export const Basic: ComponentStory<typeof ListNativeQueries> = args => {
return <ListNativeQueries {...args} />;
};

View File

@ -0,0 +1,74 @@
import {
createColumnHelper,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table';
import React from 'react';
import Skeleton from 'react-loading-skeleton';
import { Button } from '../../../new-components/Button';
import { useMetadata } from '../../hasura-metadata-api';
import { NativeQuery } from '../../hasura-metadata-types';
import { CardedTableFromReactTable } from './CardedTableFromReactTable';
const columnHelper = createColumnHelper<NativeQuery>();
export const ListNativeQueries = ({
dataSourceName,
onEditClick,
onRemoveClick,
}: {
dataSourceName: string;
onEditClick: (model: NativeQuery) => void;
onRemoveClick: (model: NativeQuery) => void;
}) => {
const { data: nativeQueries, isLoading } = useMetadata(
m => m.metadata.sources.find(s => s.name === dataSourceName)?.native_queries
);
const columns = React.useCallback(
() => [
columnHelper.accessor('root_field_name', {
id: 'name',
cell: info => <span>{info.getValue()}</span>,
header: info => <span>Name</span>,
}),
columnHelper.display({
id: 'database',
cell: () => <span>{dataSourceName}</span>,
header: 'Database',
}),
columnHelper.accessor('returns', {
id: 'logical_model',
cell: info => <span>{info.getValue()}</span>,
header: info => <span>Logical Model</span>,
}),
columnHelper.display({
id: 'actions',
header: 'Actions',
cell: ({ cell, row }) => (
<div className="flex flex-row gap-2">
<Button onClick={() => onEditClick(row.original)}>Edit</Button>
<Button
mode="destructive"
onClick={() => onRemoveClick(row.original)}
>
Remove
</Button>
</div>
),
}),
],
[]
);
const table = useReactTable({
data: nativeQueries ?? [],
columns: columns(),
getCoreRowModel: getCoreRowModel(),
});
if (isLoading) {
return <Skeleton count={5} height={30} />;
}
return <CardedTableFromReactTable table={table} />;
};

View File

@ -0,0 +1,313 @@
import { rest } from 'msw';
const metadata = {
resource_version: 528,
metadata: {
version: 3,
sources: [
{
name: 'mssql',
kind: 'mssql',
tables: [],
configuration: {
connection_info: {
connection_string:
'Driver={ODBC Driver 18 for SQL Server};Server=mssql,1433;Database=Chinook;UID=sa;PWD=Password!;Encrypt=yes;TrustServerCertificate=yes;ConnectionTimeout=30;',
pool_settings: {
idle_timeout: 5,
max_connections: null,
total_max_connections: null,
},
},
},
},
{
name: 'mysql',
kind: 'mysqlgdc',
tables: [
{
table: ['Chinook', 'Album'],
array_relationships: [
{
name: 'Chinook_Album_Chinook_Tracks',
using: {
foreign_key_constraint_on: {
column: 'AlbumId',
table: ['Chinook', 'Track'],
},
},
},
],
},
{
table: ['Chinook', 'Customer'],
},
{
table: ['Chinook', 'Genre'],
},
{
table: ['Chinook', 'Invoice'],
},
{
table: ['Chinook', 'InvoiceLine'],
},
{
table: ['Chinook', 'MediaType'],
},
{
table: ['Chinook', 'Playlist'],
},
{
table: ['Chinook', 'Track'],
object_relationships: [
{
name: 'Chinook_Track_Chinook_Album',
using: {
foreign_key_constraint_on: 'AlbumId',
},
},
],
},
],
configuration: {
template: null,
timeout: null,
value: {
jdbc_url:
'jdbc:mysql://mysql:3306/Chinook?allowMultiQueries=true&user=root&password=pass',
},
},
customization: {
root_fields: {
namespace: 'mysql',
},
type_names: {},
},
},
{
name: 'postgres',
kind: 'postgres',
tables: [
{
table: {
name: 'directory',
schema: '_fuzzysearch',
},
},
{
table: {
name: 'Album',
schema: 'public',
},
},
{
table: {
name: 'Customer',
schema: 'public',
},
array_relationships: [
{
name: 'Customer_Invoices',
using: {
foreign_key_constraint_on: {
column: 'CustomerId',
table: {
name: 'Invoice',
schema: 'public',
},
},
},
},
],
},
{
table: {
name: 'Employee',
schema: 'public',
},
},
{
table: {
name: 'Genre',
schema: 'public',
},
},
{
table: {
name: 'Invoice',
schema: 'public',
},
object_relationships: [
{
name: 'Invoice_Customer',
using: {
foreign_key_constraint_on: 'CustomerId',
},
},
],
},
{
table: {
name: 'InvoiceLine',
schema: 'public',
},
object_relationships: [
{
name: 'InvoiceLine_Invoice',
using: {
foreign_key_constraint_on: 'InvoiceId',
},
},
{
name: 'InvoiceLine_Track',
using: {
foreign_key_constraint_on: 'TrackId',
},
},
],
},
{
table: {
name: 'MediaType',
schema: 'public',
},
},
{
table: {
name: 'Playlist',
schema: 'public',
},
},
{
table: {
name: 'PlaylistTrack',
schema: 'public',
},
},
{
table: {
name: 'Track',
schema: 'public',
},
array_relationships: [
{
name: 'Track_InvoiceLines',
using: {
foreign_key_constraint_on: {
column: 'TrackId',
table: {
name: 'InvoiceLine',
schema: 'public',
},
},
},
},
],
},
],
native_queries: [
{
arguments: {},
code: "SELECT * FROM (VALUES ('hello', 'world'), ('welcome', 'friend')) as t(\"one\", \"two\")\n",
returns: 'hello_world',
root_field_name: 'hello_world_function',
},
{
arguments: {},
code: "SELECT * FROM (VALUES ('hello', 'world2'), ('welcome', 'friend')) as t(\"one\", \"two\")\n",
returns: 'hello_world2',
root_field_name: 'hello_world_function2',
},
],
logical_models: [
{
fields: [
{
name: 'one',
nullable: false,
type: 'text',
},
{
name: 'two',
nullable: false,
type: 'text',
},
],
name: 'hello_world',
},
{
fields: [
{
name: 'one',
nullable: false,
type: 'text',
},
{
name: 'two',
nullable: false,
type: 'text',
},
],
name: 'hello_world2',
},
],
configuration: {
connection_info: {
database_url: 'postgres://postgres:pass@postgres:5432/chinook',
isolation_level: 'read-committed',
use_prepared_statements: false,
},
},
customization: {
root_fields: {
namespace: 'postgres',
},
},
},
{
name: 'sqlite',
kind: 'sqlite',
tables: [],
configuration: {
template: null,
timeout: null,
value: {
db: '/sqlite.db',
explicit_main_schema: false,
include_sqlite_meta_tables: false,
},
},
customization: {
root_fields: {
namespace: 'sqlite',
},
type_names: {},
},
},
],
backend_configs: {
dataconnector: {
mysqlgdc: {
uri: 'http://data-connector-agent:8081/api/v1/mysql',
},
sqlite: {
uri: 'http://sqlite:8100',
},
},
},
},
};
export const handlers = () => [
rest.post('http://localhost:8080/v1/metadata', (req, res, ctx) => {
const requestBody = req.body as Record<string, any>;
if (requestBody.type === 'export_metadata') {
ctx.delay();
return res(ctx.json(metadata));
}
return res(ctx.json({}));
}),
];

View File

@ -52,11 +52,24 @@ export type MetadataFunction = {
};
};
export type LogicalModel = {
fields: { name: string; nullable: boolean; type: string }[];
name: string;
};
export type NativeQuery = {
arguments: Record<string, any>;
code: string;
returns: string;
root_field_name: string;
};
export type Source = {
name: string;
tables: MetadataTable[];
customization?: SourceCustomization;
functions?: MetadataFunction[];
logical_models?: LogicalModel[];
native_queries?: NativeQuery[];
} & (
| {
kind: 'postgres';
@ -81,6 +94,8 @@ export type Source = {
*/
kind: Exclude<SupportedDrivers, NativeDrivers>;
configuration: unknown;
logical_models?: never;
native_queries?: never;
}
);