separate the users and roles tabs

This commit is contained in:
Owen
2026-08-12 10:40:29 -04:00
parent 8a04f13dd4
commit 833b27ab4a
5 changed files with 194 additions and 176 deletions
-1
View File
@@ -1941,7 +1941,6 @@
"aiUsageTabOverview": "Overview",
"aiUsageTabProviders": "Providers",
"aiUsageTabResources": "Resources",
"aiUsageTabUsersRoles": "Users & Roles",
"aiUsageFilterProvider": "Provider",
"aiUsageFilterModel": "Model",
"aiUsageFilterResource": "Resource",
+6 -3
View File
@@ -27,7 +27,8 @@ import { HorizontalTabs, type TabItem } from "./HorizontalTabs";
import { OverviewTab } from "./ai-usage-analytics/OverviewTab";
import { ProvidersTab } from "./ai-usage-analytics/ProvidersTab";
import { ResourcesTab } from "./ai-usage-analytics/ResourcesTab";
import { UsersRolesTab } from "./ai-usage-analytics/UsersRolesTab";
import { RolesTab } from "./ai-usage-analytics/RolesTab";
import { UsersTab } from "./ai-usage-analytics/UsersTab";
export type AiUsageAnalyticsDataProps = {
orgId: string;
@@ -144,7 +145,8 @@ export function AiUsageAnalyticsData(props: AiUsageAnalyticsDataProps) {
{ title: t("aiUsageTabOverview"), href: "#" },
{ title: t("aiUsageTabProviders"), href: "#" },
{ title: t("aiUsageTabResources"), href: "#" },
{ title: t("aiUsageTabUsersRoles"), href: "#" }
{ title: t("aiUsageRolesTab"), href: "#" },
{ title: t("aiUsageUsersTab"), href: "#" }
];
return (
@@ -255,7 +257,8 @@ export function AiUsageAnalyticsData(props: AiUsageAnalyticsDataProps) {
<OverviewTab orgId={props.orgId} filters={filters} />
<ProvidersTab orgId={props.orgId} filters={filters} />
<ResourcesTab orgId={props.orgId} filters={filters} />
<UsersRolesTab orgId={props.orgId} filters={filters} />
<RolesTab orgId={props.orgId} filters={filters} />
<UsersTab orgId={props.orgId} filters={filters} />
</HorizontalTabs>
</div>
);
@@ -0,0 +1,91 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { aiUsageAnalyticsQueries } from "@app/lib/queries";
import type { AiUsageAnalyticsFilters } from "@app/lib/queries";
import { useTranslations } from "next-intl";
import { Card, CardContent, CardHeader } from "@app/components/ui/card";
import { ToggleableTrendChart } from "./ToggleableTrendChart";
import { TopEntitiesList, type TopEntity } from "./TopEntitiesList";
import { buildSeriesFromData, formatCost } from "./shared";
type RolesTabProps = {
orgId: string;
filters: AiUsageAnalyticsFilters;
};
export function RolesTab(props: RolesTabProps) {
const t = useTranslations();
const { data, isLoading } = useQuery(
aiUsageAnalyticsQueries.usersRoles({
orgId: props.orgId,
filters: props.filters
})
);
const roleNameByKey = new Map<string, string>();
for (const r of data?.topRoles ?? []) {
roleNameByKey.set(String(r.roleId), r.name ?? `Role #${r.roleId}`);
}
const roleLabelFor = (key: string) =>
roleNameByKey.get(key) ?? `Role #${key}`;
const roleCostSeries = buildSeriesFromData(
data?.roleCostPerDay ?? [],
roleLabelFor,
t("aiUsageOther")
);
const roleTokensSeries = buildSeriesFromData(
data?.roleTokensPerDay ?? [],
roleLabelFor,
t("aiUsageOther")
);
const topRoles: TopEntity[] = (data?.topRoles ?? []).map((r) => ({
key: String(r.roleId),
label: r.name ?? `Role #${r.roleId}`,
requests: r.requests,
totalTokens: r.totalTokens,
costUsd: r.costUsd
}));
return (
<div className="flex flex-col gap-5">
<Card>
<CardHeader>
<h3 className="font-semibold">{t("aiUsageTopRoles")}</h3>
</CardHeader>
<CardContent>
<TopEntitiesList
entities={topRoles}
isLoading={isLoading}
nameColumnLabel={t("aiUsageFilterRole")}
/>
</CardContent>
</Card>
<div className="grid lg:grid-cols-2 gap-5">
<Card>
<CardContent className="p-6">
<ToggleableTrendChart
title={t("aiUsageRoleCost")}
data={data?.roleCostPerDay ?? []}
series={roleCostSeries}
isLoading={isLoading}
valueFormatter={(v) => formatCost(v)}
/>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<ToggleableTrendChart
title={t("aiUsageRoleTokenUsage")}
data={data?.roleTokensPerDay ?? []}
series={roleTokensSeries}
isLoading={isLoading}
/>
</CardContent>
</Card>
</div>
</div>
);
}
@@ -1,172 +0,0 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { aiUsageAnalyticsQueries } from "@app/lib/queries";
import type { AiUsageAnalyticsFilters } from "@app/lib/queries";
import { useTranslations } from "next-intl";
import { Card, CardContent, CardHeader } from "@app/components/ui/card";
import { HorizontalTabs, type TabItem } from "@app/components/HorizontalTabs";
import { ToggleableTrendChart } from "./ToggleableTrendChart";
import { TopEntitiesList, type TopEntity } from "./TopEntitiesList";
import { buildSeriesFromData, formatCost } from "./shared";
type UsersRolesTabProps = {
orgId: string;
filters: AiUsageAnalyticsFilters;
};
const UNKNOWN_USER_KEY = "unknown";
export function UsersRolesTab(props: UsersRolesTabProps) {
const t = useTranslations();
const { data, isLoading } = useQuery(
aiUsageAnalyticsQueries.usersRoles({
orgId: props.orgId,
filters: props.filters
})
);
const roleNameByKey = new Map<string, string>();
for (const r of data?.topRoles ?? []) {
roleNameByKey.set(String(r.roleId), r.name ?? `Role #${r.roleId}`);
}
const roleLabelFor = (key: string) =>
roleNameByKey.get(key) ?? `Role #${key}`;
const userEmailByKey = new Map<string, string>();
for (const u of data?.topUsers ?? []) {
if (u.userId) {
userEmailByKey.set(u.userId, u.email ?? u.userId);
}
}
const userLabelFor = (key: string) =>
key === UNKNOWN_USER_KEY
? t("aiUsageUnknownUser")
: (userEmailByKey.get(key) ?? key);
const roleCostSeries = buildSeriesFromData(
data?.roleCostPerDay ?? [],
roleLabelFor,
t("aiUsageOther")
);
const roleTokensSeries = buildSeriesFromData(
data?.roleTokensPerDay ?? [],
roleLabelFor,
t("aiUsageOther")
);
const userCostSeries = buildSeriesFromData(
data?.userCostPerDay ?? [],
userLabelFor,
t("aiUsageOther")
);
const userTokensSeries = buildSeriesFromData(
data?.userTokensPerDay ?? [],
userLabelFor,
t("aiUsageOther")
);
const topRoles: TopEntity[] = (data?.topRoles ?? []).map((r) => ({
key: String(r.roleId),
label: r.name ?? `Role #${r.roleId}`,
requests: r.requests,
totalTokens: r.totalTokens,
costUsd: r.costUsd
}));
const topUsers: TopEntity[] = (data?.topUsers ?? []).map((u) => ({
key: u.userId ?? UNKNOWN_USER_KEY,
label: u.email ?? u.userId ?? t("aiUsageUnknownUser"),
requests: u.requests,
totalTokens: u.totalTokens,
costUsd: u.costUsd
}));
const tabs: TabItem[] = [
{ title: t("aiUsageRolesTab"), href: "#" },
{ title: t("aiUsageUsersTab"), href: "#" }
];
return (
<HorizontalTabs items={tabs} clientSide>
<div className="flex flex-col gap-5">
<Card>
<CardHeader>
<h3 className="font-semibold">
{t("aiUsageTopRoles")}
</h3>
</CardHeader>
<CardContent>
<TopEntitiesList
entities={topRoles}
isLoading={isLoading}
nameColumnLabel={t("aiUsageFilterRole")}
/>
</CardContent>
</Card>
<div className="grid lg:grid-cols-2 gap-5">
<Card>
<CardContent className="p-6">
<ToggleableTrendChart
title={t("aiUsageRoleCost")}
data={data?.roleCostPerDay ?? []}
series={roleCostSeries}
isLoading={isLoading}
valueFormatter={(v) => formatCost(v)}
/>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<ToggleableTrendChart
title={t("aiUsageRoleTokenUsage")}
data={data?.roleTokensPerDay ?? []}
series={roleTokensSeries}
isLoading={isLoading}
/>
</CardContent>
</Card>
</div>
</div>
<div className="flex flex-col gap-5">
<Card>
<CardHeader>
<h3 className="font-semibold">
{t("aiUsageTopUsers")}
</h3>
</CardHeader>
<CardContent>
<TopEntitiesList
entities={topUsers}
isLoading={isLoading}
nameColumnLabel={t("aiUsageFilterUser")}
/>
</CardContent>
</Card>
<div className="grid lg:grid-cols-2 gap-5">
<Card>
<CardContent className="p-6">
<ToggleableTrendChart
title={t("aiUsageUserCost")}
data={data?.userCostPerDay ?? []}
series={userCostSeries}
isLoading={isLoading}
valueFormatter={(v) => formatCost(v)}
/>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<ToggleableTrendChart
title={t("aiUsageUserTokenUsage")}
data={data?.userTokensPerDay ?? []}
series={userTokensSeries}
isLoading={isLoading}
/>
</CardContent>
</Card>
</div>
</div>
</HorizontalTabs>
);
}
@@ -0,0 +1,97 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { aiUsageAnalyticsQueries } from "@app/lib/queries";
import type { AiUsageAnalyticsFilters } from "@app/lib/queries";
import { useTranslations } from "next-intl";
import { Card, CardContent, CardHeader } from "@app/components/ui/card";
import { ToggleableTrendChart } from "./ToggleableTrendChart";
import { TopEntitiesList, type TopEntity } from "./TopEntitiesList";
import { buildSeriesFromData, formatCost } from "./shared";
type UsersTabProps = {
orgId: string;
filters: AiUsageAnalyticsFilters;
};
const UNKNOWN_USER_KEY = "unknown";
export function UsersTab(props: UsersTabProps) {
const t = useTranslations();
const { data, isLoading } = useQuery(
aiUsageAnalyticsQueries.usersRoles({
orgId: props.orgId,
filters: props.filters
})
);
const userEmailByKey = new Map<string, string>();
for (const u of data?.topUsers ?? []) {
if (u.userId) {
userEmailByKey.set(u.userId, u.email ?? u.userId);
}
}
const userLabelFor = (key: string) =>
key === UNKNOWN_USER_KEY
? t("aiUsageUnknownUser")
: (userEmailByKey.get(key) ?? key);
const userCostSeries = buildSeriesFromData(
data?.userCostPerDay ?? [],
userLabelFor,
t("aiUsageOther")
);
const userTokensSeries = buildSeriesFromData(
data?.userTokensPerDay ?? [],
userLabelFor,
t("aiUsageOther")
);
const topUsers: TopEntity[] = (data?.topUsers ?? []).map((u) => ({
key: u.userId ?? UNKNOWN_USER_KEY,
label: u.email ?? u.userId ?? t("aiUsageUnknownUser"),
requests: u.requests,
totalTokens: u.totalTokens,
costUsd: u.costUsd
}));
return (
<div className="flex flex-col gap-5">
<Card>
<CardHeader>
<h3 className="font-semibold">{t("aiUsageTopUsers")}</h3>
</CardHeader>
<CardContent>
<TopEntitiesList
entities={topUsers}
isLoading={isLoading}
nameColumnLabel={t("aiUsageFilterUser")}
/>
</CardContent>
</Card>
<div className="grid lg:grid-cols-2 gap-5">
<Card>
<CardContent className="p-6">
<ToggleableTrendChart
title={t("aiUsageUserCost")}
data={data?.userCostPerDay ?? []}
series={userCostSeries}
isLoading={isLoading}
valueFormatter={(v) => formatCost(v)}
/>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<ToggleableTrendChart
title={t("aiUsageUserTokenUsage")}
data={data?.userTokensPerDay ?? []}
series={userTokensSeries}
isLoading={isLoading}
/>
</CardContent>
</Card>
</div>
</div>
);
}