Skip to content

Commit

Permalink
INN 2962 Add status filter to runs (#1295)
Browse files Browse the repository at this point in the history
* Add empty state

* Add Skeleton

* Add status filter

* Adjust code

* Format duration

* Use remix icons

* Wire api

* Add status filter to query

* Fix

* Simplify html
  • Loading branch information
anafilipadealmeida committed Apr 23, 2024
1 parent 61d7151 commit 6c37b6a
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 135 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/20/solid';
import { useMemo } from 'react';
import { FunctionRunStatusIcon } from '@inngest/components/FunctionRunStatusIcon';
import { Skeleton } from '@inngest/components/Skeleton';
import { IDCell, StatusCell, TextCell, TimeCell } from '@inngest/components/Table';
import { type FunctionRunStatus } from '@inngest/components/types/functionRun';
import { cn } from '@inngest/components/utils/classNames';
import { formatMilliseconds } from '@inngest/components/utils/date';
import { RiSortAsc, RiSortDesc } from '@remixicon/react';
import {
createColumnHelper,
flexRender,
Expand All @@ -16,7 +19,7 @@ import { Time } from '@/components/Time';

export type Run = {
status: FunctionRunStatus;
duration: string;
durationMS: number;
id: string;
queuedAt: string;
endedAt: string;
Expand All @@ -30,11 +33,23 @@ type RunsTableProps = {
};

export default function RunsTable({ data = [], isLoading, sorting, setSorting }: RunsTableProps) {
const columns = useColumns();
// Render 8 empty lines for skeletons when data is loading
const tableData = useMemo(() => (isLoading ? Array(8).fill({}) : data), [isLoading, data]);

const tableColumns = useMemo(
() =>
isLoading
? columns.map((column) => ({
...column,
cell: () => <Skeleton className="my-4 block h-4" />,
}))
: columns,
[isLoading]
);

const table = useReactTable({
data,
columns,
data: tableData,
columns: tableColumns,
getCoreRowModel: getCoreRowModel(),
manualSorting: true,
onSortingChange: setSorting,
Expand All @@ -43,16 +58,15 @@ export default function RunsTable({ data = [], isLoading, sorting, setSorting }:
},
});

// TODO: pass loading to column cells for skeletons
if (isLoading) return;

const tableStyles = 'w-full border-y border-slate-200';
const tableHeadStyles = 'border-b border-slate-200';
const tableBodyStyles = 'divide-y divide-slate-200';
const tableColumnStyles = 'px-4';

const isEmpty = data.length < 1 && !isLoading;

return (
<table className={tableStyles}>
<table className={cn(isEmpty && 'h-full', tableStyles)}>
<thead className={tableHeadStyles}>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id} className="h-12">
Expand All @@ -71,8 +85,8 @@ export default function RunsTable({ data = [], isLoading, sorting, setSorting }:
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: <ArrowDownIcon className="h-4 w-4" />,
desc: <ArrowUpIcon className="h-4 w-4" />,
asc: <RiSortDesc className="h-4 w-4" />,
desc: <RiSortAsc className="h-4 w-4" />,
}[header.column.getIsSorted() as string] ?? null}
</div>
)}
Expand All @@ -82,15 +96,24 @@ export default function RunsTable({ data = [], isLoading, sorting, setSorting }:
))}
</thead>
<tbody className={tableBodyStyles}>
{table.getRowModel().rows.map((row) => (
<tr key={row.id} className="h-12">
{row.getVisibleCells().map((cell) => (
<td className={tableColumnStyles} key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
{isEmpty && (
<tr>
{/* TODO: when we introduce column visibility options, this colSpan has to be dinamically calculated depending on # visible columns */}
<td className="pt-28 text-center align-top font-medium text-slate-600" colSpan={5}>
No results were found.
</td>
</tr>
))}
)}
{!isEmpty &&
table.getRowModel().rows.map((row) => (
<tr key={row.id} className="h-12">
{row.getVisibleCells().map((cell) => (
<td className={tableColumnStyles} key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
<tfoot>
{table.getFooterGroups().map((footerGroup) => (
Expand All @@ -109,77 +132,73 @@ export default function RunsTable({ data = [], isLoading, sorting, setSorting }:
);
}

function useColumns() {
const columnHelper = createColumnHelper<Run>();

return [
columnHelper.accessor('status', {
cell: (info) => {
const status = info.getValue();

return (
<div className="flex items-center">
<StatusCell status={status}>
<FunctionRunStatusIcon status={status} className="h-5 w-5" />
</StatusCell>
</div>
);
},
header: 'Status',
}),
columnHelper.accessor('id', {
cell: (info) => {
const id = info.getValue();

return (
<div className="flex items-center">
<IDCell>{id}</IDCell>
</div>
);
},
header: 'Run ID',
}),
columnHelper.accessor('queuedAt', {
cell: (info) => {
const time = info.getValue();

return (
<div className="flex items-center">
<TimeCell>
<Time value={new Date(time)} />
</TimeCell>
</div>
);
},
header: 'Queued At',
}),
columnHelper.accessor('endedAt', {
cell: (info) => {
const time = info.getValue();

return (
<div className="flex items-center">
<TimeCell>
<Time value={new Date(time)} />
</TimeCell>
</div>
);
},
header: 'Ended At',
}),
columnHelper.accessor('duration', {
cell: (info) => {
const duration = info.getValue();

return (
<div className="flex items-center">
<TextCell>
<p>{duration || '-'}</p>
</TextCell>
</div>
);
},
header: 'Duration',
}),
];
}
const columnHelper = createColumnHelper<Run>();

const columns = [
columnHelper.accessor('status', {
cell: (info) => {
const status = info.getValue();

return (
<div className="flex items-center">
<StatusCell status={status}>
<FunctionRunStatusIcon status={status} className="h-5 w-5" />
</StatusCell>
</div>
);
},
header: 'Status',
}),
columnHelper.accessor('id', {
cell: (info) => {
const id = info.getValue();

return (
<div className="flex items-center">
<IDCell>{id}</IDCell>
</div>
);
},
header: 'Run ID',
}),
columnHelper.accessor('queuedAt', {
cell: (info) => {
const time = info.getValue();

return (
<div className="flex items-center">
<TimeCell>
<Time value={new Date(time)} />
</TimeCell>
</div>
);
},
header: 'Queued At',
}),
columnHelper.accessor('endedAt', {
cell: (info) => {
const time = info.getValue();

return (
<div className="flex items-center">
<TimeCell>
<Time value={new Date(time)} />
</TimeCell>
</div>
);
},
header: 'Ended At',
}),
columnHelper.accessor('durationMS', {
cell: (info) => {
const duration = info.getValue();

return (
<div className="flex items-center">
<TextCell>{duration ? formatMilliseconds(duration) : '-'}</TextCell>
</div>
);
},
header: 'Duration',
}),
];

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,38 +1,82 @@
'use client';

// import { useQuery } from 'urql';
import { Button } from '@inngest/components/Button';
import { RiLoopLeftLine } from '@remixicon/react';
import { useQuery } from 'urql';

// import { useEnvironment } from '@/app/(organization-active)/(dashboard)/env/[environmentSlug]/environment-context';
// import { graphql } from '@/gql';
import { useEnvironment } from '@/app/(organization-active)/(dashboard)/env/[environmentSlug]/environment-context';
import { graphql } from '@/gql';
import { FunctionRunStatus } from '@/gql/graphql';
import { useStringArraySearchParam } from '@/utils/useSearchParam';
import StatusFilter from '../logs/StatusFilter';
import RunsTable from './RunsTable';
import { mockedRuns } from './mockedRuns';

// const GetRunsDocument = graphql(`
// query GetRuns($environmentID: ID!) {
// workspace(id: $environmentID) {
// runs {
// }
// }
// }
// `);
const GetRunsDocument = graphql(`
query GetRuns($environmentID: ID!, $startTime: Time!, $status: [FunctionRunStatus!]) {
environment: workspace(id: $environmentID) {
runs(
filter: { from: $startTime, status: $status }
orderBy: [{ field: QUEUED_AT, direction: ASC }]
) {
edges {
node {
id
queuedAt
endedAt
durationMS
status
}
}
}
}
}
`);

export default function RunsPage() {
// const environment = useEnvironment();
// const [{ data, fetching: fetchingRuns }] = useQuery({
// query: GetRunsDocument,
// variables: {
// environmentID: environment.id,
// // filter: filtering,
// },
// });
const [filteredStatus, setFilteredStatus, removeFilteredStatus] =
useStringArraySearchParam('filterStatus');

const runs = mockedRuns;
function handleStatusesChange(statuses: FunctionRunStatus[]) {
if (statuses.length > 0) {
setFilteredStatus(statuses);
} else {
removeFilteredStatus();
}
}

const environment = useEnvironment();
const [{ data, fetching: fetchingRuns }, refetch] = useQuery({
query: GetRunsDocument,
variables: {
environmentID: environment.id,
startTime: '2024-04-19T11:26:03.203Z',
status: filteredStatus ? (filteredStatus as FunctionRunStatus[]) : null,
},
});

{
/* TODO: This is a temp parser */
}
const runs = data?.environment.runs.edges.map((edge) => ({
id: edge.node.id,
queuedAt: edge.node.queuedAt,
endedAt: edge.node.endedAt,
durationMS: edge.node.durationMS,
status: edge.node.status,
}));

return (
<main className="bg-white">
<div className="m-8 flex gap-2">{/* TODO: filters */}</div>
<main className="h-full min-h-0 overflow-y-auto bg-white">
<div className="flex justify-between gap-2 bg-slate-50 px-8 py-2">
<StatusFilter
selectedStatuses={filteredStatus ? (filteredStatus as FunctionRunStatus[]) : []}
onStatusesChange={handleStatusesChange}
/>
{/* TODO: wire button */}
<Button label="Refresh" appearance="text" btnAction={refetch} icon={<RiLoopLeftLine />} />
</div>
{/* @ts-ignore */}
<RunsTable data={runs} />
<RunsTable data={runs} isLoading={fetchingRuns} />
</main>
);
}
Loading

0 comments on commit 6c37b6a

Please sign in to comment.