From 3a6ad3c25df2ee8de076a417d16350e07c227488 Mon Sep 17 00:00:00 2001 From: Recoup Agent Date: Mon, 23 Mar 2026 17:36:01 +0000 Subject: [PATCH 1/3] feat: display pull requests on coding agent /coding page - Add pull_requests: string[] to SlackTag type - Update getTagsByDate to track pull_request_count per date - Update AdminLineChart to support an optional second line (PRs) - Add Pull Requests column to SlackTagsTable - Wire chart and table to show tags vs PRs over time Co-Authored-By: Claude Sonnet 4.6 --- components/Admin/AdminLineChart.tsx | 44 ++++++++++++++++--- .../CodingAgentSlackTagsPage.tsx | 15 ++++++- .../CodingAgentSlackTags/SlackTagsColumns.tsx | 24 ++++++++++ lib/coding-agent/getTagsByDate.ts | 15 ++++--- types/coding-agent.ts | 1 + 5 files changed, 85 insertions(+), 14 deletions(-) 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..44c1229 100644 --- a/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx +++ b/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx @@ -44,7 +44,7 @@ export default function CodingAgentSlackTagsPage() { {isLoading && ( <> - + )} @@ -62,7 +62,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: "Pull Requests", + }} + /> )} diff --git a/components/CodingAgentSlackTags/SlackTagsColumns.tsx b/components/CodingAgentSlackTags/SlackTagsColumns.tsx index 8dad5a3..09e0295 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 ( +
+ {prs.map((url) => ( + + #{url.match(/\/pull\/(\d+)/)?.[1]} + + ))} +
+ ); + }, + }, { id: "timestamp", accessorKey: "timestamp", diff --git a/lib/coding-agent/getTagsByDate.ts b/lib/coding-agent/getTagsByDate.ts index 2222be3..928e5ff 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; } 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..601b24e 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"; From e9f020359bf23d01f43833b211ae40d2b2339b8a Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Mon, 23 Mar 2026 16:42:56 -0500 Subject: [PATCH 2/3] feat: add total_pull_requests and tags_with_pull_requests to response type and UI Co-Authored-By: Claude Opus 4.6 (1M context) --- .../CodingAgentSlackTagsPage.tsx | 16 +++++++++++++--- types/coding-agent.ts | 2 ++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx b/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx index 44c1229..dc5ad44 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 +
)}
diff --git a/types/coding-agent.ts b/types/coding-agent.ts index 601b24e..65ae517 100644 --- a/types/coding-agent.ts +++ b/types/coding-agent.ts @@ -14,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[]; } From 01d5420f5905bdbc6f006bc355d8050630afd321 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Mon, 23 Mar 2026 17:06:42 -0500 Subject: [PATCH 3/3] fix: chart shows tags with PRs instead of total PRs, show repo name in PR column Co-Authored-By: Claude Opus 4.6 (1M context) --- components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx | 2 +- components/CodingAgentSlackTags/SlackTagsColumns.tsx | 2 +- lib/coding-agent/getTagsByDate.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx b/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx index dc5ad44..cdd09d5 100644 --- a/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx +++ b/components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx @@ -81,7 +81,7 @@ export default function CodingAgentSlackTagsPage() { date: d.date, count: d.pull_request_count, })), - label: "Pull Requests", + label: "Tags with PRs", }} /> diff --git a/components/CodingAgentSlackTags/SlackTagsColumns.tsx b/components/CodingAgentSlackTags/SlackTagsColumns.tsx index 09e0295..a89a7a6 100644 --- a/components/CodingAgentSlackTags/SlackTagsColumns.tsx +++ b/components/CodingAgentSlackTags/SlackTagsColumns.tsx @@ -58,7 +58,7 @@ export const slackTagsColumns: ColumnDef[] = [ rel="noopener noreferrer" className="text-sm text-blue-600 hover:underline dark:text-blue-400" > - #{url.match(/\/pull\/(\d+)/)?.[1]} + {url.match(/github\.com\/[^/]+\/([^/]+)\/pull\/(\d+)/)?.slice(1).join("#")} ))}
diff --git a/lib/coding-agent/getTagsByDate.ts b/lib/coding-agent/getTagsByDate.ts index 928e5ff..70db9ad 100644 --- a/lib/coding-agent/getTagsByDate.ts +++ b/lib/coding-agent/getTagsByDate.ts @@ -19,7 +19,7 @@ export function getTagsByDate(tags: SlackTag[]): TagsByDateEntry[] { const date = tag.timestamp.slice(0, 10); // "YYYY-MM-DD" 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; + counts[date].pull_request_count += (tag.pull_requests?.length ?? 0) > 0 ? 1 : 0; } return Object.entries(counts)