Skip to content

Commit 211404d

Browse files
Add default filter (Status) to occupancy page and compact bytes unit (#668)
* Add default filter (STATUS) to occupancy page * Use compact bytes unit in Occupancy page
1 parent 88fd200 commit 211404d

File tree

8 files changed

+91
-17
lines changed

8 files changed

+91
-17
lines changed

src/ui/src/app/(dashboard)/occupancy/occupancy-page-content.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@
2727

2828
import { useMemo, useCallback, useState } from "react";
2929
import { useQueryState, parseAsStringLiteral } from "nuqs";
30+
import { TaskGroupStatus } from "@/lib/api/generated";
3031
import { InlineErrorBoundary } from "@/components/error/inline-error-boundary";
3132
import { usePage } from "@/components/chrome/page-context";
3233
import { useResultsCount } from "@/components/filter-bar/hooks/use-results-count";
33-
import { useUrlChips } from "@/components/filter-bar/hooks/use-url-chips";
34+
import { useDefaultFilter } from "@/components/filter-bar/hooks/use-default-filter";
3435
import { OccupancyToolbar } from "@/features/occupancy/components/occupancy-toolbar";
3536
import { OccupancySummary } from "@/features/occupancy/components/occupancy-summary";
3637
import { OccupancyDataTable } from "@/features/occupancy/components/occupancy-data-table";
@@ -58,10 +59,13 @@ export function OccupancyPageContent() {
5859

5960
const [groupBy, setGroupBy] = useQueryState(
6061
"groupBy",
61-
parseAsGroupBy.withDefault("user").withOptions({ shallow: true, history: "replace", clearOnDefault: true }),
62+
parseAsGroupBy.withDefault("pool").withOptions({ shallow: true, history: "replace", clearOnDefault: true }),
6263
);
6364

64-
const { searchChips, setSearchChips } = useUrlChips();
65+
const { effectiveChips: searchChips, handleChipsChange: setSearchChips } = useDefaultFilter({
66+
field: "status",
67+
defaultValue: TaskGroupStatus.RUNNING,
68+
});
6569

6670
// ==========================================================================
6771
// Sort state from table store

src/ui/src/features/occupancy/components/occupancy-column-defs.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import type { ColumnDef } from "@tanstack/react-table";
2020
import { ChevronRight } from "lucide-react";
21-
import { cn } from "@/lib/utils";
21+
import { cn, formatCompact, formatBytes } from "@/lib/utils";
2222
import type { OccupancyFlatRow, OccupancyGroupBy } from "@/lib/api/adapter/occupancy";
2323

2424
// =============================================================================
@@ -36,7 +36,18 @@ const PRIORITY_COLOR: Record<"high" | "normal" | "low", string> = {
3636
// =============================================================================
3737

3838
function ResourceCell({ value }: { value: number }) {
39-
return <span className="text-sm text-zinc-700 tabular-nums dark:text-zinc-300">{value}</span>;
39+
return <span className="text-sm text-zinc-700 tabular-nums dark:text-zinc-300">{formatCompact(value)}</span>;
40+
}
41+
42+
function BytesCell({ value }: { value: number }) {
43+
if (value === 0) return <span className="text-zinc-300 dark:text-zinc-600"></span>;
44+
const { value: val, unit } = formatBytes(value);
45+
return (
46+
<span className="text-sm text-zinc-700 tabular-nums dark:text-zinc-300">
47+
{val}
48+
<span className="ml-0.5 text-xs text-zinc-400">{unit}</span>
49+
</span>
50+
);
4051
}
4152

4253
function PriorityBadge({ value, colorClass }: { value: number; colorClass: string }) {
@@ -142,22 +153,22 @@ export function createOccupancyColumns(groupBy: OccupancyGroupBy): ColumnDef<Occ
142153
cell: ({ row }) => <ResourceCell value={row.original.cpu} />,
143154
},
144155

145-
// Memory
156+
// Memory (GiB)
146157
{
147158
id: "memory",
148159
accessorFn: (row) => row.memory,
149160
enableSorting: true,
150161
header: "Memory",
151-
cell: ({ row }) => <ResourceCell value={row.original.memory} />,
162+
cell: ({ row }) => <BytesCell value={row.original.memory} />,
152163
},
153164

154-
// Storage
165+
// Storage (GiB)
155166
{
156167
id: "storage",
157168
accessorFn: (row) => row.storage,
158169
enableSorting: true,
159170
header: "Storage",
160-
cell: ({ row }) => <ResourceCell value={row.original.storage} />,
171+
cell: ({ row }) => <BytesCell value={row.original.storage} />,
161172
},
162173

163174
// High priority count

src/ui/src/features/occupancy/components/occupancy-summary.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import { memo } from "react";
2020
import { Cpu, HardDrive, MemoryStick, Zap } from "lucide-react";
2121
import { Skeleton } from "@/components/shadcn/skeleton";
22-
import { formatCompact } from "@/lib/utils";
22+
import { formatCompact, formatBytes } from "@/lib/utils";
2323
import type { OccupancyTotals } from "@/lib/api/adapter/occupancy";
2424

2525
// =============================================================================
@@ -36,13 +36,27 @@ interface KpiCardProps {
3636
value: number;
3737
Icon: React.ElementType;
3838
colorClass: string;
39+
isBytes?: boolean;
3940
}
4041

4142
// =============================================================================
4243
// Card
4344
// =============================================================================
4445

45-
const KpiCard = memo(function KpiCard({ label, value, Icon, colorClass }: KpiCardProps) {
46+
const KpiCard = memo(function KpiCard({ label, value, Icon, colorClass, isBytes = false }: KpiCardProps) {
47+
let display: React.ReactNode;
48+
if (isBytes) {
49+
const { value: val, unit } = formatBytes(value);
50+
display = (
51+
<>
52+
{val}
53+
<span className="ml-1 text-base font-medium text-zinc-400">{unit}</span>
54+
</>
55+
);
56+
} else {
57+
display = formatCompact(value);
58+
}
59+
4660
return (
4761
<div className="rounded-lg border border-zinc-200 bg-white p-3 dark:border-zinc-800 dark:bg-zinc-950">
4862
<div className="flex items-center gap-1.5">
@@ -52,7 +66,7 @@ const KpiCard = memo(function KpiCard({ label, value, Icon, colorClass }: KpiCar
5266
<span className="text-xs font-medium tracking-wider text-zinc-500 uppercase dark:text-zinc-400">{label}</span>
5367
</div>
5468
<div className="mt-2 tabular-nums">
55-
<span className="text-2xl font-semibold text-zinc-900 dark:text-zinc-100">{formatCompact(value)}</span>
69+
<span className="text-2xl font-semibold text-zinc-900 dark:text-zinc-100">{display}</span>
5670
</div>
5771
</div>
5872
);
@@ -95,12 +109,14 @@ export const OccupancySummary = memo(function OccupancySummary({ totals, isLoadi
95109
value={totals.memory}
96110
Icon={MemoryStick}
97111
colorClass="text-purple-500"
112+
isBytes
98113
/>
99114
<KpiCard
100115
label="Storage"
101116
value={totals.storage}
102117
Icon={HardDrive}
103118
colorClass="text-zinc-500"
119+
isBytes
104120
/>
105121
</div>
106122
);

src/ui/src/features/occupancy/components/occupancy-toolbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ export interface OccupancyToolbarProps {
5151
// =============================================================================
5252

5353
const GROUP_BY_OPTIONS: { value: OccupancyGroupBy; label: string }[] = [
54-
{ value: "user", label: "By User" },
5554
{ value: "pool", label: "By Pool" },
55+
{ value: "user", label: "By User" },
5656
];
5757

5858
function GroupByToggle({ value, onChange }: { value: OccupancyGroupBy; onChange: (v: OccupancyGroupBy) => void }) {

src/ui/src/features/occupancy/hooks/use-occupancy-data.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,17 @@
3030
import { useMemo } from "react";
3131
import { useQuery } from "@tanstack/react-query";
3232
import type { WorkflowPriority } from "@/lib/api/generated";
33+
import { TaskGroupStatus } from "@/lib/api/generated";
3334
import type { OccupancyGroup, OccupancyGroupBy, OccupancySortBy, OccupancyTotals } from "@/lib/api/adapter/occupancy";
3435
import { fetchOccupancySummary, aggregateGroups, sortGroupsLocal } from "@/lib/api/adapter/occupancy-shim";
3536
import type { SearchChip } from "@/stores/types";
3637

38+
const VALID_TASK_GROUP_STATUSES: ReadonlySet<string> = new Set(Object.values(TaskGroupStatus));
39+
40+
function isTaskGroupStatus(value: string): value is TaskGroupStatus {
41+
return VALID_TASK_GROUP_STATUSES.has(value);
42+
}
43+
3744
// =============================================================================
3845
// Types
3946
// =============================================================================
@@ -69,12 +76,14 @@ export function useOccupancyData({
6976
const users: string[] = [];
7077
const pools: string[] = [];
7178
const priorities: WorkflowPriority[] = [];
79+
const statuses: TaskGroupStatus[] = [];
7280
for (const chip of searchChips) {
7381
if (chip.field === "user") users.push(chip.value);
7482
else if (chip.field === "pool") pools.push(chip.value);
7583
else if (chip.field === "priority") priorities.push(chip.value as WorkflowPriority);
84+
else if (chip.field === "status" && isTaskGroupStatus(chip.value)) statuses.push(chip.value);
7685
}
77-
return { users, pools, priorities };
86+
return { users, pools, priorities, statuses };
7887
// Intentionally excludes groupBy/sortBy/order — switching group view or
7988
// resorting never triggers a network re-fetch. The shim returns raw rows;
8089
// aggregation + sort happen below.

src/ui/src/features/occupancy/lib/occupancy-search-fields.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
//SPDX-License-Identifier: Apache-2.0
1616

1717
import type { SearchField } from "@/components/filter-bar/lib/types";
18-
import { WorkflowPriority } from "@/lib/api/generated";
18+
import { WorkflowPriority, TaskGroupStatus } from "@/lib/api/generated";
1919
import type { OccupancyGroup } from "@/lib/api/adapter/occupancy";
2020

2121
/**
@@ -64,4 +64,24 @@ export const OCCUPANCY_SEARCH_FIELDS: SearchField<OccupancyGroup>[] = [
6464
requiresValidValue: true,
6565
match: () => true, // Filtering handled server-side
6666
},
67+
{
68+
id: "status",
69+
label: "Status",
70+
hint: "RUNNING, WAITING, ...",
71+
prefix: "status:",
72+
freeFormHint: "Type a status, press Enter",
73+
getValues: () => [
74+
TaskGroupStatus.RUNNING,
75+
TaskGroupStatus.WAITING,
76+
TaskGroupStatus.SCHEDULING,
77+
TaskGroupStatus.INITIALIZING,
78+
TaskGroupStatus.SUBMITTING,
79+
TaskGroupStatus.PROCESSING,
80+
TaskGroupStatus.COMPLETED,
81+
TaskGroupStatus.FAILED,
82+
],
83+
exhaustive: true,
84+
requiresValidValue: true,
85+
match: () => true, // Filtering handled server-side
86+
},
6787
];

src/ui/src/features/pools/styles/pools.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,14 @@
7070
============================================================================= */
7171

7272
.pools-row {
73-
@apply transition-colors duration-75 ease-out;
73+
/* Combine color transitions (hover) with the transform transition (row reposition on expand).
74+
Can't use @apply transition-colors here — it overwrites transition-property and kills the
75+
transform animation defined on .data-table-row. */
76+
transition:
77+
color 75ms ease-out,
78+
background-color 75ms ease-out,
79+
border-color 75ms ease-out,
80+
transform var(--duration-normal) var(--ease-out);
7481
}
7582

7683
.pools-row:not(.pools-row--selected):hover {

src/ui/src/lib/api/adapter/occupancy-shim.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@
3737
* =============================================================================
3838
*/
3939

40-
import { listTaskApiTaskGet, type ListTaskSummaryResponse, WorkflowPriority } from "@/lib/api/generated";
40+
import {
41+
listTaskApiTaskGet,
42+
type ListTaskSummaryResponse,
43+
WorkflowPriority,
44+
TaskGroupStatus,
45+
} from "@/lib/api/generated";
4146
import type { OccupancyGroup, OccupancyChild, OccupancyGroupBy, OccupancySortBy } from "@/lib/api/adapter/occupancy";
4247

4348
// =============================================================================
@@ -55,6 +60,7 @@ export interface OccupancyQueryParams {
5560
users?: string[];
5661
pools?: string[];
5762
priorities?: WorkflowPriority[];
63+
statuses?: TaskGroupStatus[];
5864
}
5965

6066
export interface OccupancySummaryResult {
@@ -146,6 +152,7 @@ export async function fetchOccupancySummary(params: OccupancyQueryParams): Promi
146152
...(params.users && params.users.length > 0 ? { users: params.users } : {}),
147153
...(params.pools && params.pools.length > 0 ? { pools: params.pools } : {}),
148154
...(params.priorities && params.priorities.length > 0 ? { priority: params.priorities } : {}),
155+
...(params.statuses && params.statuses.length > 0 ? { statuses: params.statuses } : {}),
149156
});
150157

151158
// customFetch throws on 4xx/5xx — we only reach here on 200

0 commit comments

Comments
 (0)