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 = {
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user