diff --git a/components/Admin/AdminLineChart.tsx b/components/Admin/AdminLineChart.tsx index c5d1d5e..984e811 100644 --- a/components/Admin/AdminLineChart.tsx +++ b/components/Admin/AdminLineChart.tsx @@ -3,32 +3,54 @@ import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"; import { ChartContainer, + ChartLegend, + ChartLegendContent, ChartTooltip, ChartTooltipContent, type ChartConfig, } from "@/components/ui/chart"; +interface SecondLine { + data: Array<{ date: string; count: number }>; + label: string; + color?: string; +} + interface AdminLineChartProps { title: string; data: Array<{ date: string; count: number }>; label?: string; + secondLine?: SecondLine; } -export default function AdminLineChart({ title, data, label = "Count" }: AdminLineChartProps) { +export default function AdminLineChart({ + title, + data, + label = "Count", + secondLine, +}: AdminLineChartProps) { if (data.length === 0) return null; const chartConfig = { - count: { - label, - color: "#345A5D", - }, + count: { label, color: "#345A5D" }, + ...(secondLine + ? { count2: { label: secondLine.label, color: secondLine.color ?? "#6B8E93" } } + : {}), } satisfies ChartConfig; + // Merge primary and secondary data by date + const secondMap = new Map(secondLine?.data.map((d) => [d.date, d.count]) ?? []); + const mergedData = data.map((d) => ({ + date: d.date, + count: d.count, + ...(secondLine ? { count2: secondMap.get(d.date) ?? 0 } : {}), + })); + return (

{title}

- + } /> + {secondLine && } />} + {secondLine && ( + + )}
diff --git a/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx b/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx index 4b529b2..cdd09d5 100644 --- a/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx +++ b/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx @@ -34,9 +34,19 @@ export default function CodingAgentSlackTagsPage() {
{data && ( -
- {data.total}{" "} - {data.total === 1 ? "tag" : "tags"} found +
+ + {data.total}{" "} + {data.total === 1 ? "tag" : "tags"} + + + {data.tags_with_pull_requests}{" "} + with PRs + + + {data.total_pull_requests}{" "} + total PRs +
)}
@@ -44,7 +54,7 @@ export default function CodingAgentSlackTagsPage() { {isLoading && ( <> - + )} @@ -62,7 +72,18 @@ export default function CodingAgentSlackTagsPage() { {!isLoading && !error && data && data.tags.length > 0 && ( <> - + ({ date: d.date, count: d.count }))} + label="Tags" + secondLine={{ + data: getTagsByDate(data.tags).map((d) => ({ + date: d.date, + count: d.pull_request_count, + })), + label: "Tags with PRs", + }} + /> )} diff --git a/components/CodingAgentSlackTags/SlackTagsColumns.tsx b/components/CodingAgentSlackTags/SlackTagsColumns.tsx index 8dad5a3..a89a7a6 100644 --- a/components/CodingAgentSlackTags/SlackTagsColumns.tsx +++ b/components/CodingAgentSlackTags/SlackTagsColumns.tsx @@ -41,6 +41,30 @@ export const slackTagsColumns: ColumnDef[] = [ #{getValue()} ), }, + { + id: "pull_requests", + accessorKey: "pull_requests", + header: "Pull Requests", + cell: ({ getValue }) => { + const prs = getValue(); + if (!prs?.length) return ; + return ( + + ); + }, + }, { id: "timestamp", accessorKey: "timestamp", diff --git a/lib/coding-agent/getTagsByDate.ts b/lib/coding-agent/getTagsByDate.ts index 2222be3..70db9ad 100644 --- a/lib/coding-agent/getTagsByDate.ts +++ b/lib/coding-agent/getTagsByDate.ts @@ -1,25 +1,28 @@ import type { SlackTag } from "@/types/coding-agent"; -interface TagsByDateEntry { +export interface TagsByDateEntry { date: string; count: number; + pull_request_count: number; } /** - * Aggregates Slack tags by UTC date (YYYY-MM-DD) for charting. + * Aggregates Slack tags and their associated pull requests by UTC date (YYYY-MM-DD) for charting. * * @param tags - Array of SlackTag objects - * @returns Array of { date, count } sorted ascending by date + * @returns Array of { date, count, pull_request_count } sorted ascending by date */ export function getTagsByDate(tags: SlackTag[]): TagsByDateEntry[] { - const counts: Record = {}; + const counts: Record = {}; for (const tag of tags) { const date = tag.timestamp.slice(0, 10); // "YYYY-MM-DD" - counts[date] = (counts[date] ?? 0) + 1; + if (!counts[date]) counts[date] = { count: 0, pull_request_count: 0 }; + counts[date].count += 1; + counts[date].pull_request_count += (tag.pull_requests?.length ?? 0) > 0 ? 1 : 0; } return Object.entries(counts) - .map(([date, count]) => ({ date, count })) + .map(([date, { count, pull_request_count }]) => ({ date, count, pull_request_count })) .sort((a, b) => a.date.localeCompare(b.date)); } diff --git a/types/coding-agent.ts b/types/coding-agent.ts index 26e9ffd..65ae517 100644 --- a/types/coding-agent.ts +++ b/types/coding-agent.ts @@ -6,6 +6,7 @@ export interface SlackTag { timestamp: string; channel_id: string; channel_name: string; + pull_requests: string[]; } export type { AdminPeriod as SlackTagsPeriod } from "./admin"; @@ -13,5 +14,7 @@ export type { AdminPeriod as SlackTagsPeriod } from "./admin"; export interface SlackTagsResponse { status: "success"; total: number; + total_pull_requests: number; + tags_with_pull_requests: number; tags: SlackTag[]; }