Skip to content
Closed
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
61 changes: 59 additions & 2 deletions apps/loopover-ui/src/routes/app.analytics.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";

// #6817: app.analytics.tsx's StateBoundary had no loadingSkeleton, falling through to the generic spinner
// -- the same gap #6816 fixed for app.operator.tsx.
Expand Down Expand Up @@ -42,3 +42,60 @@ describe("ProductAnalytics loading skeleton (#6817)", () => {
expect(container.querySelectorAll(".animate-pulse").length).toBe(0);
});
});

// #8699: the analytics window preference was renamed from "gittensory.analytics.windowDays" to
// "loopover.analytics.windowDays" in the rebrand (75450f1d5) without the one-time legacyKey fallback
// app.runs.tsx / app.workbench.tsx got, silently resetting returning users to the 7-day default.
describe("ProductAnalytics window preference rebrand migration (#8699)", () => {
afterEach(() => {
window.localStorage.clear();
});

it("migrates a pre-rebrand gittensory window preference forward instead of resetting to 7d", async () => {
window.localStorage.setItem("gittensory.analytics.windowDays", "30");
useApiResource.mockReturnValue({
status: "ready",
data: { metrics: [{ label: "Active repos", value: "12", delta: "+2" }], noiseReduction: [] },
error: null,
loadedAt: "2026-07-17T00:00:00.000Z",
reload: () => {},
});

render(<ProductAnalytics />);

// The toggle group renders once the preference has hydrated -- with the legacy value carried
// over, 30d is the selected window (not the 7-day default the user never chose).
const thirtyDay = await screen.findByRole("radio", { name: "30 day window" });
await waitFor(() => expect(thirtyDay.getAttribute("data-state")).toBe("on"));
expect(screen.getByRole("radio", { name: "7 day window" }).getAttribute("data-state")).toBe(
"off",
);
// The dashboard is fetched for the migrated window, not the default.
await waitFor(() =>
expect(useApiResource).toHaveBeenLastCalledWith(
"/v1/app/operator-dashboard?days=30",
"Product analytics",
),
);
// Backfilled: the new key now holds the value directly (the hook migrates it forward on first read).
expect(window.localStorage.getItem("loopover.analytics.windowDays")).toBe("30");
});

it("still uses the 7-day default when neither the new nor the legacy key is present", async () => {
useApiResource.mockReturnValue({
status: "ready",
data: { metrics: [{ label: "Active repos", value: "12", delta: "+2" }], noiseReduction: [] },
error: null,
loadedAt: "2026-07-17T00:00:00.000Z",
reload: () => {},
});

render(<ProductAnalytics />);

const sevenDay = await screen.findByRole("radio", { name: "7 day window" });
await waitFor(() => expect(sevenDay.getAttribute("data-state")).toBe("on"));
// Nothing to migrate: no value is invented under either key.
expect(window.localStorage.getItem("loopover.analytics.windowDays")).toBeNull();
expect(window.localStorage.getItem("gittensory.analytics.windowDays")).toBeNull();
});
});
3 changes: 3 additions & 0 deletions apps/loopover-ui/src/routes/app.analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ export function ProductAnalytics() {
const [windowDays, setWindowDays, windowHydrated] = useLocalStorage<AnalyticsWindowDays>(
ANALYTICS_WINDOW_STORAGE_KEY,
DEFAULT_ANALYTICS_WINDOW_DAYS,
// Pre-rebrand key (#8699): read once as a fallback and migrate forward, mirroring
// app.runs.tsx / app.workbench.tsx, so a returning user's window choice survives the rename.
"gittensory.analytics.windowDays",
);
const selectedWindow = parseAnalyticsWindowDays(windowDays);
const dashboard = useApiResource<OperatorDashboard>(
Expand Down
Loading