mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
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:
parent
5e6c93e7aa
commit
ef7d8b7d31
@ -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>;
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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
|
||||
<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}
|
||||
|
@ -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
|
||||
<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}
|
||||
|
@ -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;
|
@ -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'];
|
||||
|
@ -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())
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user