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

feat(ui): new table view for flags and segments #3664

Merged
merged 3 commits into from
Dec 1, 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
539 changes: 539 additions & 0 deletions ui/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@heroicons/react": "^2.1.5",
"@loadable/component": "^5.16.4",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@reduxjs/toolkit": "^2.3.0",
"@tanstack/react-table": "^8.20.5",
Expand Down
4 changes: 2 additions & 2 deletions ui/src/app/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ function InnerLayout() {
href="https://docs.flipt.io/cloud/overview"
/>
)}
<main className="flex px-6 py-10">
<div className="w-full overflow-x-auto px-4 sm:px-6 lg:px-8">
<main className="flex sm:pt-4">
<div className="mx-auto w-full max-w-7xl overflow-x-auto px-4 sm:px-6 lg:px-8">
<Outlet />
</div>
</main>
Expand Down
13 changes: 5 additions & 8 deletions ui/src/app/console/Console.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
generateCurlCommand,
getErrorMessage
} from '~/utils/helpers';
import { PageHeader } from '~/components/ui/page';

function ResetOnNamespaceChange({ namespace }: { namespace: INamespace }) {
const { resetForm } = useFormikContext();
Expand Down Expand Up @@ -213,14 +214,10 @@ export default function Console() {

return (
<>
<div className="relative flex flex-col">
<h1 className="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl">
Console
</h1>
<p className="mt-2 text-sm text-gray-500">
See the results of your flag evaluations and debug any issues
</p>
</div>
<PageHeader title="Console" />
<p className="mt-2 text-sm text-gray-500">
See the results of your flag evaluations and debug any issues
</p>
<div className="flex flex-col md:flex-row">
{flags.length > 0 && (
<>
Expand Down
80 changes: 38 additions & 42 deletions ui/src/app/flags/Flag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from './flagsApi';
import { FlagType } from '~/types/Flag';
import { cls } from '~/utils/helpers';
import { PageHeader } from '~/components/ui/page';

const variantFlagTabs = [
{ name: 'Variants', to: '' },
Expand Down Expand Up @@ -130,51 +131,46 @@ export default function Flag() {
</Modal>

{/* flag header / actions */}
<PageHeader title={flag.name}>
<Dropdown
label="Actions"
actions={[
{
id: 'flag-copy',
label: 'Copy to Namespace',
disabled: readOnly || namespaces.length < 2,
onClick: () => {
setShowCopyFlagModal(true);
},
icon: FilesIcon
},
{
id: 'flag-delete',
label: 'Delete',
disabled: readOnly,
onClick: () => setShowDeleteFlagModal(true),
icon: Trash2Icon,
variant: 'destructive'
}
]}
/>
</PageHeader>
<div className="flex items-center justify-between">
<div className="min-w-0 flex-1">
<h2 className="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
{flag.name}
</h2>
<div className="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-6">
<div
title={inTimezone(flag.createdAt)}
className="mt-2 flex items-center text-sm text-gray-500"
>
<CalendarIcon
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
aria-hidden="true"
/>
Created{' '}
{formatDistanceToNowStrict(parseISO(flag.createdAt), {
addSuffix: true
})}
</div>
<div className="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-6">
<div
title={inTimezone(flag.createdAt)}
className="mt-2 flex items-center text-sm text-gray-500"
>
<CalendarIcon
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
aria-hidden="true"
/>
Created{' '}
{formatDistanceToNowStrict(parseISO(flag.createdAt), {
addSuffix: true
})}
</div>
</div>
<div className="flex flex-none">
<Dropdown
label="Actions"
actions={[
{
id: 'flag-copy',
label: 'Copy to Namespace',
disabled: readOnly || namespaces.length < 2,
onClick: () => {
setShowCopyFlagModal(true);
},
icon: FilesIcon
},
{
id: 'flag-delete',
label: 'Delete',
disabled: readOnly,
onClick: () => setShowDeleteFlagModal(true),
icon: Trash2Icon,
variant: 'destructive'
}
]}
/>
</div>
</div>

<div className="flex flex-col">
Expand Down
54 changes: 19 additions & 35 deletions ui/src/app/flags/Flags.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { Link, useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { selectReadonly } from '~/app/meta/metaSlice';
import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice';
import EmptyState from '~/components/EmptyState';
import FlagTable from '~/components/flags/FlagTable';
import { ButtonWithPlus } from '~/components/forms/buttons/Button';
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);
Expand All @@ -17,50 +19,32 @@ export default function Flags() {
const flags = data?.flags || [];

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

const readOnly = useSelector(selectReadonly);

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

return (
<>
<div className="flex-row justify-between pb-5 sm:flex sm:items-center">
<div className="flex flex-col">
<h1 className="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl">
Flags
</h1>
<p className="mt-2 text-sm text-gray-500">
Flags represent features that you can easily enable or disable
</p>
</div>
<div className="mt-4">
<Link to={`${path}/new`}>
<ButtonWithPlus
variant="primary"
disabled={readOnly}
title={readOnly ? 'Not allowed in Read-Only mode' : undefined}
>
New Flag
</ButtonWithPlus>
</Link>
</div>
</div>
<div className="mt-4 flex flex-col">
<PageHeader title="Flags">
<Button onClick={() => navigate(`${path}/new`)} disabled={readOnly}>
<Plus />
New Flag
</Button>
</PageHeader>
<div className="flex flex-col gap-1 space-y-2 py-2">
{flags && flags.length > 0 ? (
<FlagTable flags={flags} />
) : (
<EmptyState
text="Create Flag"
disabled={readOnly}
onClick={() => navigate(`${path}/new`)}
/>
<Guide className="mt-6">
Flags enable you to control and roll out new functionality
dynamically. Create a new flag to get started.
</Guide>
)}
</div>
</>
Expand Down
11 changes: 3 additions & 8 deletions ui/src/app/flags/NewFlag.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import FlagForm from '~/components/flags/forms/FlagForm';
import MoreInfo from '~/components/MoreInfo';
import { PageHeader } from '~/components/ui/page';

export default function NewFlag() {
return (
<>
<div className="lg:flex lg:items-center lg:justify-between">
<div className="min-w-0 flex-1">
<h2 className="text-2xl font-semibold leading-6 text-gray-900">
New Flag
</h2>
</div>
</div>
<div className="my-10">
<PageHeader title="New Flag" />
<div className="my-6">
<div className="md:grid md:grid-cols-3 md:gap-6">
<div className="md:col-span-1">
<p className="mt-2 text-sm text-gray-500">
Expand Down
11 changes: 3 additions & 8 deletions ui/src/app/segments/NewSegment.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import MoreInfo from '~/components/MoreInfo';
import SegmentForm from '~/components/segments/SegmentForm';
import { PageHeader } from '~/components/ui/page';

export default function NewSegment() {
return (
<>
<div className="lg:flex lg:items-center lg:justify-between">
<div className="min-w-0 flex-1">
<h2 className="text-2xl font-semibold leading-6 text-gray-900">
New Segment
</h2>
</div>
</div>
<div className="my-10">
<PageHeader title="New Segment" />
<div className="my-6">
<div className="md:grid md:grid-cols-3 md:gap-6">
<div className="md:col-span-1">
<p className="mt-2 text-sm text-gray-500">
Expand Down
78 changes: 37 additions & 41 deletions ui/src/app/segments/Segment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
constraintTypeToLabel,
IConstraint
} from '~/types/Constraint';
import { PageHeader } from '~/components/ui/page';

function ConstraintArrayValue({ value }: { value: string | undefined }) {
const items: string[] | number[] = useMemo(() => {
Expand Down Expand Up @@ -215,54 +216,49 @@ export default function Segment() {
</Modal>

{/* segment header / delete button */}
<PageHeader title={segment.name}>
<Dropdown
label="Actions"
actions={[
{
id: 'segment-copy',
label: 'Copy to Namespace',
disabled: readOnly || namespaces.length < 2,
onClick: () => setShowCopySegmentModal(true),
icon: FilesIcon
},
{
id: 'segement-delete',
label: 'Delete',
disabled: readOnly,
onClick: () => setShowDeleteSegmentModal(true),
icon: Trash2Icon,
variant: 'destructive'
}
]}
/>
</PageHeader>
<div className="flex items-center justify-between">
<div className="min-w-0 flex-1">
<h2 className="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
{segment.name}
</h2>
<div className="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-6">
<div
title={inTimezone(segment.createdAt)}
className="mt-2 flex items-center text-sm text-gray-500"
>
<CalendarIcon
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
aria-hidden="true"
/>
Created{' '}
{formatDistanceToNowStrict(parseISO(segment.createdAt), {
addSuffix: true
})}
</div>
<div className="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-6">
<div
title={inTimezone(segment.createdAt)}
className="mt-2 flex items-center text-sm text-gray-500"
>
<CalendarIcon
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
aria-hidden="true"
/>
Created{' '}
{formatDistanceToNowStrict(parseISO(segment.createdAt), {
addSuffix: true
})}
</div>
</div>
<div className="flex flex-none">
<Dropdown
label="Actions"
actions={[
{
id: 'segment-copy',
label: 'Copy to Namespace',
disabled: readOnly || namespaces.length < 2,
onClick: () => setShowCopySegmentModal(true),
icon: FilesIcon
},
{
id: 'segement-delete',
label: 'Delete',
disabled: readOnly,
onClick: () => setShowDeleteSegmentModal(true),
icon: Trash2Icon,
variant: 'destructive'
}
]}
/>
</div>
</div>

<div className="flex flex-col">
{/* segment details */}
<div className="mb-5 mt-10">
<div className="b-5">
<div className="md:grid md:grid-cols-3 md:gap-6">
<div className="md:col-span-1">
<p className="mt-1 text-sm text-gray-500">
Expand Down
Loading
Loading