Files
kandev/apps/web/components/github/pr-status-automation-badges.tsx
T
2026-08-12 18:36:11 +01:00

186 lines
6.4 KiB
TypeScript

"use client";
import type { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
import {
autoFixRoundForState,
findCIAutomationStateForPR,
findPRAutomationOptionsForPR,
} from "@/lib/github/ci-automation";
import type { AutoFixRoundInfo } from "@/lib/github/ci-automation";
import type { TaskCIAutomationOptions, TaskPR } from "@/lib/types/github";
export type AutomationFlags = {
autoFix: boolean;
autoMerge: boolean;
autoFixRound: AutoFixRoundInfo | null;
promptOnReviewRequested: boolean;
promptOnMerged: boolean;
promptOnClosed: boolean;
};
function taskWideAutomationFlags(
options: TaskCIAutomationOptions | null | undefined,
): Omit<AutomationFlags, "autoFixRound"> {
return {
autoFix: Boolean(options?.auto_fix_enabled),
autoMerge: Boolean(options?.auto_merge_enabled),
promptOnReviewRequested: Boolean(options?.prompt_on_review_requested),
promptOnMerged: Boolean(options?.prompt_on_merged),
promptOnClosed: Boolean(options?.prompt_on_closed),
};
}
export function automationForPR(
options: TaskCIAutomationOptions | null | undefined,
pr: TaskPR,
): AutomationFlags {
const prOptions = options?.pr_options
? findPRAutomationOptionsForPR(options.pr_options, pr)
: null;
const flags = prOptions
? {
autoFix: prOptions.auto_fix_enabled,
autoMerge: prOptions.auto_merge_enabled,
promptOnReviewRequested: prOptions.prompt_on_review_requested,
promptOnMerged: prOptions.prompt_on_merged,
promptOnClosed: prOptions.prompt_on_closed,
}
: taskWideAutomationFlags(options);
return {
...flags,
autoFixRound: (prOptions ? prOptions.auto_fix_enabled : options?.auto_fix_enabled)
? autoFixRoundForState(
findCIAutomationStateForPR(options?.pr_states, pr),
options?.auto_fix_max_rounds,
)
: null,
};
}
export function automationForPRs(
options: TaskCIAutomationOptions | null | undefined,
prs: TaskPR[],
): AutomationFlags {
if (!options?.pr_options) {
const roundInfos = options?.auto_fix_enabled
? prs.map((pr) =>
autoFixRoundForState(
findCIAutomationStateForPR(options.pr_states, pr),
options.auto_fix_max_rounds,
),
)
: [];
return {
...taskWideAutomationFlags(options),
autoFixRound: pickAttentionRound(roundInfos),
};
}
const perPR = prs.map((pr) => findPRAutomationOptionsForPR(options.pr_options, pr));
const roundInfos = perPR
.map((prOptions, index) =>
prOptions.auto_fix_enabled
? autoFixRoundForState(
findCIAutomationStateForPR(options.pr_states, prs[index]),
options.auto_fix_max_rounds,
)
: null,
)
.filter((round): round is AutoFixRoundInfo => round !== null);
return {
autoFix: perPR.some((prOptions) => prOptions.auto_fix_enabled),
autoMerge: perPR.some((prOptions) => prOptions.auto_merge_enabled),
promptOnReviewRequested: perPR.some((prOptions) => prOptions.prompt_on_review_requested),
promptOnMerged: perPR.some((prOptions) => prOptions.prompt_on_merged),
promptOnClosed: perPR.some((prOptions) => prOptions.prompt_on_closed),
autoFixRound: pickAttentionRound(roundInfos),
};
}
function pickAttentionRound(roundInfos: AutoFixRoundInfo[]): AutoFixRoundInfo | null {
if (roundInfos.length === 0) return null;
return roundInfos.reduce((best, next) => {
if (next.exhausted && !best.exhausted) return next;
if (next.exhausted === best.exhausted && next.current > best.current) return next;
return best;
});
}
function prEventsCount(automation: AutomationFlags): number {
return [
automation.promptOnReviewRequested,
automation.promptOnMerged,
automation.promptOnClosed,
].filter(Boolean).length;
}
export function automationAriaSuffix(automation: AutomationFlags, t: TFunction): string {
const flags = [
autoFixAriaLabel(automation, t),
automation.autoMerge ? t("github:autoMergeEnabledAria") : null,
automation.promptOnReviewRequested ? t("github:yourReviewIsRequested") : null,
automation.promptOnMerged ? t("github:prMerged") : null,
automation.promptOnClosed ? t("github:prClosedWithoutMerging") : null,
].filter(Boolean);
return flags.length > 0 ? `, ${flags.join(", ")}` : "";
}
function autoFixAriaLabel(automation: AutomationFlags, t: TFunction): string | null {
if (!automation.autoFix) return null;
if (!automation.autoFixRound) return t("github:autoFixEnabledAria");
if (automation.autoFixRound.exhausted) {
return t("github:autoFixExhaustedAria", {
current: automation.autoFixRound.current,
max: automation.autoFixRound.max,
});
}
return t("github:autoFixEnabledWithRoundsAria", {
current: automation.autoFixRound.current,
max: automation.autoFixRound.max,
});
}
export function AutomationFlagBadges({ automation }: { automation: AutomationFlags }) {
const { t } = useTranslation();
const enabledPrEvents = prEventsCount(automation);
if (!automation.autoFix && !automation.autoMerge && enabledPrEvents === 0) return null;
const autoFixRound = automation.autoFixRound;
return (
<>
{automation.autoFix && autoFixRound && (
<span
data-testid="pr-status-auto-fix-chip"
data-auto-fix-round={`${autoFixRound.current}/${autoFixRound.max}`}
data-auto-fix-exhausted={autoFixRound.exhausted ? "true" : "false"}
className={`rounded-sm px-1 py-0.5 text-[9px] font-medium leading-none ${
autoFixRound.exhausted
? "bg-yellow-500/15 text-yellow-500"
: "bg-emerald-500/15 text-emerald-500"
}`}
>
{t(autoFixRound.exhausted ? "github:autoFixExhausted" : "github:autoFix")}{" "}
{autoFixRound.current}/{autoFixRound.max}
</span>
)}
{automation.autoMerge && (
<span
data-testid="pr-status-auto-merge-chip"
className="rounded-sm bg-sky-500/15 px-1 py-0.5 text-[9px] font-medium leading-none text-sky-500"
>
{t("github:autoMerge")}
</span>
)}
{enabledPrEvents > 0 && (
<span
data-testid="pr-status-pr-events-chip"
data-legacy-testid="pr-status-follow-up-chip"
data-pr-events-count={enabledPrEvents}
className="rounded-sm bg-violet-500/15 px-1 py-0.5 text-[9px] font-medium leading-none text-violet-500"
>
{t("github:prEvents")} {enabledPrEvents}/3
</span>
)}
</>
);
}