mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
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:
parent
059fe14154
commit
aab5a2082b
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -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} />;
|
||||||
|
};
|
@ -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} />;
|
||||||
|
};
|
@ -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} />;
|
||||||
|
};
|
@ -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} />;
|
||||||
|
};
|
@ -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({}));
|
||||||
|
}),
|
||||||
|
];
|
@ -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 = {
|
export type Source = {
|
||||||
name: string;
|
name: string;
|
||||||
tables: MetadataTable[];
|
tables: MetadataTable[];
|
||||||
customization?: SourceCustomization;
|
customization?: SourceCustomization;
|
||||||
functions?: MetadataFunction[];
|
functions?: MetadataFunction[];
|
||||||
|
logical_models?: LogicalModel[];
|
||||||
|
native_queries?: NativeQuery[];
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
kind: 'postgres';
|
kind: 'postgres';
|
||||||
@ -81,6 +94,8 @@ export type Source = {
|
|||||||
*/
|
*/
|
||||||
kind: Exclude<SupportedDrivers, NativeDrivers>;
|
kind: Exclude<SupportedDrivers, NativeDrivers>;
|
||||||
configuration: unknown;
|
configuration: unknown;
|
||||||
|
logical_models?: never;
|
||||||
|
native_queries?: never;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user