diff --git a/site/src/pages/AgentsPage/AgentAnalyticsPage.tsx b/site/src/pages/AgentsPage/AgentAnalyticsPage.tsx index c1875ab92dc2d..c04769f7c1b7c 100644 --- a/site/src/pages/AgentsPage/AgentAnalyticsPage.tsx +++ b/site/src/pages/AgentsPage/AgentAnalyticsPage.tsx @@ -1,19 +1,22 @@ import dayjs, { type Dayjs } from "dayjs"; import { type FC, useState } from "react"; import { useQuery } from "react-query"; +import { useSearchParams } from "react-router"; import { chatCostSummary } from "#/api/queries/chats"; +import type { DateRangeValue } from "#/components/DateRangePicker/DateRangePicker"; import { ScrollArea } from "#/components/ScrollArea/ScrollArea"; import { useAuthContext } from "#/contexts/auth/AuthProvider"; import { AgentAnalyticsPageView } from "./AgentAnalyticsPageView"; import { AgentPageHeader } from "./components/AgentPageHeader"; -const createDateRange = (now?: Dayjs) => { +const startDateSearchParam = "startDate"; +const endDateSearchParam = "endDate"; + +const getDefaultDateRange = (now?: Dayjs): DateRangeValue => { const end = now ?? dayjs(); - const start = end.subtract(30, "day"); return { - startDate: start.toISOString(), - endDate: end.toISOString(), - rangeLabel: `${start.format("MMM D")} – ${end.format("MMM D, YYYY")}`, + startDate: end.subtract(30, "day").toDate(), + endDate: end.toDate(), }; }; @@ -24,13 +27,44 @@ interface AgentAnalyticsPageProps { const AgentAnalyticsPage: FC = ({ now }) => { const { user } = useAuthContext(); - const [anchor] = useState(() => dayjs()); - const dateRange = createDateRange(now ?? anchor); + + const [searchParams, setSearchParams] = useSearchParams(); + const startDateParam = searchParams.get(startDateSearchParam)?.trim() ?? ""; + const endDateParam = searchParams.get(endDateSearchParam)?.trim() ?? ""; + const [defaultDateRange] = useState(() => getDefaultDateRange(now)); + let dateRange = defaultDateRange; + let hasExplicitDateRange = false; + + if (startDateParam && endDateParam) { + const parsedStartDate = new Date(startDateParam); + const parsedEndDate = new Date(endDateParam); + + if ( + !Number.isNaN(parsedStartDate.getTime()) && + !Number.isNaN(parsedEndDate.getTime()) && + parsedStartDate.getTime() <= parsedEndDate.getTime() + ) { + dateRange = { + startDate: parsedStartDate, + endDate: parsedEndDate, + }; + hasExplicitDateRange = true; + } + } + + const onDateRangeChange = (value: DateRangeValue) => { + setSearchParams((prev) => { + const next = new URLSearchParams(prev); + next.set(startDateSearchParam, value.startDate.toISOString()); + next.set(endDateSearchParam, value.endDate.toISOString()); + return next; + }); + }; const summaryQuery = useQuery({ ...chatCostSummary(user?.id ?? "me", { - start_date: dateRange.startDate, - end_date: dateRange.endDate, + start_date: dateRange.startDate.toISOString(), + end_date: dateRange.endDate.toISOString(), }), enabled: Boolean(user?.id), }); @@ -43,7 +77,9 @@ const AgentAnalyticsPage: FC = ({ now }) => { isLoading={summaryQuery.isLoading} error={summaryQuery.error} onRetry={() => void summaryQuery.refetch()} - rangeLabel={dateRange.rangeLabel} + dateRange={dateRange} + onDateRangeChange={onDateRangeChange} + hasExplicitDateRange={hasExplicitDateRange} /> ); diff --git a/site/src/pages/AgentsPage/AgentAnalyticsPageView.tsx b/site/src/pages/AgentsPage/AgentAnalyticsPageView.tsx index f6aef93e41448..e56aff75f7140 100644 --- a/site/src/pages/AgentsPage/AgentAnalyticsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentAnalyticsPageView.tsx @@ -1,15 +1,21 @@ -import { BarChart3Icon } from "lucide-react"; import type { FC } from "react"; import type { ChatCostSummary } from "#/api/typesGenerated"; +import { + DateRangePicker, + type DateRangeValue, +} from "#/components/DateRangePicker/DateRangePicker"; import { ChatCostSummaryView } from "./components/ChatCostSummaryView"; import { SectionHeader } from "./components/SectionHeader"; +import { toInclusiveDateRange } from "./utils/dateRange"; interface AgentAnalyticsPageViewProps { summary: ChatCostSummary | undefined; isLoading: boolean; error: unknown; onRetry: () => void; - rangeLabel: string; + dateRange: DateRangeValue; + onDateRangeChange: (value: DateRangeValue) => void; + hasExplicitDateRange: boolean; } export const AgentAnalyticsPageView: FC = ({ @@ -17,8 +23,15 @@ export const AgentAnalyticsPageView: FC = ({ isLoading, error, onRetry, - rangeLabel, + dateRange, + onDateRangeChange, + hasExplicitDateRange, }) => { + const displayDateRange = toInclusiveDateRange( + dateRange, + hasExplicitDateRange, + ); + return (
@@ -26,10 +39,10 @@ export const AgentAnalyticsPageView: FC = ({ label="Analytics" description="Review your personal Coder Agents usage and cost breakdowns." action={ -
- - {rangeLabel} -
+ } /> diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ChatModelAdminPanel.tsx b/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ChatModelAdminPanel.tsx index 05bafae5eee68..db0db3c47e981 100644 --- a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ChatModelAdminPanel.tsx +++ b/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ChatModelAdminPanel.tsx @@ -285,7 +285,7 @@ export const ChatModelAdminPanel: FC = ({ const modelConfigsUnavailable = modelConfigsData === null; return ( -
+
{isLoading && (
diff --git a/site/src/pages/AgentsPage/components/MCPServerAdminPanel.tsx b/site/src/pages/AgentsPage/components/MCPServerAdminPanel.tsx index 2355c8501856f..203d46e479b6b 100644 --- a/site/src/pages/AgentsPage/components/MCPServerAdminPanel.tsx +++ b/site/src/pages/AgentsPage/components/MCPServerAdminPanel.tsx @@ -989,7 +989,7 @@ export const MCPServerAdminPanel: FC = ({ } return ( -
+
{!isFormView ? ( = ({ const isEmpty = summary.total_prs_created === 0; return ( -
+
{/* ── Header ── */} -
-
-

- Pull Request Insights -

-

- Code changes detected by Agents. -

-
- -
+ } + action={ + + } + /> {isEmpty ? ( @@ -355,7 +354,7 @@ export const PRInsightsView: FC = ({ {/* ── Model breakdown + Recent PRs side by side ── */} -
+
{/* ── Model performance (simplified) ── */} {by_model.length > 0 && (
diff --git a/site/src/pages/AgentsPage/utils/dateRange.ts b/site/src/pages/AgentsPage/utils/dateRange.ts new file mode 100644 index 0000000000000..23592aa647212 --- /dev/null +++ b/site/src/pages/AgentsPage/utils/dateRange.ts @@ -0,0 +1,35 @@ +import type { DateRangeValue } from "#/components/DateRangePicker/DateRangePicker"; + +/** + * Returns true when the given date falls exactly on midnight + * (00:00:00.000). Date-range pickers use midnight of the *following* + * day as the exclusive upper bound for a full-day selection. Detecting + * this lets call sites subtract 1 ms (or 1 day) so the UI shows the + * inclusive end date instead. + */ +function isMidnight(date: Date): boolean { + return ( + date.getHours() === 0 && + date.getMinutes() === 0 && + date.getSeconds() === 0 && + date.getMilliseconds() === 0 + ); +} + +/** + * When the user picks an explicit date range whose end boundary is + * midnight of the following day, adjust it by −1 ms so the + * DateRangePicker highlights the inclusive end date. + */ +export function toInclusiveDateRange( + dateRange: DateRangeValue, + hasExplicitDateRange: boolean, +): DateRangeValue { + if (hasExplicitDateRange && isMidnight(dateRange.endDate)) { + return { + startDate: dateRange.startDate, + endDate: new Date(dateRange.endDate.getTime() - 1), + }; + } + return dateRange; +}