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
4 changes: 4 additions & 0 deletions platform/lib/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ export const translations = {
"Contributors with high velocity or 80%+ AI adoption across the org",
badge: "Hyper Engineer",
repos: "{count} repos",
identified: "Identified",
unidentified: "Unidentified (no linked GitHub account)",
},
repoList: {
empty: "No repositories yet.",
Expand Down Expand Up @@ -1868,6 +1870,8 @@ export const translations = {
"Contribuidores com alta velocidade ou 80%+ de adoção de IA na organização",
badge: "Hyper Engineer",
repos: "{count} repos",
identified: "Identificados",
unidentified: "Não identificados (sem conta do GitHub vinculada)",
},
repoList: {
empty: "Nenhum repositório ainda.",
Expand Down
120 changes: 81 additions & 39 deletions platform/src/app/[tenant]/dashboard/sections/HyperEngineers.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,102 @@
'use client';
"use client";

import { GitHubAvatar } from '@/app/[tenant]/repos/[repoName]/github-avatar';
import { useTranslation } from '@/hooks/useTranslation';
import type { HyperEngineer } from '@/types/org-summary';
import { GitHubAvatar } from "@/app/[tenant]/repos/[repoName]/github-avatar";
import { useTranslation } from "@/hooks/useTranslation";
import type { HyperEngineer } from "@/types/org-summary";

interface HyperEngineersProps {
engineers: HyperEngineer[];
}

function EngineerCard({
eng,
t,
}: {
eng: HyperEngineer;
t: ReturnType<typeof useTranslation>["t"];
}) {
return (
<div className="flex items-center gap-2 rounded-lg border border-border bg-card px-3 py-2">
{eng.github ? (
<GitHubAvatar username={eng.github} name={eng.name} />
) : (
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-primary/10 text-xs font-bold text-primary">
{eng.name.charAt(0).toUpperCase()}
</div>
)}
{eng.github ? (
<a
href={`https://github.com/${eng.github}`}
target="_blank"
rel="noopener noreferrer"
className="text-sm hover:text-primary transition-colors"
>
{eng.name}
</a>
) : (
<span className="text-sm">{eng.name}</span>
)}
<span title={t("dashboard.hyperEngineers.badge")}>&#x1F3C6;</span>
{eng.repos > 1 && (
<span className="text-xs text-muted-foreground">
{t("dashboard.hyperEngineers.repos", { count: eng.repos })}
</span>
)}
</div>
);
}

export function HyperEngineers({ engineers }: HyperEngineersProps) {
const { t } = useTranslation();
if (engineers.length === 0) return null;

// Split by whether we could resolve a real GitHub identity. Two people
// (or two aliases of the same person iris/cli.py couldn't tie together —
// e.g. a personal email that isn't linked/verified on their GitHub
// account) can share a display name, so "identified" is the group we can
// actually confirm and link to a profile; "unidentified" is everyone else,
// shown separately rather than mixed in or hidden outright.
const identified = engineers.filter((eng) => eng.github);
const unidentified = engineers.filter((eng) => !eng.github);

return (
<section className="space-y-4">
<div>
<h2 className="text-lg font-medium">{t('dashboard.hyperEngineers.title')}</h2>
<h2 className="text-lg font-medium">
{t("dashboard.hyperEngineers.title")}
</h2>
<p className="text-sm text-muted-foreground">
{t('dashboard.hyperEngineers.subtitle')}
{t("dashboard.hyperEngineers.subtitle")}
</p>
</div>

<div className="flex flex-wrap gap-2">
{engineers.map((eng) => (
<div
key={eng.name}
className="flex items-center gap-2 rounded-lg border border-border bg-card px-3 py-2"
>
{eng.github ? (
<GitHubAvatar username={eng.github} name={eng.name} />
) : (
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-primary/10 text-xs font-bold text-primary">
{eng.name.charAt(0).toUpperCase()}
</div>
)}
{eng.github ? (
<a
href={`https://github.com/${eng.github}`}
target="_blank"
rel="noopener noreferrer"
className="text-sm hover:text-primary transition-colors"
>
{eng.name}
</a>
) : (
<span className="text-sm">{eng.name}</span>
)}
<span title={t('dashboard.hyperEngineers.badge')}>&#x1F3C6;</span>
{eng.repos > 1 && (
<span className="text-xs text-muted-foreground">
{t('dashboard.hyperEngineers.repos', { count: eng.repos })}
</span>
)}
{identified.length > 0 && (
<div className="space-y-2">
{unidentified.length > 0 && (
<h3 className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
{t("dashboard.hyperEngineers.identified")}
</h3>
)}
<div className="flex flex-wrap gap-2">
{identified.map((eng) => (
<EngineerCard key={eng.name} eng={eng} t={t} />
))}
</div>
))}
</div>
</div>
)}

{unidentified.length > 0 && (
<div className="space-y-2">
<h3 className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
{t("dashboard.hyperEngineers.unidentified")}
</h3>
<div className="flex flex-wrap gap-2">
{unidentified.map((eng) => (
<EngineerCard key={eng.name} eng={eng} t={t} />
))}
</div>
</div>
)}
</section>
);
}
48 changes: 43 additions & 5 deletions platform/tests/org-summary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ describe("computeHyperEngineers — dedupes the same person across name variants
expect(result[0].repos).toBe(2);
});

it("merges two display-name variants sharing a GitHub noreply email", () => {
it("merges two display-name variants sharing a GitHub noreply email, surfacing github from userMap once merged", () => {
const payloads = new Map<string, ReportMetrics>([
[
"repo-a",
Expand Down Expand Up @@ -420,13 +420,22 @@ describe("computeHyperEngineers — dedupes the same person across name variants
}),
],
]);
// Nothing in nameToGithub — the shared noreply email is what ties the
// two name variants into one group; userMap (keyed by the normalized
// email, which for a noreply address IS the github username) is what
// then supplies the github field for display.
const userMap = new Map([
[
"renatoguimaraescb",
{ name: "Renato Guimarães", github: "renatoguimaraescb" },
],
]);

// No github resolved anywhere — only the shared noreply email ties
// the two name variants together.
const result = computeHyperEngineers(payloads, new Map(), new Map());
const result = computeHyperEngineers(payloads, userMap, new Map());

expect(result).toHaveLength(1);
expect(result[0].repos).toBe(2);
expect(result[0].github).toBe("renatoguimaraescb");
});

it("keeps genuinely different people separate", () => {
Expand All @@ -448,9 +457,38 @@ describe("computeHyperEngineers — dedupes the same person across name variants
}),
],
]);
const nameToGithub = new Map([
["alice", "alice-gh"],
["bob", "bob-gh"],
]);

const result = computeHyperEngineers(payloads, new Map(), new Map());
const result = computeHyperEngineers(payloads, new Map(), nameToGithub);

expect(result).toHaveLength(2);
});

it("still includes an engineer with no resolved GitHub username, with github left undefined", () => {
// computeHyperEngineers doesn't filter these out — the "identified" vs
// "unidentified" split is a display concern, done in HyperEngineers.tsx.
const payloads = new Map<string, ReportMetrics>([
[
"repo-a",
payload({
author_velocity: {
authors: [
hyperAuthor({
name: "Mystery Person",
email: "mystery@corp.com",
}),
],
},
}),
],
]);

const result = computeHyperEngineers(payloads, new Map(), new Map());

expect(result).toHaveLength(1);
expect(result[0].github).toBeUndefined();
});
});
Loading