console: UI improvements to snowflake & Athena tracking section

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8424
Co-authored-by: Julian <843342+okjulian@users.noreply.github.com>
GitOrigin-RevId: 8b514a8e40c10c4546a1d3d454daea7217c9cd3b
This commit is contained in:
Vijay Prasanna 2023-03-24 13:54:01 +05:30 committed by hasura-bot
parent 5e6c93e7aa
commit ef7d8b7d31
7 changed files with 205 additions and 21 deletions

View File

@ -4,6 +4,7 @@ import { useTables } from './hooks/useTables';
import { TrackedTables } from './components/TrackedTables';
import { UntrackedTables } from './components/UntrackedTables';
import Skeleton from 'react-loading-skeleton';
const classNames = {
selected:
@ -23,7 +24,12 @@ export const TrackTables = ({ dataSourceName }: Props) => {
dataSourceName,
});
if (isLoading) return <div className="px-md">Loading...</div>;
if (isLoading)
return (
<div className="px-md">
<Skeleton count={8} height={25} />
</div>
);
if (!data) return <div className="px-md">Something went wrong</div>;

View File

@ -0,0 +1,36 @@
import { FaSearch } from 'react-icons/fa';
import { z } from 'zod';
import { Button } from '../../../../new-components/Button';
import { InputField, SimpleForm } from '../../../../new-components/Form';
type SearchBarProps = {
onSubmit: (searchText: string) => void;
};
const schema = z.object({
searchText: z.string().optional(),
});
export const SearchBar = (props: SearchBarProps) => {
const { onSubmit } = props;
return (
<SimpleForm
schema={schema}
onSubmit={data => {
onSubmit(data.searchText ?? '');
}}
>
<div className="flex gap-2">
<InputField
name="searchText"
icon={<FaSearch />}
iconPosition="start"
noErrorPlaceholder
clearButton
/>
<Button type="submit">Search</Button>
</div>
</SimpleForm>
);
};

View File

@ -2,10 +2,19 @@ import { useTrackTable } from '../..';
import { Button } from '../../../../new-components/Button';
import { CardedTable } from '../../../../new-components/CardedTable';
import { IndicatorCard } from '../../../../new-components/IndicatorCard';
import React from 'react';
import React, { useState } from 'react';
import { useCheckRows } from '../hooks/useCheckRows';
import { TrackableTable } from '../types';
import { TableRow } from './TableRow';
import {
DEFAULT_PAGE_NUMBER,
DEFAULT_PAGE_SIZE,
DEFAULT_PAGE_SIZES,
} from '../constants';
import { FaAngleLeft, FaAngleRight } from 'react-icons/fa';
import { paginate, search } from '../utils';
import { SearchBar } from './SearchBar';
import { Badge } from '../../../../new-components/Badge';
interface TrackTableProps {
dataSourceName: string;
@ -13,7 +22,10 @@ interface TrackTableProps {
}
export const TrackedTables = (props: TrackTableProps) => {
const filteredTables = props.tables;
const [pageNumber, setPageNumber] = useState(DEFAULT_PAGE_NUMBER);
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
const [searchText, setSearchText] = useState('');
const filteredTables = search(props.tables, searchText);
const checkboxRef = React.useRef<HTMLInputElement>(null);
@ -25,7 +37,7 @@ export const TrackedTables = (props: TrackTableProps) => {
checkboxRef.current.indeterminate = inputStatus === 'indeterminate';
}, [inputStatus]);
const { untrackTables } = useTrackTable(props.dataSourceName);
const { untrackTables, loading } = useTrackTable(props.dataSourceName);
const onClick = () => {
untrackTables(
@ -34,7 +46,7 @@ export const TrackedTables = (props: TrackTableProps) => {
reset();
};
if (!filteredTables.length) {
if (!props.tables.length) {
return (
<div className="space-y-4">
<IndicatorCard>No tracked tables found</IndicatorCard>
@ -44,10 +56,51 @@ export const TrackedTables = (props: TrackTableProps) => {
return (
<div className="space-y-4">
<div className="space-x-4">
<Button mode="primary" disabled={!checkedIds.length} onClick={onClick}>
Untrack Selected
</Button>
<div className="flex justify-between space-x-4">
<div className="flex gap-5">
<Button
mode="primary"
disabled={!checkedIds.length}
onClick={onClick}
isLoading={loading}
loadingText="Please Wait"
>
Untrack Selected ({checkedIds.length})
</Button>
<span className="border-r border-slate-300"></span>
<div className="flex gap-2">
<SearchBar onSubmit={data => setSearchText(data)} />
{searchText.length ? (
<Badge>{filteredTables.length} results found</Badge>
) : null}
</div>{' '}
</div>
<div className="flex gap-1">
<Button
icon={<FaAngleLeft />}
onClick={() => setPageNumber(pageNumber - 1)}
disabled={pageNumber === 1}
/>
<select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value));
}}
className="block w-full max-w-xl h-8 min-h-full shadow-sm rounded pl-3 pr-6 py-0.5 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"
>
{DEFAULT_PAGE_SIZES.map(_pageSize => (
<option key={_pageSize} value={_pageSize}>
Show {_pageSize} tables
</option>
))}
</select>
<Button
icon={<FaAngleRight />}
onClick={() => setPageNumber(pageNumber + 1)}
disabled={pageNumber >= filteredTables.length / pageSize}
/>
</div>
</div>
<CardedTable.Table>
<CardedTable.TableHead>
@ -69,7 +122,7 @@ export const TrackedTables = (props: TrackTableProps) => {
</CardedTable.TableHead>
<CardedTable.TableBody>
{filteredTables.map(table => (
{paginate(filteredTables, pageSize, pageNumber).map(table => (
<TableRow
key={table.id}
table={table}

View File

@ -1,14 +1,18 @@
import React from 'react';
import React, { useState } from 'react';
import { Button } from '../../../../new-components/Button';
import { CardedTable } from '../../../../new-components/CardedTable';
import { IndicatorCard } from '../../../../new-components/IndicatorCard';
// import { useTables } from '../hooks/useTables';
import { useTrackTable } from '../..';
import { useCheckRows } from '../hooks/useCheckRows';
import { TrackableTable } from '../types';
import { TableRow } from './TableRow';
import { DEFAULT_PAGE_SIZES } from '../constants';
import { FaAngleLeft, FaAngleRight } from 'react-icons/fa';
import { DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE } from '../constants';
import { paginate, search } from '../utils';
import { SearchBar } from './SearchBar';
import { Badge } from '../../../../new-components/Badge';
interface TrackTableProps {
dataSourceName: string;
@ -16,7 +20,10 @@ interface TrackTableProps {
}
export const UntrackedTables = (props: TrackTableProps) => {
const filteredTables = props.tables;
const [pageNumber, setPageNumber] = useState(DEFAULT_PAGE_NUMBER);
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
const [searchText, setSearchText] = useState('');
const filteredTables = search(props.tables, searchText);
const checkboxRef = React.useRef<HTMLInputElement>(null);
const { checkedIds, onCheck, allChecked, toggleAll, reset, inputStatus } =
@ -26,14 +33,14 @@ export const UntrackedTables = (props: TrackTableProps) => {
checkboxRef.current.indeterminate = inputStatus === 'indeterminate';
}, [inputStatus]);
const { trackTables } = useTrackTable(props.dataSourceName);
const { trackTables, loading } = useTrackTable(props.dataSourceName);
const onClick = () => {
trackTables(props.tables.filter(({ name }) => checkedIds.includes(name)));
reset();
};
if (!filteredTables.length) {
if (!props.tables.length) {
return (
<div className="space-y-4">
<IndicatorCard>No untracked tables found</IndicatorCard>
@ -43,11 +50,55 @@ export const UntrackedTables = (props: TrackTableProps) => {
return (
<div className="space-y-4">
<div className="space-x-4">
<Button mode="primary" disabled={!checkedIds.length} onClick={onClick}>
Track Selected
</Button>
<div className="flex justify-between space-x-4">
<div className="flex gap-5">
<Button
mode="primary"
disabled={!checkedIds.length}
onClick={onClick}
isLoading={loading}
loadingText="Please Wait"
>
Track Selected ({checkedIds.length})
</Button>
<span className="border-r border-slate-300"></span>
<div className="flex gap-2">
<SearchBar onSubmit={data => setSearchText(data)} />
{searchText.length ? (
<Badge>{filteredTables.length} results found</Badge>
) : null}
</div>
</div>
<div className="flex gap-1">
<Button
icon={<FaAngleLeft />}
onClick={() => setPageNumber(pageNumber - 1)}
disabled={pageNumber === 1}
/>
<select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value));
}}
className="block w-full max-w-xl h-8 min-h-full shadow-sm rounded pl-3 pr-6 py-0.5 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"
>
{DEFAULT_PAGE_SIZES.map(_pageSize => (
<option key={_pageSize} value={_pageSize}>
Show {_pageSize} tables
</option>
))}
</select>
<Button
icon={<FaAngleRight />}
onClick={() => setPageNumber(pageNumber + 1)}
disabled={pageNumber >= filteredTables.length / pageSize}
/>
</div>
</div>
<CardedTable.Table>
<CardedTable.TableHead>
<CardedTable.TableHeadRow>
@ -68,7 +119,7 @@ export const UntrackedTables = (props: TrackTableProps) => {
</CardedTable.TableHead>
<CardedTable.TableBody>
{filteredTables.map(table => (
{paginate(filteredTables, pageSize, pageNumber).map(table => (
<TableRow
key={table.id}
table={table}

View File

@ -0,0 +1,3 @@
export const DEFAULT_PAGE_SIZES = [10, 20, 30, 40, 50, 100];
export const DEFAULT_PAGE_NUMBER = 1;
export const DEFAULT_PAGE_SIZE = 10;

View File

@ -102,9 +102,25 @@ const runSQLResponse: RunSQLResponse = {
['PlaylistTrack', 'public', 'BASE TABLE'],
['Genre', 'public', 'BASE TABLE'],
['MediaType', 'public', 'BASE TABLE'],
['Country', 'public', 'BASE TABLE'],
['State', 'public', 'BASE TABLE'],
['City', 'public', 'BASE TABLE'],
['CustomerList', 'public', 'VIEW'],
['InvoiceLineView', 'public', 'VIEW'],
['InvoiceView', 'public', 'VIEW'],
['TrackView', 'public', 'VIEW'],
...createTables(1700),
],
};
function createTables(count: number) {
const tables = [];
for (let i = 0; i < count; i++) {
tables.push([`table_${i}`, 'public', 'BASE TABLE']);
}
return tables;
}
export const handlers = () => [
rest.post(`http://localhost:8080/v1/metadata`, async (req, res, ctx) => {
const body = (await req.json()) as TMigration['query'];

View File

@ -0,0 +1,19 @@
import { TrackableTable } from './types';
export const paginate = <T>(
array: T[],
page_size: number,
page_number: number
): T[] => {
// human-readable page numbers usually start with 1, so we reduce 1 in the first argument
return array.slice((page_number - 1) * page_size, page_number * page_size);
};
export const search = (tables: TrackableTable[], searchText: string) => {
if (!searchText.length) return tables;
console.log(searchText, tables);
return tables.filter(table =>
table.name.toLowerCase().includes(searchText.toLowerCase())
);
};