@git-snitch/renderer 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.d.ts +7 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +53 -0
- package/dist/charts.d.ts +106 -0
- package/dist/charts.d.ts.map +1 -0
- package/dist/charts.js +212 -0
- package/dist/custom-templates.d.ts +3 -0
- package/dist/custom-templates.d.ts.map +1 -0
- package/dist/custom-templates.js +1 -0
- package/dist/data.d.ts +24 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +30 -0
- package/dist/empty-state.d.ts +13 -0
- package/dist/empty-state.d.ts.map +1 -0
- package/dist/empty-state.js +9 -0
- package/dist/export.d.ts +15 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +53 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/inline-plugin.d.ts +13 -0
- package/dist/inline-plugin.d.ts.map +1 -0
- package/dist/inline-plugin.js +81 -0
- package/dist/layout.d.ts +43 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +25 -0
- package/dist/remote-urls.d.ts +6 -0
- package/dist/remote-urls.d.ts.map +1 -0
- package/dist/remote-urls.js +82 -0
- package/dist/serialization.d.ts +5 -0
- package/dist/serialization.d.ts.map +1 -0
- package/dist/serialization.js +46 -0
- package/dist/tables.d.ts +50 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +228 -0
- package/dist/template/report-template.html +135 -0
- package/dist/template.d.ts +21 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +1 -0
- package/dist/theme-toggle.d.ts +2 -0
- package/dist/theme-toggle.d.ts.map +1 -0
- package/dist/theme-toggle.js +9 -0
- package/dist/theme.d.ts +16 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +70 -0
- package/package.json +57 -0
- package/report-template.html +15 -0
- package/src/app.tsx +351 -0
- package/src/build.ts +68 -0
- package/src/charts-route.tsx +158 -0
- package/src/charts.tsx +482 -0
- package/src/custom-template-module.d.ts +5 -0
- package/src/custom-templates.ts +3 -0
- package/src/data.ts +52 -0
- package/src/empty-state.tsx +31 -0
- package/src/export.ts +77 -0
- package/src/index.ts +52 -0
- package/src/inline-plugin.ts +123 -0
- package/src/layout.tsx +152 -0
- package/src/main.tsx +17 -0
- package/src/overview.tsx +253 -0
- package/src/quality-hotspots-routes.tsx +340 -0
- package/src/remote-urls.ts +97 -0
- package/src/repo-routes.tsx +285 -0
- package/src/scan-routes.tsx +393 -0
- package/src/serialization.ts +58 -0
- package/src/styles.css +2 -0
- package/src/tables.tsx +467 -0
- package/src/template.ts +30 -0
- package/src/theme-toggle.tsx +24 -0
- package/src/theme.tsx +108 -0
- package/src/vite-env.d.ts +1 -0
- package/vite.config.ts +41 -0
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReportData } from "@git-snitch/core";
|
|
2
|
+
export interface BuildReportHtmlOptions {
|
|
3
|
+
readonly report: ReportData;
|
|
4
|
+
readonly templatePath?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function buildStandaloneReportHtml(options: BuildReportHtmlOptions): Promise<string>;
|
|
7
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAID,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAMhG"}
|
package/dist/build.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { build as viteBuild } from "vite";
|
|
5
|
+
import { injectReportDataIntoHtml } from "./serialization.js";
|
|
6
|
+
const moduleDirectory = fileURLToPath(new URL(".", import.meta.url));
|
|
7
|
+
export async function buildStandaloneReportHtml(options) {
|
|
8
|
+
const templatePath = options.templatePath ? resolve(options.templatePath) : undefined;
|
|
9
|
+
const templateHtmlPath = await buildRendererTemplate(templatePath);
|
|
10
|
+
const templateHtml = await readFile(templateHtmlPath, "utf8");
|
|
11
|
+
return injectReportDataIntoHtml(templateHtml, options.report);
|
|
12
|
+
}
|
|
13
|
+
async function buildRendererTemplate(templatePath) {
|
|
14
|
+
const packageDirectory = resolve(moduleDirectory, "..");
|
|
15
|
+
const outDir = resolve(packageDirectory, "dist", "template");
|
|
16
|
+
const configFile = resolve(packageDirectory, "vite.config.ts");
|
|
17
|
+
await mkdir(dirname(outDir), { recursive: true });
|
|
18
|
+
await rm(outDir, { recursive: true, force: true });
|
|
19
|
+
const previousTemplateModule = process.env.GIT_SNITCH_TEMPLATE_MODULE;
|
|
20
|
+
try {
|
|
21
|
+
if (templatePath) {
|
|
22
|
+
process.env.GIT_SNITCH_TEMPLATE_MODULE = templatePath;
|
|
23
|
+
await ensureTemplateReadable(templatePath);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
delete process.env.GIT_SNITCH_TEMPLATE_MODULE;
|
|
27
|
+
}
|
|
28
|
+
await viteBuild({ configFile, root: packageDirectory, logLevel: "silent" });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw new Error(`Unable to compile report renderer${templatePath ? ` with template ${templatePath}` : ""}: ${errorMessage(error)}`);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
if (previousTemplateModule === undefined) {
|
|
35
|
+
delete process.env.GIT_SNITCH_TEMPLATE_MODULE;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
process.env.GIT_SNITCH_TEMPLATE_MODULE = previousTemplateModule;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return resolve(outDir, "report-template.html");
|
|
42
|
+
}
|
|
43
|
+
async function ensureTemplateReadable(templatePath) {
|
|
44
|
+
try {
|
|
45
|
+
await readFile(templatePath, "utf8");
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw new Error(`Unable to read custom template ${templatePath}: ${errorMessage(error)}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function errorMessage(error) {
|
|
52
|
+
return error instanceof Error ? error.message : "unknown error";
|
|
53
|
+
}
|
package/dist/charts.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { CommitRecord, ContributorSummary, RepoReportData, ScanProjectReport, ScanReportData } from "@git-snitch/core";
|
|
2
|
+
export type CommitActivityPoint = {
|
|
3
|
+
readonly period: string;
|
|
4
|
+
readonly commits: number;
|
|
5
|
+
};
|
|
6
|
+
export type ContributorPieSlice = {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly commits: number;
|
|
9
|
+
};
|
|
10
|
+
export type LanguageDistributionSlice = {
|
|
11
|
+
readonly language: string;
|
|
12
|
+
readonly lines: number;
|
|
13
|
+
readonly files: number;
|
|
14
|
+
};
|
|
15
|
+
export type AdditionsVsDeletionsPoint = {
|
|
16
|
+
readonly period: string;
|
|
17
|
+
readonly additions: number;
|
|
18
|
+
readonly deletions: number;
|
|
19
|
+
};
|
|
20
|
+
export type CommitSizeBucket = {
|
|
21
|
+
readonly label: string;
|
|
22
|
+
readonly commits: number;
|
|
23
|
+
};
|
|
24
|
+
export type WeeklyActivityPoint = {
|
|
25
|
+
readonly day: string;
|
|
26
|
+
readonly commits: number;
|
|
27
|
+
};
|
|
28
|
+
export type TimeOfDayPoint = {
|
|
29
|
+
readonly hour: string;
|
|
30
|
+
readonly commits: number;
|
|
31
|
+
};
|
|
32
|
+
export type ContributionCalendarDay = {
|
|
33
|
+
readonly date: string;
|
|
34
|
+
readonly commits: number;
|
|
35
|
+
};
|
|
36
|
+
export type VelocityPoint = {
|
|
37
|
+
readonly period: string;
|
|
38
|
+
readonly commits: number;
|
|
39
|
+
readonly average: number;
|
|
40
|
+
};
|
|
41
|
+
export type CodeOwnershipPoint = {
|
|
42
|
+
readonly owner: string;
|
|
43
|
+
readonly additions: number;
|
|
44
|
+
readonly deletions: number;
|
|
45
|
+
readonly filesChanged: number;
|
|
46
|
+
};
|
|
47
|
+
export type ProjectComparisonPoint = {
|
|
48
|
+
readonly project: string;
|
|
49
|
+
readonly commits: number;
|
|
50
|
+
readonly contributors: number;
|
|
51
|
+
readonly filesChanged: number;
|
|
52
|
+
};
|
|
53
|
+
export type ActivityHeatmapCell = {
|
|
54
|
+
readonly day: string;
|
|
55
|
+
readonly hour: string;
|
|
56
|
+
readonly commits: number;
|
|
57
|
+
};
|
|
58
|
+
export declare function deriveCommitActivityData(report: Pick<RepoReportData, "analysis">): readonly CommitActivityPoint[];
|
|
59
|
+
export declare function deriveContributorPieData(contributors: readonly ContributorSummary[]): readonly ContributorPieSlice[];
|
|
60
|
+
export declare function deriveLanguageDistributionData(report: Pick<RepoReportData | ScanReportData, "analysis">): readonly LanguageDistributionSlice[];
|
|
61
|
+
export declare function deriveAdditionsVsDeletionsData(commits: readonly CommitRecord[]): readonly AdditionsVsDeletionsPoint[];
|
|
62
|
+
export declare function deriveCommitSizeDistributionData(commits: readonly CommitRecord[]): readonly CommitSizeBucket[];
|
|
63
|
+
export declare function deriveWeeklyActivityData(commits: readonly CommitRecord[]): readonly WeeklyActivityPoint[];
|
|
64
|
+
export declare function deriveTimeOfDayData(commits: readonly CommitRecord[]): readonly TimeOfDayPoint[];
|
|
65
|
+
export declare function deriveContributionCalendarData(commits: readonly CommitRecord[]): readonly ContributionCalendarDay[];
|
|
66
|
+
export declare function deriveVelocityData(report: Pick<RepoReportData, "analysis">): readonly VelocityPoint[];
|
|
67
|
+
export declare function deriveCodeOwnershipData(contributors: readonly ContributorSummary[]): readonly CodeOwnershipPoint[];
|
|
68
|
+
export declare function deriveProjectsComparisonData(projects: readonly ScanProjectReport[]): readonly ProjectComparisonPoint[];
|
|
69
|
+
export declare function deriveActivityHeatmapData(commits: readonly CommitRecord[]): readonly ActivityHeatmapCell[];
|
|
70
|
+
export declare function CommitActivityChart({ data }: {
|
|
71
|
+
readonly data: readonly CommitActivityPoint[];
|
|
72
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
73
|
+
export declare function ContributorPieChart({ data }: {
|
|
74
|
+
readonly data: readonly ContributorPieSlice[];
|
|
75
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
76
|
+
export declare function LanguageDistributionChart({ data }: {
|
|
77
|
+
readonly data: readonly LanguageDistributionSlice[];
|
|
78
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
79
|
+
export declare function AdditionsVsDeletionsChart({ data }: {
|
|
80
|
+
readonly data: readonly AdditionsVsDeletionsPoint[];
|
|
81
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
82
|
+
export declare function CommitSizeDistributionChart({ data }: {
|
|
83
|
+
readonly data: readonly CommitSizeBucket[];
|
|
84
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
85
|
+
export declare function WeeklyActivityChart({ data }: {
|
|
86
|
+
readonly data: readonly WeeklyActivityPoint[];
|
|
87
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
88
|
+
export declare function TimeOfDayChart({ data }: {
|
|
89
|
+
readonly data: readonly TimeOfDayPoint[];
|
|
90
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
91
|
+
export declare function ContributionCalendar({ data }: {
|
|
92
|
+
readonly data: readonly ContributionCalendarDay[];
|
|
93
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
94
|
+
export declare function VelocityChart({ data }: {
|
|
95
|
+
readonly data: readonly VelocityPoint[];
|
|
96
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
97
|
+
export declare function CodeOwnershipChart({ data }: {
|
|
98
|
+
readonly data: readonly CodeOwnershipPoint[];
|
|
99
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
100
|
+
export declare function ProjectsComparisonChart({ data }: {
|
|
101
|
+
readonly data: readonly ProjectComparisonPoint[];
|
|
102
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
103
|
+
export declare function ActivityHeatmap({ data }: {
|
|
104
|
+
readonly data: readonly ActivityHeatmapCell[];
|
|
105
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
106
|
+
//# sourceMappingURL=charts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"charts.d.ts","sourceRoot":"","sources":["../src/charts.tsx"],"names":[],"mappings":"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"}
|
package/dist/charts.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@git-snitch/ui/components/chart";
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@git-snitch/ui/components/card";
|
|
4
|
+
import { cn } from "@git-snitch/ui/lib/utils";
|
|
5
|
+
import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, Line, LineChart, Pie, PieChart, PolarAngleAxis, PolarGrid, Radar, RadarChart, XAxis, YAxis, } from "recharts";
|
|
6
|
+
import { EmptyState } from "./empty-state.js";
|
|
7
|
+
const chartPalette = ["var(--chart-1)", "var(--chart-2)", "var(--chart-3)", "var(--chart-4)", "var(--chart-5)"];
|
|
8
|
+
const shortWeekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
9
|
+
const staticChartProps = { isAnimationActive: false };
|
|
10
|
+
function ChartPanel({ title, description, children, isEmpty, emptyTitle, emptyDescription }) {
|
|
11
|
+
if (isEmpty) {
|
|
12
|
+
return _jsx(EmptyState, { title: emptyTitle, description: emptyDescription });
|
|
13
|
+
}
|
|
14
|
+
return (_jsxs(Card, { className: "shadow-none", children: [_jsxs(CardHeader, { className: "space-y-2", children: [_jsx(CardTitle, { className: "text-base font-semibold tracking-tight text-foreground", children: title }), _jsx("p", { className: "text-sm leading-6 text-muted-foreground", children: description })] }), _jsx(CardContent, { children: children })] }));
|
|
15
|
+
}
|
|
16
|
+
function hasPositiveValue(data, select) {
|
|
17
|
+
return data.some((item) => select(item).some((value) => value > 0));
|
|
18
|
+
}
|
|
19
|
+
function parseIsoDate(value) {
|
|
20
|
+
const date = new Date(value);
|
|
21
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
22
|
+
}
|
|
23
|
+
function monthKey(value) {
|
|
24
|
+
return value.slice(0, 7);
|
|
25
|
+
}
|
|
26
|
+
function weekdayForDate(date) {
|
|
27
|
+
return shortWeekdays[date.getUTCDay()] ?? "Sun";
|
|
28
|
+
}
|
|
29
|
+
export function deriveCommitActivityData(report) {
|
|
30
|
+
return report.analysis.cadence.map((point) => ({ period: point.period, commits: point.commits }));
|
|
31
|
+
}
|
|
32
|
+
export function deriveContributorPieData(contributors) {
|
|
33
|
+
return contributors
|
|
34
|
+
.filter((contributor) => contributor.commitCount > 0)
|
|
35
|
+
.map((contributor) => ({ name: contributor.name, commits: contributor.commitCount }))
|
|
36
|
+
.sort((left, right) => right.commits - left.commits || left.name.localeCompare(right.name));
|
|
37
|
+
}
|
|
38
|
+
export function deriveLanguageDistributionData(report) {
|
|
39
|
+
return report.analysis.languages
|
|
40
|
+
.filter((language) => language.lines > 0 || language.files > 0)
|
|
41
|
+
.map((language) => ({ language: language.language, lines: language.lines, files: language.files }))
|
|
42
|
+
.sort((left, right) => right.lines - left.lines || left.language.localeCompare(right.language));
|
|
43
|
+
}
|
|
44
|
+
export function deriveAdditionsVsDeletionsData(commits) {
|
|
45
|
+
const periods = new Map();
|
|
46
|
+
for (const commit of commits) {
|
|
47
|
+
const period = monthKey(commit.authoredAt);
|
|
48
|
+
const current = periods.get(period) ?? { additions: 0, deletions: 0 };
|
|
49
|
+
current.additions += commit.files.reduce((sum, file) => sum + file.additions, 0);
|
|
50
|
+
current.deletions += commit.files.reduce((sum, file) => sum + file.deletions, 0);
|
|
51
|
+
periods.set(period, current);
|
|
52
|
+
}
|
|
53
|
+
return [...periods.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([period, totals]) => ({ period, ...totals }));
|
|
54
|
+
}
|
|
55
|
+
export function deriveCommitSizeDistributionData(commits) {
|
|
56
|
+
const buckets = [
|
|
57
|
+
{ label: "0 (empty)", min: 0, max: 0, commits: 0 },
|
|
58
|
+
{ label: "1-10", min: 1, max: 10, commits: 0 },
|
|
59
|
+
{ label: "11-50", min: 11, max: 50, commits: 0 },
|
|
60
|
+
{ label: "51-200", min: 51, max: 200, commits: 0 },
|
|
61
|
+
{ label: "201+", min: 201, max: Number.POSITIVE_INFINITY, commits: 0 },
|
|
62
|
+
];
|
|
63
|
+
for (const commit of commits) {
|
|
64
|
+
const changedLines = commit.files.reduce((sum, file) => sum + file.additions + file.deletions, 0);
|
|
65
|
+
const bucket = buckets.find((candidate) => changedLines >= candidate.min && changedLines <= candidate.max);
|
|
66
|
+
if (bucket) {
|
|
67
|
+
bucket.commits += 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return buckets.map(({ label, commits }) => ({ label, commits }));
|
|
71
|
+
}
|
|
72
|
+
export function deriveWeeklyActivityData(commits) {
|
|
73
|
+
const counts = new Map(shortWeekdays.map((day) => [day, 0]));
|
|
74
|
+
for (const commit of commits) {
|
|
75
|
+
const date = parseIsoDate(commit.authoredAt);
|
|
76
|
+
if (date) {
|
|
77
|
+
const day = weekdayForDate(date);
|
|
78
|
+
counts.set(day, (counts.get(day) ?? 0) + 1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return shortWeekdays.map((day) => ({ day, commits: counts.get(day) ?? 0 }));
|
|
82
|
+
}
|
|
83
|
+
export function deriveTimeOfDayData(commits) {
|
|
84
|
+
const counts = Array.from({ length: 24 }, () => 0);
|
|
85
|
+
for (const commit of commits) {
|
|
86
|
+
const date = parseIsoDate(commit.authoredAt);
|
|
87
|
+
if (date) {
|
|
88
|
+
const hour = date.getUTCHours();
|
|
89
|
+
counts[hour] = (counts[hour] ?? 0) + 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return counts.map((commits, hour) => ({ hour: `${hour.toString().padStart(2, "0")}:00`, commits }));
|
|
93
|
+
}
|
|
94
|
+
export function deriveContributionCalendarData(commits) {
|
|
95
|
+
if (commits.length === 0)
|
|
96
|
+
return [];
|
|
97
|
+
const counts = new Map();
|
|
98
|
+
let minDate = "";
|
|
99
|
+
let maxDate = "";
|
|
100
|
+
for (const commit of commits) {
|
|
101
|
+
const date = commit.authoredAt.slice(0, 10);
|
|
102
|
+
counts.set(date, (counts.get(date) ?? 0) + 1);
|
|
103
|
+
if (minDate === "" || date < minDate)
|
|
104
|
+
minDate = date;
|
|
105
|
+
if (maxDate === "" || date > maxDate)
|
|
106
|
+
maxDate = date;
|
|
107
|
+
}
|
|
108
|
+
const result = [];
|
|
109
|
+
const current = new Date(`${minDate}T00:00:00Z`);
|
|
110
|
+
const end = new Date(`${maxDate}T00:00:00Z`);
|
|
111
|
+
while (current <= end) {
|
|
112
|
+
const dateStr = current.toISOString().slice(0, 10);
|
|
113
|
+
result.push({ date: dateStr, commits: counts.get(dateStr) ?? 0 });
|
|
114
|
+
current.setUTCDate(current.getUTCDate() + 1);
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
export function deriveVelocityData(report) {
|
|
119
|
+
const activity = deriveCommitActivityData(report);
|
|
120
|
+
return activity.map((point, index) => {
|
|
121
|
+
const window = activity.slice(Math.max(0, index - 2), index + 1);
|
|
122
|
+
const average = window.reduce((sum, item) => sum + item.commits, 0) / window.length;
|
|
123
|
+
return { ...point, average: Math.round(average * 10) / 10 };
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
export function deriveCodeOwnershipData(contributors) {
|
|
127
|
+
return contributors
|
|
128
|
+
.filter((contributor) => contributor.additions > 0 || contributor.deletions > 0 || contributor.filesChanged > 0)
|
|
129
|
+
.map((contributor) => ({
|
|
130
|
+
owner: contributor.name,
|
|
131
|
+
additions: contributor.additions,
|
|
132
|
+
deletions: contributor.deletions,
|
|
133
|
+
filesChanged: contributor.filesChanged,
|
|
134
|
+
}))
|
|
135
|
+
.sort((left, right) => right.filesChanged - left.filesChanged || left.owner.localeCompare(right.owner));
|
|
136
|
+
}
|
|
137
|
+
export function deriveProjectsComparisonData(projects) {
|
|
138
|
+
return projects
|
|
139
|
+
.map((project) => ({
|
|
140
|
+
project: project.repository.relativePath,
|
|
141
|
+
commits: project.report.repository.totalCommits,
|
|
142
|
+
contributors: project.report.repository.totalContributors,
|
|
143
|
+
filesChanged: new Set(project.report.commits.flatMap((commit) => commit.files.map((file) => file.path))).size,
|
|
144
|
+
}))
|
|
145
|
+
.sort((left, right) => right.commits - left.commits || left.project.localeCompare(right.project));
|
|
146
|
+
}
|
|
147
|
+
export function deriveActivityHeatmapData(commits) {
|
|
148
|
+
const counts = new Map();
|
|
149
|
+
for (const commit of commits) {
|
|
150
|
+
const date = parseIsoDate(commit.authoredAt);
|
|
151
|
+
if (date) {
|
|
152
|
+
const key = `${weekdayForDate(date)}-${date.getUTCHours().toString().padStart(2, "0")}:00`;
|
|
153
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return shortWeekdays.flatMap((day) => Array.from({ length: 24 }, (_, hour) => {
|
|
157
|
+
const label = `${hour.toString().padStart(2, "0")}:00`;
|
|
158
|
+
return { day, hour: label, commits: counts.get(`${day}-${label}`) ?? 0 };
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
export function CommitActivityChart({ data }) {
|
|
162
|
+
return (_jsx(ChartPanel, { title: "Commit activity", description: "Monthly commit cadence without decorative chrome.", isEmpty: !hasPositiveValue(data, (item) => [item.commits]), emptyTitle: "No commit activity to chart", emptyDescription: "This report has no dated commits, so there is no cadence data yet.", children: _jsx(ChartContainer, { config: { commits: { label: "Commits", color: chartPalette[1] } }, className: "h-64 w-full", children: _jsxs(AreaChart, { data: data, margin: { left: 0, right: 12, top: 8, bottom: 0 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: "period", tickLine: false, axisLine: false }), _jsx(YAxis, { width: 32, tickLine: false, axisLine: false, allowDecimals: false }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Area, { type: "monotone", dataKey: "commits", stroke: "var(--color-commits)", fill: "var(--color-commits)", fillOpacity: 0.18, ...staticChartProps })] }) }) }));
|
|
163
|
+
}
|
|
164
|
+
export function ContributorPieChart({ data }) {
|
|
165
|
+
return (_jsx(ChartPanel, { title: "Contributor share", description: "Commit share by contributor.", isEmpty: !hasPositiveValue(data, (item) => [item.commits]), emptyTitle: "No contributor share to chart", emptyDescription: "Contributor share appears after at least one contributor has commits.", children: _jsx(ChartContainer, { config: { commits: { label: "Commits" } }, className: "h-64 w-full", children: _jsxs(PieChart, { children: [_jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { nameKey: "name" }) }), _jsx(Pie, { data: data, dataKey: "commits", nameKey: "name", innerRadius: 56, outerRadius: 88, paddingAngle: 2, ...staticChartProps, children: data.map((slice, index) => (_jsx(Cell, { fill: chartPalette[index % chartPalette.length] }, slice.name))) })] }) }) }));
|
|
166
|
+
}
|
|
167
|
+
export function LanguageDistributionChart({ data }) {
|
|
168
|
+
return (_jsx(ChartPanel, { title: "Language distribution", description: "Lines by detected language.", isEmpty: !hasPositiveValue(data, (item) => [item.lines, item.files]), emptyTitle: "No language distribution to chart", emptyDescription: "Language data is unavailable when no source files were counted.", children: _jsx(ChartContainer, { config: { lines: { label: "Lines", color: chartPalette[0] } }, className: "h-64 w-full", children: _jsxs(BarChart, { data: data, layout: "vertical", margin: { left: 8, right: 16, top: 8, bottom: 0 }, children: [_jsx(CartesianGrid, { horizontal: false }), _jsx(XAxis, { type: "number", hide: true }), _jsx(YAxis, { dataKey: "language", type: "category", width: 96, tickLine: false, axisLine: false }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Bar, { dataKey: "lines", fill: "var(--color-lines)", radius: [0, 3, 3, 0], ...staticChartProps })] }) }) }));
|
|
169
|
+
}
|
|
170
|
+
export function AdditionsVsDeletionsChart({ data }) {
|
|
171
|
+
return (_jsx(ChartPanel, { title: "Additions vs deletions", description: "Monthly churn split into added and removed lines.", isEmpty: !hasPositiveValue(data, (item) => [item.additions, item.deletions]), emptyTitle: "No line churn to chart", emptyDescription: "This report has no file-level additions or deletions.", children: _jsx(ChartContainer, { config: { additions: { label: "Additions", color: chartPalette[0] }, deletions: { label: "Deletions", color: chartPalette[4] } }, className: "h-64 w-full", children: _jsxs(BarChart, { data: data, margin: { left: 0, right: 12, top: 8, bottom: 0 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: "period", tickLine: false, axisLine: false }), _jsx(YAxis, { width: 40, tickLine: false, axisLine: false, allowDecimals: false }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Bar, { dataKey: "additions", fill: "var(--color-additions)", radius: [3, 3, 0, 0], ...staticChartProps }), _jsx(Bar, { dataKey: "deletions", fill: "var(--color-deletions)", radius: [3, 3, 0, 0], ...staticChartProps })] }) }) }));
|
|
172
|
+
}
|
|
173
|
+
export function CommitSizeDistributionChart({ data }) {
|
|
174
|
+
return (_jsx(ChartPanel, { title: "Commit size distribution", description: "Commits grouped by touched lines.", isEmpty: !hasPositiveValue(data, (item) => [item.commits]), emptyTitle: "No commit sizes to chart", emptyDescription: "Commit size distribution needs commits with file-level line changes.", children: _jsx(ChartContainer, { config: { commits: { label: "Commits", color: chartPalette[2] } }, className: "h-64 w-full", children: _jsxs(BarChart, { data: data, margin: { left: 0, right: 12, top: 8, bottom: 0 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: "label", tickLine: false, axisLine: false }), _jsx(YAxis, { width: 32, tickLine: false, axisLine: false, allowDecimals: false }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Bar, { dataKey: "commits", fill: "var(--color-commits)", radius: [3, 3, 0, 0], ...staticChartProps })] }) }) }));
|
|
175
|
+
}
|
|
176
|
+
export function WeeklyActivityChart({ data }) {
|
|
177
|
+
return (_jsx(ChartPanel, { title: "Weekly activity", description: "Commits by UTC weekday.", isEmpty: !hasPositiveValue(data, (item) => [item.commits]), emptyTitle: "No weekly activity to chart", emptyDescription: "This activity view needs at least one dated commit.", children: _jsx(ChartContainer, { config: { commits: { label: "Commits", color: chartPalette[1] } }, className: "h-64 w-full", children: _jsxs(BarChart, { data: data, margin: { left: 0, right: 12, top: 8, bottom: 0 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: "day", tickLine: false, axisLine: false, interval: "preserveStartEnd" }), _jsx(YAxis, { width: 32, tickLine: false, axisLine: false, allowDecimals: false }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Bar, { dataKey: "commits", fill: "var(--color-commits)", radius: [3, 3, 0, 0], ...staticChartProps })] }) }) }));
|
|
178
|
+
}
|
|
179
|
+
export function TimeOfDayChart({ data }) {
|
|
180
|
+
return (_jsx(ChartPanel, { title: "Time of day", description: "Commits by UTC hour.", isEmpty: !hasPositiveValue(data, (item) => [item.commits]), emptyTitle: "No hourly activity to chart", emptyDescription: "This activity view needs at least one dated commit.", children: _jsx(ChartContainer, { config: { commits: { label: "Commits", color: chartPalette[1] } }, className: "h-64 w-full", children: _jsxs(BarChart, { data: data, margin: { left: 0, right: 12, top: 8, bottom: 0 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: "hour", tickLine: false, axisLine: false, interval: "preserveStartEnd" }), _jsx(YAxis, { width: 32, tickLine: false, axisLine: false, allowDecimals: false }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Bar, { dataKey: "commits", fill: "var(--color-commits)", radius: [3, 3, 0, 0], ...staticChartProps })] }) }) }));
|
|
181
|
+
}
|
|
182
|
+
export function ContributionCalendar({ data }) {
|
|
183
|
+
const firstDate = data.length > 0 ? data[0] : undefined;
|
|
184
|
+
const firstDayOffset = firstDate !== undefined
|
|
185
|
+
? new Date(`${firstDate.date}T00:00:00Z`).getUTCDay()
|
|
186
|
+
: 0;
|
|
187
|
+
return (_jsx(ChartPanel, { title: "Contribution calendar", description: "Daily commit density.", isEmpty: !hasPositiveValue(data, (item) => [item.commits]), emptyTitle: "No contribution calendar to show", emptyDescription: "The calendar needs at least one dated commit.", children: _jsxs("div", { className: "grid grid-cols-7 gap-1", "aria-label": "Daily contribution calendar", children: [shortWeekdays.map((day) => (_jsx("div", { className: "text-center text-xs text-muted-foreground", children: day }, day))), Array.from({ length: firstDayOffset }, (_, i) => (_jsx("div", {}, `offset-${i}`))), data.map((day) => (_jsx("div", { title: `${day.date}: ${day.commits} commits`, className: cn("h-7 rounded-sm border", heatClass(day.commits)), children: _jsx("span", { className: "sr-only", children: `${day.date}: ${day.commits} commits` }) }, day.date)))] }) }));
|
|
188
|
+
}
|
|
189
|
+
export function VelocityChart({ data }) {
|
|
190
|
+
return (_jsx(ChartPanel, { title: "Velocity", description: "Commit cadence with a three-period rolling average.", isEmpty: !hasPositiveValue(data, (item) => [item.commits, item.average]), emptyTitle: "No velocity to chart", emptyDescription: "Velocity appears after commits exist across dated periods.", children: _jsx(ChartContainer, { config: { commits: { label: "Commits", color: chartPalette[1] }, average: { label: "Average", color: chartPalette[4] } }, className: "h-64 w-full", children: _jsxs(LineChart, { data: data, margin: { left: 0, right: 12, top: 8, bottom: 0 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: "period", tickLine: false, axisLine: false }), _jsx(YAxis, { width: 32, tickLine: false, axisLine: false, allowDecimals: false }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Line, { type: "monotone", dataKey: "commits", stroke: "var(--color-commits)", strokeWidth: 2, dot: false, ...staticChartProps }), _jsx(Line, { type: "monotone", dataKey: "average", stroke: "var(--color-average)", strokeWidth: 2, dot: false, strokeDasharray: "4 4", ...staticChartProps })] }) }) }));
|
|
191
|
+
}
|
|
192
|
+
export function CodeOwnershipChart({ data }) {
|
|
193
|
+
return (_jsx(ChartPanel, { title: "Code ownership", description: "Contributor footprint by changed files and churn.", isEmpty: !hasPositiveValue(data, (item) => [item.filesChanged, item.additions, item.deletions]), emptyTitle: "No ownership data to chart", emptyDescription: "Ownership needs contributor file or churn statistics.", children: _jsx(ChartContainer, { config: { additions: { label: "Additions", color: chartPalette[0] }, deletions: { label: "Deletions", color: chartPalette[3] }, filesChanged: { label: "Files changed", color: chartPalette[2] } }, className: "h-72 w-full", children: _jsxs(RadarChart, { data: data, margin: { left: 16, right: 16, top: 8, bottom: 8 }, children: [_jsx(PolarGrid, {}), _jsx(PolarAngleAxis, { dataKey: "owner" }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Radar, { dataKey: "filesChanged", stroke: "var(--color-filesChanged)", fill: "var(--color-filesChanged)", fillOpacity: 0.18, ...staticChartProps })] }) }) }));
|
|
194
|
+
}
|
|
195
|
+
export function ProjectsComparisonChart({ data }) {
|
|
196
|
+
return (_jsx(ChartPanel, { title: "Projects comparison", description: "Repository activity across a scan.", isEmpty: !hasPositiveValue(data, (item) => [item.commits, item.contributors, item.filesChanged]), emptyTitle: "No projects to compare", emptyDescription: "Project comparison needs at least one scanned repository with activity.", children: _jsx(ChartContainer, { config: { commits: { label: "Commits", color: chartPalette[1] }, contributors: { label: "Contributors", color: chartPalette[3] } }, className: "h-72 w-full", children: _jsxs(BarChart, { data: data, layout: "vertical", margin: { left: 8, right: 16, top: 8, bottom: 0 }, children: [_jsx(CartesianGrid, { horizontal: false }), _jsx(XAxis, { type: "number", hide: true }), _jsx(YAxis, { dataKey: "project", type: "category", width: 112, tickLine: false, axisLine: false }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Bar, { dataKey: "commits", fill: "var(--color-commits)", radius: [0, 3, 3, 0], ...staticChartProps }), _jsx(Bar, { dataKey: "contributors", fill: "var(--color-contributors)", radius: [0, 3, 3, 0], ...staticChartProps })] }) }) }));
|
|
197
|
+
}
|
|
198
|
+
export function ActivityHeatmap({ data }) {
|
|
199
|
+
return (_jsx(ChartPanel, { title: "Activity heatmap", description: "UTC weekday and hour density.", isEmpty: !hasPositiveValue(data, (item) => [item.commits]), emptyTitle: "No activity heatmap to show", emptyDescription: "The heatmap needs at least one dated commit.", children: _jsx("div", { className: "grid grid-flow-dense grid-cols-[repeat(24,minmax(0,1fr))] gap-1", "aria-label": "Activity heatmap by day and hour", children: data.map((cell) => (_jsx("div", { title: `${cell.day} ${cell.hour}: ${cell.commits} commits`, className: cn("h-4 rounded-[2px] border", heatClass(cell.commits)), children: _jsx("span", { className: "sr-only", children: `${cell.day} ${cell.hour}: ${cell.commits} commits` }) }, `${cell.day}-${cell.hour}`))) }) }));
|
|
200
|
+
}
|
|
201
|
+
function heatClass(commits) {
|
|
202
|
+
if (commits >= 8) {
|
|
203
|
+
return "border-chart-4/40 bg-chart-4";
|
|
204
|
+
}
|
|
205
|
+
if (commits >= 4) {
|
|
206
|
+
return "border-chart-3/40 bg-chart-3/80";
|
|
207
|
+
}
|
|
208
|
+
if (commits >= 1) {
|
|
209
|
+
return "border-chart-2/30 bg-chart-2/45";
|
|
210
|
+
}
|
|
211
|
+
return "border-border bg-muted/45";
|
|
212
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-templates.d.ts","sourceRoot":"","sources":["../src/custom-templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAE5D,eAAO,MAAM,SAAS,EAAE,sBAA2B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const templates = {};
|
package/dist/data.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RepoReportData, ReportData, ScanReportData } from "@git-snitch/core";
|
|
2
|
+
declare global {
|
|
3
|
+
interface Window {
|
|
4
|
+
readonly __GIT_SNITCH_REPORT_DATA__?: unknown;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export type ReportDataState = {
|
|
8
|
+
readonly status: "ready";
|
|
9
|
+
readonly report: ReportData;
|
|
10
|
+
} | {
|
|
11
|
+
readonly status: "missing";
|
|
12
|
+
} | {
|
|
13
|
+
readonly status: "invalid";
|
|
14
|
+
readonly reason: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function readInjectedReportData(): ReportDataState;
|
|
17
|
+
export declare function isReadyReportData(state: ReportDataState): state is {
|
|
18
|
+
readonly status: "ready";
|
|
19
|
+
readonly report: ReportData;
|
|
20
|
+
};
|
|
21
|
+
export declare function useReportData(): ReportDataState;
|
|
22
|
+
export declare function useIsRepoReport(report: ReportData | null | undefined): report is RepoReportData;
|
|
23
|
+
export declare function useIsScanReport(report: ReportData | null | undefined): report is ScanReportData;
|
|
24
|
+
//# sourceMappingURL=data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../src/data.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEnF,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,QAAQ,CAAC,0BAA0B,CAAC,EAAE,OAAO,CAAC;KAC/C;CACF;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GACzD;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,wBAAgB,sBAAsB,IAAI,eAAe,CAoBxD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,IAAI;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CAE5H;AAED,wBAAgB,aAAa,IAAI,eAAe,CAE/C;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,IAAI,cAAc,CAE/F;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,IAAI,cAAc,CAE/F"}
|
package/dist/data.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { isRepoReportData, isScanReportData, reportDataDiscriminantSchema } from "@git-snitch/core/report-data";
|
|
3
|
+
export function readInjectedReportData() {
|
|
4
|
+
if (typeof window === "undefined") {
|
|
5
|
+
return { status: "missing" };
|
|
6
|
+
}
|
|
7
|
+
const candidate = window.__GIT_SNITCH_REPORT_DATA__;
|
|
8
|
+
if (candidate === undefined || typeof candidate === "string") {
|
|
9
|
+
return { status: "missing" };
|
|
10
|
+
}
|
|
11
|
+
if (isRepoReportData(candidate) || isScanReportData(candidate)) {
|
|
12
|
+
return { status: "ready", report: candidate };
|
|
13
|
+
}
|
|
14
|
+
if (reportDataDiscriminantSchema.safeParse(candidate).success) {
|
|
15
|
+
return { status: "invalid", reason: "Injected report data is missing required report sections." };
|
|
16
|
+
}
|
|
17
|
+
return { status: "invalid", reason: "Injected report data does not match the git-snitch report contract." };
|
|
18
|
+
}
|
|
19
|
+
export function isReadyReportData(state) {
|
|
20
|
+
return state.status === "ready";
|
|
21
|
+
}
|
|
22
|
+
export function useReportData() {
|
|
23
|
+
return useMemo(() => readInjectedReportData(), []);
|
|
24
|
+
}
|
|
25
|
+
export function useIsRepoReport(report) {
|
|
26
|
+
return Boolean(report && report.kind === "repo");
|
|
27
|
+
}
|
|
28
|
+
export function useIsScanReport(report) {
|
|
29
|
+
return Boolean(report && report.kind === "scan");
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
type EmptyStateProps = {
|
|
3
|
+
readonly title: string;
|
|
4
|
+
readonly description: string;
|
|
5
|
+
readonly action?: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
export declare function EmptyState({ title, description, action }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare function EmptyStateAction({ href, children }: {
|
|
9
|
+
readonly href: string;
|
|
10
|
+
readonly children: ReactNode;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=empty-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../src/empty-state.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B,CAAC;AAEF,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,eAAe,2CAYzE;AAED,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAM3G"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { buttonVariants } from "@git-snitch/ui/components/button";
|
|
3
|
+
import { Card, CardContent } from "@git-snitch/ui/components/card";
|
|
4
|
+
export function EmptyState({ title, description, action }) {
|
|
5
|
+
return (_jsx(Card, { className: "border-dashed bg-card/70 shadow-none", children: _jsxs(CardContent, { className: "flex flex-col items-start gap-4 py-8 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "max-w-xl", children: [_jsx("h2", { className: "text-base font-semibold tracking-tight text-foreground", children: title }), _jsx("p", { className: "mt-2 text-sm leading-6 text-muted-foreground", children: description })] }), action ? _jsx("div", { className: "shrink-0", children: action }) : null] }) }));
|
|
6
|
+
}
|
|
7
|
+
export function EmptyStateAction({ href, children }) {
|
|
8
|
+
return (_jsx("a", { href: href, className: buttonVariants({ variant: "outline", size: "sm" }), children: children }));
|
|
9
|
+
}
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JsonValue, ReportData } from "@git-snitch/core";
|
|
2
|
+
export type CsvCell = string | number | boolean | null | undefined;
|
|
3
|
+
export type CsvRow = Readonly<Record<string, CsvCell>>;
|
|
4
|
+
export type DownloadResult = {
|
|
5
|
+
readonly status: "downloaded";
|
|
6
|
+
} | {
|
|
7
|
+
readonly status: "unavailable";
|
|
8
|
+
readonly reason: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function serializeCsv(rows: readonly CsvRow[], columns?: readonly string[]): string;
|
|
11
|
+
export declare function serializeReportJson(report: ReportData | JsonValue): string;
|
|
12
|
+
export declare function downloadTextFile(filename: string, content: string, mimeType: string): DownloadResult;
|
|
13
|
+
export declare function downloadCsv(filename: string, rows: readonly CsvRow[], columns?: readonly string[]): DownloadResult;
|
|
14
|
+
export declare function downloadJson(filename: string, report: ReportData | JsonValue): DownloadResult;
|
|
15
|
+
//# sourceMappingURL=export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9D,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AACnE,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvD,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACjC;IAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAwBhE,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAUzF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAE1E;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,CAmBpG;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAGlH;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,cAAc,CAG7F"}
|
package/dist/export.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const JSON_INDENT = 2;
|
|
2
|
+
function hasBrowserDownloadApis() {
|
|
3
|
+
return (typeof document !== "undefined" &&
|
|
4
|
+
typeof Blob !== "undefined" &&
|
|
5
|
+
typeof URL !== "undefined" &&
|
|
6
|
+
typeof URL.createObjectURL === "function" &&
|
|
7
|
+
typeof URL.revokeObjectURL === "function");
|
|
8
|
+
}
|
|
9
|
+
function escapeCsvCell(cell) {
|
|
10
|
+
if (cell === null || cell === undefined) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
const value = String(cell);
|
|
14
|
+
const escaped = value.replaceAll('"', '""');
|
|
15
|
+
return /[",\n\r]/.test(escaped) ? `"${escaped}"` : escaped;
|
|
16
|
+
}
|
|
17
|
+
export function serializeCsv(rows, columns) {
|
|
18
|
+
const resolvedColumns = columns ?? Array.from(new Set(rows.flatMap((row) => Object.keys(row))));
|
|
19
|
+
if (resolvedColumns.length === 0) {
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
const header = resolvedColumns.map(escapeCsvCell).join(",");
|
|
23
|
+
const body = rows.map((row) => resolvedColumns.map((column) => escapeCsvCell(row[column])).join(","));
|
|
24
|
+
return [header, ...body].join("\n");
|
|
25
|
+
}
|
|
26
|
+
export function serializeReportJson(report) {
|
|
27
|
+
return JSON.stringify(report, null, JSON_INDENT);
|
|
28
|
+
}
|
|
29
|
+
export function downloadTextFile(filename, content, mimeType) {
|
|
30
|
+
if (!hasBrowserDownloadApis()) {
|
|
31
|
+
return { status: "unavailable", reason: "Browser download APIs are not available in this environment." };
|
|
32
|
+
}
|
|
33
|
+
const blob = new Blob([content], { type: mimeType });
|
|
34
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
35
|
+
const anchor = document.createElement("a");
|
|
36
|
+
anchor.href = objectUrl;
|
|
37
|
+
anchor.download = filename;
|
|
38
|
+
anchor.rel = "noopener";
|
|
39
|
+
anchor.style.display = "none";
|
|
40
|
+
document.body.append(anchor);
|
|
41
|
+
anchor.click();
|
|
42
|
+
anchor.remove();
|
|
43
|
+
URL.revokeObjectURL(objectUrl);
|
|
44
|
+
return { status: "downloaded" };
|
|
45
|
+
}
|
|
46
|
+
export function downloadCsv(filename, rows, columns) {
|
|
47
|
+
const csv = serializeCsv(rows, columns);
|
|
48
|
+
return downloadTextFile(filename, csv, "text/csv;charset=utf-8");
|
|
49
|
+
}
|
|
50
|
+
export function downloadJson(filename, report) {
|
|
51
|
+
const json = serializeReportJson(report);
|
|
52
|
+
return downloadTextFile(filename, json, "application/json;charset=utf-8");
|
|
53
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { ActivityHeatmap, AdditionsVsDeletionsChart, CodeOwnershipChart, CommitActivityChart, CommitSizeDistributionChart, ContributionCalendar, ContributorPieChart, LanguageDistributionChart, ProjectsComparisonChart, TimeOfDayChart, VelocityChart, WeeklyActivityChart, deriveActivityHeatmapData, deriveAdditionsVsDeletionsData, deriveCodeOwnershipData, deriveCommitActivityData, deriveCommitSizeDistributionData, deriveContributionCalendarData, deriveContributorPieData, deriveLanguageDistributionData, deriveProjectsComparisonData, deriveTimeOfDayData, deriveVelocityData, deriveWeeklyActivityData, } from "./charts.js";
|
|
2
|
+
export { buildStandaloneReportHtml } from "./build.js";
|
|
3
|
+
export { readInjectedReportData, isReadyReportData, useIsRepoReport, useIsScanReport, useReportData } from "./data.js";
|
|
4
|
+
export { EmptyState, EmptyStateAction } from "./empty-state.js";
|
|
5
|
+
export { downloadCsv, downloadJson, downloadTextFile, serializeCsv, serializeReportJson } from "./export.js";
|
|
6
|
+
export { createInlineHtmlPlugin, inlineHtmlAssets } from "./inline-plugin.js";
|
|
7
|
+
export { AppShell, Header, Navigation, StatsGrid } from "./layout.js";
|
|
8
|
+
export { injectReportDataIntoHtml, REPORT_DATA_PLACEHOLDER, serializeReportDataForHtml } from "./serialization.js";
|
|
9
|
+
export { CommitsTable, ContributorsTable, DataTable, HotspotsTable } from "./tables.js";
|
|
10
|
+
export type { ActivityHeatmapCell, AdditionsVsDeletionsPoint, CodeOwnershipPoint, CommitActivityPoint, CommitSizeBucket, ContributionCalendarDay, ContributorPieSlice, LanguageDistributionSlice, ProjectComparisonPoint, TimeOfDayPoint, VelocityPoint, WeeklyActivityPoint, } from "./charts.js";
|
|
11
|
+
export type { CsvCell, CsvRow, DownloadResult } from "./export.js";
|
|
12
|
+
export type { NavigationItem, StatItem } from "./layout.js";
|
|
13
|
+
export type { CommitsTableProps, ContributorsTableProps, DataTableEmptyState, DataTableExport, DataTableProps, HotspotsTableProps } from "./tables.js";
|
|
14
|
+
export type { RouteTemplateOverrides, RouteTemplatePropsById, TemplateComponent, TemplateModule } from "./template.js";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,2BAA2B,EAC3B,oBAAoB,EACpB,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,8BAA8B,EAC9B,uBAAuB,EACvB,wBAAwB,EACxB,gCAAgC,EAChC,8BAA8B,EAC9B,wBAAwB,EACxB,8BAA8B,EAC9B,4BAA4B,EAC5B,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACvH,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC7G,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AACnH,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACxF,YAAY,EACV,mBAAmB,EACnB,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,EACzB,sBAAsB,EACtB,cAAc,EACd,aAAa,EACb,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACnE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5D,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,eAAe,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACvJ,YAAY,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { ActivityHeatmap, AdditionsVsDeletionsChart, CodeOwnershipChart, CommitActivityChart, CommitSizeDistributionChart, ContributionCalendar, ContributorPieChart, LanguageDistributionChart, ProjectsComparisonChart, TimeOfDayChart, VelocityChart, WeeklyActivityChart, deriveActivityHeatmapData, deriveAdditionsVsDeletionsData, deriveCodeOwnershipData, deriveCommitActivityData, deriveCommitSizeDistributionData, deriveContributionCalendarData, deriveContributorPieData, deriveLanguageDistributionData, deriveProjectsComparisonData, deriveTimeOfDayData, deriveVelocityData, deriveWeeklyActivityData, } from "./charts.js";
|
|
2
|
+
export { buildStandaloneReportHtml } from "./build.js";
|
|
3
|
+
export { readInjectedReportData, isReadyReportData, useIsRepoReport, useIsScanReport, useReportData } from "./data.js";
|
|
4
|
+
export { EmptyState, EmptyStateAction } from "./empty-state.js";
|
|
5
|
+
export { downloadCsv, downloadJson, downloadTextFile, serializeCsv, serializeReportJson } from "./export.js";
|
|
6
|
+
export { createInlineHtmlPlugin, inlineHtmlAssets } from "./inline-plugin.js";
|
|
7
|
+
export { AppShell, Header, Navigation, StatsGrid } from "./layout.js";
|
|
8
|
+
export { injectReportDataIntoHtml, REPORT_DATA_PLACEHOLDER, serializeReportDataForHtml } from "./serialization.js";
|
|
9
|
+
export { CommitsTable, ContributorsTable, DataTable, HotspotsTable } from "./tables.js";
|