Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion apps/gittensory-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -12136,9 +12136,49 @@
},
"/v1/app/analytics/weekly-value-report": {
"get": {
"parameters": [
{
"schema": {
"type": "string",
"enum": [
"public",
"operator"
],
"example": "public"
},
"required": false,
"description": "Report variant. Operator reports require the operator app role.",
"name": "variant",
"in": "query"
},
{
"schema": {
"type": "string",
"example": "7"
},
"required": false,
"description": "Report window in days, clamped from 1 to 31.",
"name": "days",
"in": "query"
},
{
"schema": {
"type": "string",
"enum": [
"json",
"markdown"
],
"example": "markdown"
},
"required": false,
"description": "Response format. Omit or use json for the structured report; use markdown for copy-ready text.",
"name": "format",
"in": "query"
}
],
"responses": {
"200": {
"description": "Live app API response",
"description": "Weekly value report as structured JSON or copy-ready Markdown",
"content": {
"application/json": {
"schema": {
Expand All @@ -12147,11 +12187,20 @@
"nullable": true
}
}
},
"text/markdown": {
"schema": {
"type": "string",
"example": "# Weekly Gittensory value report\n\n## Adoption metrics\n- Active users: 4\n"
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Insufficient app role for requested report variant"
}
},
"security": [
Expand Down
91 changes: 87 additions & 4 deletions apps/gittensory-ui/src/routes/app.operator.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { useState } from "react";
import { createFileRoute } from "@tanstack/react-router";
import { Check, Copy, FileJson } from "lucide-react";
import { toast } from "sonner";

import {
BoundaryBadge,
Expand All @@ -8,6 +11,8 @@ import {
} from "@/components/site/control-primitives";
import { NotificationReadinessCard } from "@/components/site/notification-readiness-card";
import { StateBoundary } from "@/components/site/state-views";
import { getApiOrigin } from "@/lib/api/origin";
import { apiFetch } from "@/lib/api/request";
import { useApiResource } from "@/lib/api/use-api-resource";

export const Route = createFileRoute("/app/operator")({
Expand All @@ -26,12 +31,40 @@ type OperatorDashboardResponse = {
upstreamDrift?: { status?: string } | null;
};

type ReportExportFormat = "markdown" | "json";

function OperatorDashboard() {
const dashboard = useApiResource<OperatorDashboardResponse>(
"/v1/app/operator-dashboard",
"Operator dashboard",
);
const [copiedExport, setCopiedExport] = useState<ReportExportFormat | null>(null);
const data = dashboard.status === "ready" ? dashboard.data : null;
const copyWeeklyReport = async (format: ReportExportFormat) => {
if (!data?.weeklyValueReport) return;
try {
const text =
format === "json"
? JSON.stringify(data.weeklyValueReport, null, 2)
: await loadWeeklyReportMarkdown();
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
throw new Error("Clipboard API unavailable");
}
await navigator.clipboard.writeText(text);
setCopiedExport(format);
toast.success("Weekly report copied", {
description: `${format === "json" ? "JSON" : "Markdown"} export copied.`,
});
window.setTimeout(() => setCopiedExport(null), 1400);
} catch (error) {
toast.error("Copy failed", {
description:
error instanceof Error && error.message
? `${error.message}. Select the report text and copy manually.`
: "Select the report text and copy manually.",
});
}
};

return (
<StateBoundary
Expand Down Expand Up @@ -92,10 +125,46 @@ function OperatorDashboard() {
</div>

<div className="rounded-token border border-border bg-transparent p-5">
<h2 className="font-display text-token-lg font-semibold">Weekly value report</h2>
<p className="mt-1 text-token-xs text-muted-foreground">
Rollup-backed summary across usage, maintenance, and drift signals.
</p>
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 className="font-display text-token-lg font-semibold">Weekly value report</h2>
<p className="mt-1 text-token-xs text-muted-foreground">
Rollup-backed summary across usage, maintenance, and drift signals.
</p>
</div>
{data.weeklyValueReport ? (
<div className="flex flex-wrap gap-2">
<button
type="button"
onClick={() => void copyWeeklyReport("markdown")}
aria-label="Copy weekly report Markdown"
title="Copy weekly report Markdown"
className="inline-flex h-8 items-center gap-1.5 rounded-token border border-border bg-transparent px-2.5 text-token-xs text-muted-foreground transition-colors duration-150 hover:bg-accent hover:text-foreground focus-ring motion-reduce:transition-none"
>
{copiedExport === "markdown" ? (
<Check className="size-3.5 text-mint" />
) : (
<Copy className="size-3.5" />
)}
Markdown
</button>
<button
type="button"
onClick={() => void copyWeeklyReport("json")}
aria-label="Copy weekly report JSON"
title="Copy weekly report JSON"
className="inline-flex h-8 items-center gap-1.5 rounded-token border border-border bg-transparent px-2.5 text-token-xs text-muted-foreground transition-colors duration-150 hover:bg-accent hover:text-foreground focus-ring motion-reduce:transition-none"
>
{copiedExport === "json" ? (
<Check className="size-3.5 text-mint" />
) : (
<FileJson className="size-3.5" />
)}
JSON
</button>
</div>
) : null}
</div>
{data.weeklyValueReport ? (
<div className="mt-3 flex flex-wrap gap-2">
<StatusPill
Expand Down Expand Up @@ -130,3 +199,17 @@ function OperatorDashboard() {
</StateBoundary>
);
}

async function loadWeeklyReportMarkdown(): Promise<string> {
const result = await apiFetch<string>(
`${getApiOrigin().replace(/\/$/, "")}/v1/app/analytics/weekly-value-report?variant=operator&format=markdown`,
{
label: "Weekly report export",
credentials: "include",
headers: { Accept: "text/markdown" },
parse: (res) => res.text(),
},
);
if (!result.ok) throw new Error(result.message);
return result.data;
}
18 changes: 15 additions & 3 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,12 @@ import {
LATEST_RECOMMENDED_MCP_VERSION,
MINIMUM_SUPPORTED_MCP_VERSION,
} from "../services/mcp-compatibility";
import { buildWeeklyValueReport, generateWeeklyValueReport, loadWeeklyValueReport } from "../services/weekly-value-report";
import {
buildWeeklyValueReport,
formatWeeklyValueReportMarkdown,
generateWeeklyValueReport,
loadWeeklyValueReport,
} from "../services/weekly-value-report";
import { loadOrComputeIssueQualityResponse } from "../services/issue-quality";
import { loadOrComputeBurdenForecastResponse } from "../services/burden-forecast";
import { loadOrComputeRepoOutcomePatternsResponse } from "../services/repo-outcome-patterns";
Expand Down Expand Up @@ -969,11 +974,18 @@ export function createApp() {

app.get("/v1/app/analytics/weekly-value-report", async (c) => {
const variant = c.req.query("variant") === "operator" ? "operator" : "public";
const allowedRoles: ControlPanelRoleName[] = variant === "operator" ? ["operator"] : ["miner", "maintainer", "owner", "operator"];
const allowedRoles: ControlPanelRoleName[] =
variant === "operator" ? ["operator"] : ["miner", "maintainer", "owner", "operator"];
const forbidden = await requireAppRole(c, allowedRoles);
if (forbidden) return forbidden;
const days = Math.max(1, Math.min(31, Number(c.req.query("days") ?? 7) || 7));
return c.json(await loadWeeklyValueReport(c.env, { variant, days }));
const report = await loadWeeklyValueReport(c.env, { variant, days });
if (c.req.query("format") === "markdown") {
return c.text(formatWeeklyValueReportMarkdown(report), 200, {
"Content-Type": "text/markdown; charset=utf-8",
});
}
return c.json(report);
});

app.get("/v1/app/commands", async (c) =>
Expand Down
40 changes: 39 additions & 1 deletion src/openapi/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,6 @@ export function buildOpenApiSpec() {
"/v1/app/digest",
"/v1/app/analytics/daily-rollups",
"/v1/app/analytics/mcp-compatibility",
"/v1/app/analytics/weekly-value-report",
]) {
registry.registerPath({
method: "get",
Expand All @@ -598,6 +597,45 @@ export function buildOpenApiSpec() {
},
});
}
registry.registerPath({
method: "get",
path: "/v1/app/analytics/weekly-value-report",
request: {
query: z.object({
variant: z.enum(["public", "operator"]).optional().openapi({
param: {
description: "Report variant. Operator reports require the operator app role.",
},
example: "public",
}),
days: z.string().optional().openapi({
param: { description: "Report window in days, clamped from 1 to 31." },
example: "7",
}),
format: z.enum(["json", "markdown"]).optional().openapi({
param: {
description: "Response format. Omit or use json for the structured report; use markdown for copy-ready text.",
},
example: "markdown",
}),
}),
},
responses: {
200: {
description: "Weekly value report as structured JSON or copy-ready Markdown",
content: {
"application/json": { schema: z.record(z.string(), z.unknown()) },
"text/markdown": {
schema: z.string().openapi({
example: "# Weekly Gittensory value report\n\n## Adoption metrics\n- Active users: 4\n",
}),
},
},
},
401: { description: "Unauthorized" },
403: { description: "Insufficient app role for requested report variant" },
},
});
registry.registerPath({
method: "post",
path: "/v1/app/commands/preview",
Expand Down
80 changes: 79 additions & 1 deletion src/services/weekly-value-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,37 @@ export function buildWeeklyValueReport(args: WeeklyValueReportInputs): WeeklyVal
};
}

export function formatWeeklyValueReportMarkdown(report: WeeklyValueReport): string {
const lines = [
"# Weekly Gittensory value report",
"",
`- Generated: ${markdownText(report.generatedAt)}`,
`- Variant: ${markdownText(report.variant)}`,
`- Window: ${report.period.days} day(s)${report.period.startDay && report.period.endDay ? `, ${markdownText(report.period.startDay)} to ${markdownText(report.period.endDay)}` : ""}`,
`- Public-safe: ${report.publicSafe ? "yes" : "operator-only"}`,
"",
"## Summary",
...listLines(report.summary),
"",
"## Adoption metrics",
...metricLines(report, ["active_users", "active_repos", "product_events", "active_sessions", "digest_subscriptions"]),
"",
"## Miner utility",
...metricLines(report, ["mcp_usage", "pr_preflights", "pr_packets"]),
"",
"## Maintainer trust",
...metricLines(report, ["github_commands", "quiet_skips", "maintainer_signals", "drift_reports"]),
"",
"## Repo-owner readiness",
...metricLines(report, ["registered_repos", "installed_repos", "installations", "install_issues", "active_repos"]),
"",
"## Known blockers",
...knownBlockerLines(report),
...operatorDetailLines(report),
];
return `${lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
}

function buildWeeklyMetrics(args: {
activeActors: number;
aggregate: WeeklyAggregate;
Expand Down Expand Up @@ -322,6 +353,48 @@ function countDimensions(entries: ProductUsageDimensionCount[], limit = 10): Pro
.slice(0, limit);
}

function metricLines(report: WeeklyValueReport, ids: string[]): string[] {
const metrics = new Map(report.metrics.map((metric) => [metric.id, metric]));
const lines = ids.flatMap((id) => {
const metric = metrics.get(id);
if (!metric) return [];
const detail = markdownText(metric.detail);
return [`- ${markdownText(metric.label)}: ${metric.value}${detail ? ` (${detail})` : ""}`];
});
return lines.length > 0 ? lines : ["- No rollup-backed metric is available for this section."];
}

function knownBlockerLines(report: WeeklyValueReport): string[] {
const blockers = [...report.warnings, ...report.freshness.warnings];
return blockers.length > 0 ? listLines([...new Set(blockers)]) : ["- No known blocker surfaced by the current report window."];
}

function operatorDetailLines(report: WeeklyValueReport): string[] {
if (!report.operatorDetails) return [];
return [
"",
"## Operator detail",
`- Activation: ${report.operatorDetails.activation.fullyActivatedActors} fully activated actor(s), ${report.operatorDetails.activation.githubActivatedRepos} GitHub activated repo(s).`,
...dimensionLines("Top repos", report.operatorDetails.topRepos),
...dimensionLines("Top commands", report.operatorDetails.topCommands),
...dimensionLines("Top tools", report.operatorDetails.topTools),
...dimensionLines("Top route classes", report.operatorDetails.topRouteClasses),
];
}

function dimensionLines(title: string, entries: ProductUsageDimensionCount[]): string[] {
if (entries.length === 0) return [];
return [`- ${title}: ${entries.slice(0, 5).map((entry) => `${markdownText(entry.key)} (${entry.count})`).join(", ")}`];
}

function listLines(items: string[]): string[] {
return items.length > 0 ? items.map((item) => `- ${markdownText(item)}`) : ["- No report data available."];
}

function markdownText(value: string): string {
return sanitizeReportText(value).replace(/\s+/g, " ").trim();
}

function sum(values: number[]): number {
return values.reduce((total, value) => total + value, 0);
}
Expand All @@ -339,6 +412,11 @@ function sanitizeReportText(value: string): string {
.replace(/(?:\/Users|\/home|\/tmp)\/[^\s"',;)]*|[A-Za-z]:\\Users\\[^\s"',;)]*/g, "<redacted-path>")
.replace(/\b(?:ghp_|github_pat_|gts_|glpat-|sk-)[A-Za-z0-9_=-]{8,}/g, "<redacted-token>")
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, "Bearer <redacted-token>");
if (/\b(seed phrase|mnemonic|private key|raw trust|trust score|wallet|hotkey|coldkey|payout|reward estimate|farming|private reviewability|public score estimate)\b/i.test(redacted)) return "<redacted>";
if (
/\b(seed phrase|mnemonic|private key|raw[-\s]?trust|trust[-\s]?score|wallet|hotkey|coldkey|payout|reward(?:[-\s]?(?:estimate|prediction|claim|score|payout|risk))?|farming|private[-\s]?reviewability|private[-\s]?scoreability|scoreability|public[-\s]?score[-\s]?(?:estimate|prediction|claim)|score[-\s]?(?:estimate|prediction|preview))\b/i.test(
redacted,
)
)
return "<redacted>";
return redacted.slice(0, 240);
}
Loading