diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/ResourceConsumption.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/ResourceConsumption.tsx index ca6de63f900..46b7d39e635 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/ResourceConsumption.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/ResourceConsumption.tsx @@ -1,17 +1,37 @@ +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuTrigger, +} from "@superset/ui/dropdown-menu"; import { Popover, PopoverContent, PopoverTrigger } from "@superset/ui/popover"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; +import { useLiveQuery } from "@tanstack/react-db"; import { useNavigate } from "@tanstack/react-router"; -import { useState } from "react"; -import { HiOutlineArrowPath, HiOutlineCpuChip } from "react-icons/hi2"; +import { useMemo, useState } from "react"; +import { + HiOutlineArrowPath, + HiOutlineBarsArrowDown, + HiOutlineCpuChip, +} from "react-icons/hi2"; import { electronTrpc } from "renderer/lib/electron-trpc"; +import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; import { useTabsStore } from "renderer/stores/tabs/store"; import { AppResourceSection } from "./components/AppResourceSection"; import { MetricBadge } from "./components/MetricBadge"; import { WorkspaceResourceSection } from "./components/WorkspaceResourceSection"; -import type { UsageValues } from "./types"; +import type { SortOption, UsageValues } from "./types"; import { formatCpu, formatMemory, formatPercent } from "./utils/formatters"; import { normalizeResourceMetricsSnapshot } from "./utils/normalizeSnapshot"; +const SORT_LABELS: Record = { + memory: "Memory", + cpu: "CPU", + name: "Name", + sidebar: "Sidebar order", +}; + function getTotalUsage( cpu: number | undefined, memory: number | undefined, @@ -32,6 +52,7 @@ function getTrackedMemorySharePercent( export function ResourceConsumption() { const [open, setOpen] = useState(false); + const [sortOption, setSortOption] = useState("memory"); const [collapsedProjects, setCollapsedProjects] = useState>( new Set(), ); @@ -43,10 +64,39 @@ export function ResourceConsumption() { const panes = useTabsStore((state) => state.panes); const setActiveTab = useTabsStore((state) => state.setActiveTab); const setFocusedPane = useTabsStore((state) => state.setFocusedPane); + const collections = useCollections(); const { data: enabled } = electronTrpc.settings.getShowResourceMonitor.useQuery(); + const { data: rawSidebarProjects = [] } = useLiveQuery( + (q) => + q + .from({ sp: collections.v2SidebarProjects }) + .orderBy(({ sp }) => sp.tabOrder, "asc") + .select(({ sp }) => ({ projectId: sp.projectId })), + [collections], + ); + + const { data: rawSidebarWorkspaces = [] } = useLiveQuery( + (q) => + q + .from({ ws: collections.v2WorkspaceLocalState }) + .orderBy(({ ws }) => ws.sidebarState.tabOrder, "asc") + .select(({ ws }) => ({ workspaceId: ws.workspaceId })), + [collections], + ); + + const sidebarProjectOrder = useMemo( + () => rawSidebarProjects.map((p) => p.projectId), + [rawSidebarProjects], + ); + + const sidebarWorkspaceOrder = useMemo( + () => rawSidebarWorkspaces.map((w) => w.workspaceId), + [rawSidebarWorkspaces], + ); + const { data: snapshot, refetch, @@ -154,16 +204,51 @@ export function ResourceConsumption() {

Resource Usage

- +
+ + + + + + + setSortOption(value as SortOption) + } + > + + Memory + + + CPU + + + Name + + + Sidebar order + + + + + +
{normalizedSnapshot && ( @@ -198,6 +283,9 @@ export function ResourceConsumption() { {normalizedSnapshot && ( ; toggleProject: (projectId: string) => void; collapsedWorkspaces: Set; @@ -59,6 +62,68 @@ function groupWorkspacesByProject( return [...projectMap.values()]; } +function sortWorkspaces( + workspaces: WorkspaceMetrics[], + sortOption: SortOption, + sidebarWorkspaceOrder: string[], +): WorkspaceMetrics[] { + const sorted = [...workspaces]; + switch (sortOption) { + case "memory": + sorted.sort((a, b) => b.memory - a.memory); + break; + case "cpu": + sorted.sort((a, b) => b.cpu - a.cpu); + break; + case "name": + sorted.sort((a, b) => a.workspaceName.localeCompare(b.workspaceName)); + break; + case "sidebar": { + const orderMap = new Map( + sidebarWorkspaceOrder.map((id, index) => [id, index]), + ); + sorted.sort( + (a, b) => + (orderMap.get(a.workspaceId) ?? Number.MAX_SAFE_INTEGER) - + (orderMap.get(b.workspaceId) ?? Number.MAX_SAFE_INTEGER), + ); + break; + } + } + return sorted; +} + +function sortProjectGroups( + groups: ProjectResourceGroup[], + sortOption: SortOption, + sidebarProjectOrder: string[], +): ProjectResourceGroup[] { + const sorted = [...groups]; + switch (sortOption) { + case "memory": + sorted.sort((a, b) => b.memory - a.memory); + break; + case "cpu": + sorted.sort((a, b) => b.cpu - a.cpu); + break; + case "name": + sorted.sort((a, b) => a.projectName.localeCompare(b.projectName)); + break; + case "sidebar": { + const orderMap = new Map( + sidebarProjectOrder.map((id, index) => [id, index]), + ); + sorted.sort( + (a, b) => + (orderMap.get(a.projectId) ?? Number.MAX_SAFE_INTEGER) - + (orderMap.get(b.projectId) ?? Number.MAX_SAFE_INTEGER), + ); + break; + } + } + return sorted; +} + function getProjectTotals(projects: ProjectResourceGroup[]) { return projects.reduce( (acc, project) => ({ @@ -71,6 +136,9 @@ function getProjectTotals(projects: ProjectResourceGroup[]) { export function WorkspaceResourceSection({ workspaces, + sortOption, + sidebarProjectOrder, + sidebarWorkspaceOrder, collapsedProjects, toggleProject, collapsedWorkspaces, @@ -79,7 +147,20 @@ export function WorkspaceResourceSection({ navigateToPane, getPaneName, }: WorkspaceResourceSectionProps) { - const projectGroups = groupWorkspacesByProject(workspaces); + const rawProjectGroups = groupWorkspacesByProject(workspaces); + const sortedProjectGroups = sortProjectGroups( + rawProjectGroups, + sortOption, + sidebarProjectOrder, + ); + const projectGroups = sortedProjectGroups.map((group) => ({ + ...group, + workspaces: sortWorkspaces( + group.workspaces, + sortOption, + sidebarWorkspaceOrder, + ), + })); const projectTotals = getProjectTotals(projectGroups); return projectGroups.map((project) => { diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/types.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/types.ts index 5d4e20b08be..f1a76490036 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/types.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/types.ts @@ -1,5 +1,7 @@ export type UsageSeverity = "normal" | "elevated" | "high"; +export type SortOption = "memory" | "cpu" | "name" | "sidebar"; + export interface UsageValues { cpu: number; memory: number;