Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ui): use table skeleton while flags or segments are loading #3682

Merged
merged 3 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 1 addition & 22 deletions ui/src/app/flags/Flags.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,20 @@
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { selectReadonly } from '~/app/meta/metaSlice';
import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice';
import { useError } from '~/data/hooks/error';
import { useListFlagsQuery } from './flagsApi';
import { Plus } from 'lucide-react';
import { Button } from '~/components/ui/button';
import FlagTable from '~/components/flags/FlagTable';
import Guide from '~/components/ui/guide';
import { PageHeader } from '~/components/ui/page';

export default function Flags() {
const namespace = useSelector(selectCurrentNamespace);
const path = `/namespaces/${namespace.key}/flags`;

const { data, error } = useListFlagsQuery(namespace.key);
const flags = data?.flags || [];

const navigate = useNavigate();
const { setError } = useError();

const readOnly = useSelector(selectReadonly);

useEffect(() => {
if (error) {
setError(error);
}
}, [error, setError]);

return (
<>
<PageHeader title="Flags">
Expand All @@ -38,14 +24,7 @@ export default function Flags() {
</Button>
</PageHeader>
<div className="flex flex-col gap-1 space-y-2 py-2">
{flags && flags.length > 0 ? (
<FlagTable flags={flags} />
) : (
<Guide className="mt-6">
Flags enable you to control and roll out new functionality
dynamically. Create a new flag to get started.
</Guide>
)}
<FlagTable namespace={namespace} />
</div>
</>
);
Expand Down
22 changes: 1 addition & 21 deletions ui/src/app/segments/Segments.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
import { Plus } from 'lucide-react';
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { selectReadonly } from '~/app/meta/metaSlice';
import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice';
import { useListSegmentsQuery } from '~/app/segments/segmentsApi';
import SegmentTable from '~/components/segments/SegmentTable';
import { Button } from '~/components/ui/button';
import Guide from '~/components/ui/guide';
import { PageHeader } from '~/components/ui/page';
import { useError } from '~/data/hooks/error';

export default function Segments() {
const namespace = useSelector(selectCurrentNamespace);

const path = `/namespaces/${namespace.key}/segments`;

const { data, error } = useListSegmentsQuery(namespace.key);
const segments = data?.segments || [];
const navigate = useNavigate();
const { setError } = useError();

const readOnly = useSelector(selectReadonly);

useEffect(() => {
if (error) {
setError(error);
return;
}
}, [error, setError]);

return (
<>
<PageHeader title="Segments">
Expand All @@ -39,13 +25,7 @@ export default function Segments() {
</Button>
</PageHeader>
<div className="flex flex-col gap-1 space-y-2 py-2">
{segments && segments.length > 0 ? (
<SegmentTable segments={segments} />
) : (
<Guide className="mt-6">
Segments enable request targeting based on defined criteria.
</Guide>
)}
<SegmentTable namespace={namespace} />
</div>
</>
);
Expand Down
156 changes: 90 additions & 66 deletions ui/src/components/flags/FlagTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,35 @@ import {
Row,
useReactTable
} from '@tanstack/react-table';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice';
import { DataTablePagination } from '~/components/ui/table-pagination';
import { useTimezone } from '~/data/hooks/timezone';
import { FlagType, flagTypeToLabel, IFlag } from '~/types/Flag';
import { selectSorting, setSorting } from '~/app/flags/flagsApi';
import {
selectSorting,
setSorting,
useListFlagsQuery
} from '~/app/flags/flagsApi';
import { cn } from '~/lib/utils';
import { Badge } from '~/components/ui/badge';
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
import { Search } from '~/components/ui/search';
import { DataTableViewOptions } from '~/components/ui/table-view-options';
import Guide from '~/components/ui/guide';
import { VariableIcon, ToggleLeftIcon } from 'lucide-react';
import { useError } from '~/data/hooks/error';
import { INamespaceBase } from '~/types/Namespace';
import { TableSkeleton } from '~/components/ui/table-skeleton';

type FlagTableProps = {
flags: IFlag[];
namespace: INamespaceBase;
};

function FlagDetails({ item }: { item: IFlag }) {
const enabled = item.type === FlagType.BOOLEAN || item.enabled;
const { inTimezone } = useTimezone();
return (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<Badge variant={enabled ? 'enabled' : 'muted'}>
Expand All @@ -44,31 +51,80 @@ function FlagDetails({ item }: { item: IFlag }) {
{flagTypeToLabel(item.type)}
</span>
<span className="hidden sm:block">•</span>
<span className="hidden sm:block">
<time className="hidden sm:block" title={inTimezone(item.createdAt)}>
Created{' '}
{formatDistanceToNowStrict(parseISO(item.createdAt), {
addSuffix: true
})}
</span>
</time>
<span>•</span>
<span>
<time title={inTimezone(item.updatedAt)}>
Updated{' '}
{formatDistanceToNowStrict(parseISO(item.updatedAt), {
addSuffix: true
})}
</span>
</time>
</div>
);
}

const columnHelper = createColumnHelper<IFlag>();

const columns = [
columnHelper.accessor('key', {
header: 'Key',
cell: (info) => info.getValue()
}),
columnHelper.accessor('name', {
header: 'Name',
cell: (info) => info.getValue()
}),
columnHelper.accessor('description', {
header: 'Description',
enableSorting: false,
cell: (info) => info.getValue()
}),
columnHelper.accessor((row) => row, {
header: 'Evaluation',
enableSorting: false,
cell: (info) => (info.getValue() ? 'Enabled' : 'Disabled')
}),
columnHelper.accessor('type', {
header: 'Type',
cell: (info) => flagTypeToLabel(info.getValue())
}),
columnHelper.accessor((row) => row.createdAt, {
header: 'Created',
id: 'createdAt',
sortingFn: (
rowA: Row<IFlag>,
rowB: Row<IFlag>,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_columnId: string
): number =>
new Date(rowA.original.createdAt) < new Date(rowB.original.createdAt)
? 1
: -1
}),
columnHelper.accessor((row) => row.updatedAt, {
header: 'Updated',
id: 'updatedAt',
sortingFn: (
rowA: Row<IFlag>,
rowB: Row<IFlag>,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_columnId: string
): number =>
new Date(rowA.original.updatedAt) < new Date(rowB.original.updatedAt)
? 1
: -1
})
];

export default function FlagTable(props: FlagTableProps) {
const { flags } = props;
const navigate = useNavigate();

const dispatch = useDispatch();

const namespace = useSelector(selectCurrentNamespace);
const { inTimezone } = useTimezone();
const namespace = props.namespace;

const path = `/namespaces/${namespace.key}/flags`;

Expand All @@ -79,60 +135,18 @@ export default function FlagTable(props: FlagTableProps) {

const [filter, setFilter] = useState<string>('');

const columnHelper = createColumnHelper<IFlag>();
const sorting = useSelector(selectSorting);

const columns = [
columnHelper.accessor('key', {
header: 'Key',
cell: (info) => info.getValue()
}),
columnHelper.accessor('name', {
header: 'Name',
cell: (info) => info.getValue()
}),
columnHelper.accessor('description', {
header: 'Description',
enableSorting: false,
cell: (info) => info.getValue()
}),
columnHelper.accessor((row) => row, {
header: 'Evaluation',
enableSorting: false,
cell: (info) => (info.getValue() ? 'Enabled' : 'Disabled')
}),
columnHelper.accessor('type', {
header: 'Type',
cell: (info) => flagTypeToLabel(info.getValue())
}),
columnHelper.accessor((row) => inTimezone(row.createdAt), {
header: 'Created',
id: 'createdAt',
sortingFn: (
rowA: Row<IFlag>,
rowB: Row<IFlag>,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_columnId: string
): number =>
new Date(rowA.original.createdAt) < new Date(rowB.original.createdAt)
? 1
: -1
}),
columnHelper.accessor((row) => inTimezone(row.updatedAt), {
header: 'Updated',
id: 'updatedAt',
sortingFn: (
rowA: Row<IFlag>,
rowB: Row<IFlag>,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_columnId: string
): number =>
new Date(rowA.original.updatedAt) < new Date(rowB.original.updatedAt)
? 1
: -1
})
];
const { data, isLoading, error } = useListFlagsQuery(namespace.key);
const flags = data?.flags || [];

const { setError } = useError();
useEffect(() => {
if (error) {
setError(error);
}
}, [error, setError]);

const sorting = useSelector(selectSorting);
const table = useReactTable({
data: flags,
columns,
Expand All @@ -155,6 +169,10 @@ export default function FlagTable(props: FlagTableProps) {
getFilteredRowModel: getFilteredRowModel()
});

if (isLoading) {
return <TableSkeleton />;
}

return (
<>
<div className="flex items-center justify-between">
Expand All @@ -167,7 +185,13 @@ export default function FlagTable(props: FlagTableProps) {
<DataTableViewOptions table={table} />
</div>
</div>
{table.getRowCount() === 0 && (
{table.getRowCount() === 0 && filter.length === 0 && (
<Guide>
Flags enable you to control and roll out new functionality
dynamically. Create a new flag to get started.
</Guide>
)}
{table.getRowCount() === 0 && filter.length > 0 && (
<Guide>No flags matched your search.</Guide>
)}
{table.getRowModel().rows.map((row) => {
Expand Down
Loading
Loading