Skip to content

Commit 9d5e447

Browse files
refactor: share signal metric entries between profile and comparison
1 parent 0a8fbed commit 9d5e447

5 files changed

Lines changed: 82 additions & 67 deletions

File tree

‎src/features/comparison/components/result-dashboard.tsx‎

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { InsightsList } from "./insights-list";
1313
import { ScoreCard } from "@/components/cards";
1414
import { Button } from "@/components/ui/button";
1515
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
16-
import type { UserResult } from "@/features/developer";
16+
import { getSignalMetricEntries, type UserResult } from "@/features/developer";
1717
import { useTranslation } from "@/components/providers/language-provider";
1818
import { formatComparisonToMarkdown } from "../format-comparison-to-markdown";
1919
import type { CompareInsights, CompareWinner } from "../types";
@@ -274,31 +274,7 @@ export function ResultDashboard({
274274
if (!user.signals) return null;
275275

276276
const signalEntries: Array<{ label: string; value: string | number }> = [
277-
{ label: t("signals.reposAnalyzed"), value: user.signals.reposAnalyzed ?? "-" },
278-
{
279-
label: t("signals.pullRequestsAnalyzed"),
280-
value: user.signals.pullRequestsAnalyzed ?? "-",
281-
},
282-
{ label: t("signals.mergedExternalPRs"), value: user.signals.mergedExternalPRs ?? "-" },
283-
{ label: t("signals.ownRepoPRsIgnored"), value: user.signals.ownRepoPRsIgnored ?? "-" },
284-
{ label: t("signals.unmergedPRsIgnored"), value: user.signals.unmergedPRsIgnored ?? "-" },
285-
{
286-
label: t("signals.uniqueExternalPRRepos"),
287-
value: user.signals.uniqueExternalPRRepos ?? "-",
288-
},
289-
{ label: t("signals.issuesAnalyzed"), value: user.signals.issuesAnalyzed ?? "-" },
290-
{
291-
label: t("signals.externalIssuesCounted"),
292-
value: user.signals.externalIssuesCounted ?? "-",
293-
},
294-
{
295-
label: t("signals.discussionsAnalyzed"),
296-
value: user.signals.discussionsAnalyzed ?? "-",
297-
},
298-
{
299-
label: t("signals.externalDiscussionsCounted"),
300-
value: user.signals.externalDiscussionsCounted ?? "-",
301-
},
277+
...getSignalMetricEntries(user.signals, t),
302278
{
303279
label: t("signals.reposWithLanguageData"),
304280
value: user.signals.reposWithLanguageData ?? "-",

‎src/features/developer/components/user-profile-client.tsx‎

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { useTranslation } from "@/components/providers/language-provider";
3232
import { getCountryCode, detectCountry } from "@/lib/geo";
3333
import countriesData from "@/data/countries.json";
3434
import type { UserResult } from "../types";
35+
import { getSignalMetricEntries } from "../signal-metrics";
3536

3637
type CountryInfo = {
3738
slug: string;
@@ -121,47 +122,7 @@ export function UserProfileClient({ user, location, countryParam }: Props) {
121122
const handleCopyLink = () => copy(window.location.href);
122123

123124
// Signal stats entries for transparency
124-
const signalEntries = user.signals
125-
? [
126-
{ label: t("signals.reposAnalyzed"), value: user.signals.reposAnalyzed ?? "-" },
127-
{
128-
label: t("signals.pullRequestsAnalyzed"),
129-
value: user.signals.pullRequestsAnalyzed ?? "-",
130-
},
131-
{
132-
label: t("signals.mergedExternalPRs"),
133-
value: user.signals.mergedExternalPRs ?? "-",
134-
},
135-
{
136-
label: t("signals.ownRepoPRsIgnored"),
137-
value: user.signals.ownRepoPRsIgnored ?? "-",
138-
},
139-
{
140-
label: t("signals.unmergedPRsIgnored"),
141-
value: user.signals.unmergedPRsIgnored ?? "-",
142-
},
143-
{
144-
label: t("signals.uniqueExternalPRRepos"),
145-
value: user.signals.uniqueExternalPRRepos ?? "-",
146-
},
147-
{
148-
label: t("signals.issuesAnalyzed"),
149-
value: user.signals.issuesAnalyzed ?? "-",
150-
},
151-
{
152-
label: t("signals.externalIssuesCounted"),
153-
value: user.signals.externalIssuesCounted ?? "-",
154-
},
155-
{
156-
label: t("signals.discussionsAnalyzed"),
157-
value: user.signals.discussionsAnalyzed ?? "-",
158-
},
159-
{
160-
label: t("signals.externalDiscussionsCounted"),
161-
value: user.signals.externalDiscussionsCounted ?? "-",
162-
},
163-
]
164-
: [];
125+
const signalEntries = getSignalMetricEntries(user.signals, t);
165126

166127
const totalRawSum = Math.max(
167128
1,

‎src/features/developer/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./types";
22
export * from "./components";
3+
export { getSignalMetricEntries } from "./signal-metrics";
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { I18nContextValue } from "@/lib/i18n";
2+
import type { ScoringSignals } from "@/features/scoring";
3+
4+
const SIGNAL_METRICS = [
5+
"reposAnalyzed",
6+
"pullRequestsAnalyzed",
7+
"mergedExternalPRs",
8+
"ownRepoPRsIgnored",
9+
"unmergedPRsIgnored",
10+
"uniqueExternalPRRepos",
11+
"issuesAnalyzed",
12+
"externalIssuesCounted",
13+
"discussionsAnalyzed",
14+
"externalDiscussionsCounted",
15+
] as const satisfies readonly (keyof ScoringSignals)[];
16+
17+
export function getSignalMetricEntries(
18+
signals: ScoringSignals | undefined,
19+
t: I18nContextValue["t"],
20+
): Array<{ label: string; value: number | string }> {
21+
if (!signals) return [];
22+
23+
return SIGNAL_METRICS.map((metric) => ({
24+
label: t(`signals.${metric}`),
25+
value: signals[metric] ?? "-",
26+
}));
27+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { describe, expect, it } from "vitest";
2+
import type { ScoringSignals } from "@/features/scoring";
3+
import en from "@/locales/en.json";
4+
import ar from "@/locales/ar.json";
5+
import { getSignalMetricEntries } from "../signal-metrics";
6+
7+
const signals: ScoringSignals = {
8+
reposAnalyzed: 0,
9+
pullRequestsAnalyzed: 2,
10+
mergedExternalPRs: 3,
11+
ownRepoPRsIgnored: 4,
12+
unmergedPRsIgnored: 5,
13+
uniqueExternalPRRepos: 6,
14+
issuesAnalyzed: 7,
15+
externalIssuesCounted: 8,
16+
discussionsAnalyzed: 9,
17+
externalDiscussionsCounted: 10,
18+
reposWithLanguageData: 11,
19+
};
20+
21+
describe("getSignalMetricEntries", () => {
22+
it.each([en, ar])("preserves metric order, zeroes and translated labels", (messages) => {
23+
const entries = getSignalMetricEntries(signals, (key) => messages[key as keyof typeof en]);
24+
25+
expect(entries).toEqual([
26+
{ label: messages["signals.reposAnalyzed"], value: 0 },
27+
{ label: messages["signals.pullRequestsAnalyzed"], value: 2 },
28+
{ label: messages["signals.mergedExternalPRs"], value: 3 },
29+
{ label: messages["signals.ownRepoPRsIgnored"], value: 4 },
30+
{ label: messages["signals.unmergedPRsIgnored"], value: 5 },
31+
{ label: messages["signals.uniqueExternalPRRepos"], value: 6 },
32+
{ label: messages["signals.issuesAnalyzed"], value: 7 },
33+
{ label: messages["signals.externalIssuesCounted"], value: 8 },
34+
{ label: messages["signals.discussionsAnalyzed"], value: 9 },
35+
{ label: messages["signals.externalDiscussionsCounted"], value: 10 },
36+
]);
37+
});
38+
39+
it("returns no entries when signals are absent", () => {
40+
expect(getSignalMetricEntries(undefined, (key) => key)).toEqual([]);
41+
});
42+
43+
it("retains the fallback for incomplete cached signals without mutating input", () => {
44+
const incomplete = Object.freeze({ reposAnalyzed: 0 }) as ScoringSignals;
45+
const entries = getSignalMetricEntries(incomplete, (key) => key);
46+
expect(entries[0].value).toBe(0);
47+
expect(entries.slice(1).every((entry) => entry.value === "-")).toBe(true);
48+
expect(incomplete).toEqual({ reposAnalyzed: 0 });
49+
});
50+
});

0 commit comments

Comments
 (0)