feat(ui): show the attack path in the Issues panel and rename the menu entry (#13195)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro BailoandClaude Opus 5.5 authored and GitHub committed 2026-10-29 10:43:42 +01:00
1 parent 814ae54d6a
commit ca6ed48664
20 files changed
+461 -109

No files matched your search

@@ -0,0 +1,67 @@
import { render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
const { useFeatureFlagStateMock, redirect } = vi.hoisted(() => ({
useFeatureFlagStateMock: vi.fn(),
redirect: vi.fn(() => {
throw new Error("REDIRECT");
}),
}));
vi.mock("next/navigation", () => ({ redirect }));
vi.mock("@/lib/feature-flags", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/feature-flags")>()),
useFeatureFlagState: useFeatureFlagStateMock,
}));
import { FEATURE_FLAG, FEATURE_FLAG_STATE } from "@/lib/feature-flags";
import { FeatureFlagRoute } from "./feature-flag-route";
const renderRoute = () =>
render(
<FeatureFlagRoute flag={FEATURE_FLAG.ISSUES}>
<p>route content</p>
</FeatureFlagRoute>,
);
describe("FeatureFlagRoute", () => {
beforeEach(() => {
useFeatureFlagStateMock.mockReset();
redirect.mockClear();
});
it("sends the user to the overview once the flag resolves off", () => {
// Given
useFeatureFlagStateMock.mockReturnValue(FEATURE_FLAG_STATE.OFF);
vi.spyOn(console, "error").mockImplementation(() => {});
// When / Then
expect(renderRoute).toThrow("REDIRECT");
expect(redirect).toHaveBeenCalledWith("/");
});
it("renders the route once the flag resolves on", () => {
// Given
useFeatureFlagStateMock.mockReturnValue(FEATURE_FLAG_STATE.ON);
// When
renderRoute();
// Then
expect(screen.getByText("route content")).toBeInTheDocument();
expect(redirect).not.toHaveBeenCalled();
});
it("renders nothing while the flag is unresolved", () => {
// Given
useFeatureFlagStateMock.mockReturnValue(FEATURE_FLAG_STATE.PENDING);
// When
const { container } = renderRoute();
// Then
expect(container).toBeEmptyDOMElement();
expect(redirect).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,25 @@
"use client";
import { redirect } from "next/navigation";
import type { ReactNode } from "react";
import { FeatureFlagGate } from "@/components/shared/feature-flag-gate";
import type { FeatureFlag } from "@/lib/feature-flags";
const ToOverview = (): never => redirect("/");
interface FeatureFlagRouteProps {
flag: FeatureFlag;
children: ReactNode;
}
// A route behind a flag: off sends the user to the overview, pending renders
// nothing. Same client evaluation as the menu entry, so the link and the
// route never disagree.
export function FeatureFlagRoute({ flag, children }: FeatureFlagRouteProps) {
return (
<FeatureFlagGate flag={flag} fallback={<ToOverview />}>
{children}
</FeatureFlagGate>
);
}