mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-27 21:33:41 +02:00
* docs(timezone): add scheduling/viewing timezone architecture RFC * feat(db): replace daily rollups with task_usage_hourly, add user.timezone Migrations 100-104: add "user".timezone (Viewing tz), build the UTC hourly task_usage_hourly rollup with its pipeline, drop the legacy task_usage_daily / task_usage_dashboard_daily pipelines, and drop the agent_runtime.timezone column. Report queries now slice day boundaries at read time by the caller-supplied @tz instead of materialising in a fixed tz. Regenerate sqlc. * feat(server): add task_usage_hourly backfill command Replace the two legacy backfill commands (daily / dashboard_daily) with a single backfill_task_usage_hourly that loads historical task_usage into the new UTC hourly rollup, sliced per workspace. * refactor(server): resolve viewing timezone in report handlers Report handlers resolve the Viewing tz per request (?tz query param, then user.timezone, then UTC) and pass it to the hourly-rollup queries. Drop the UseDailyRollup feature flags and the old raw-scan/daily-rollup dual paths, remove the /api/usage endpoints, and stop the daemon from reporting and the runtime handler from accepting host timezone. * refactor(core): switch report queries to viewing timezone API client and dashboard/runtime queries send ?tz with each report request, the user schema/types carry the new timezone field, and the runtime timezone field/mutation is removed. * feat(views): add viewing timezone preference and UI Add the useViewingTimezone hook and a Timezone setting in Preferences; report charts and the dashboard week boundary follow the viewer tz. Remove the runtime detail timezone editor and its locale strings. * fix(test): update fixtures and stabilize tests for timezone refactor The timezone architecture refactor changed several types without updating dependent test code: - RuntimeDevice no longer has a timezone field — drop it from the create-agent-dialog runtime fixture. - User now requires a timezone field — add it to the apps/web mockUser fixture. - The PreferencesTab timezone tests asserted on the async save handler (PATCH then store update) with a bare expect, racing the mutation's settle callback, and timed out querying the Select's ~600-option IANA list on a loaded CI runner. Wrap the assertions in waitFor and extend the timeout for those three tests. * docs(timezone): document self-host migration order and trigger invariant Add a SELF-HOST UPGRADE ORDER runbook to the backfill command's package comment: applying migrations 100-104 in a single migrate-up drops the legacy daily rollups before the hourly backfill runs, leaving dashboards empty until cron catches up. Add an INVARIANT comment on trg_atq_dirty_hourly noting that agent_id must be added to the trigger's OF list if it ever becomes mutable, otherwise dirty buckets for the old agent_id are silently missed. * style(runtimes): drop trailing blank line in runtime-detail
68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { renderHook } from "@testing-library/react";
|
|
|
|
const userRef = vi.hoisted(
|
|
() => ({ current: null as { timezone?: string | null } | null }),
|
|
);
|
|
|
|
vi.mock("@multica/core/auth", () => {
|
|
type AuthState = { user: typeof userRef.current };
|
|
const useAuthStore = Object.assign(
|
|
(sel: (s: AuthState) => unknown) => sel({ user: userRef.current }),
|
|
{ getState: () => ({ user: userRef.current }) },
|
|
);
|
|
return { useAuthStore };
|
|
});
|
|
|
|
vi.mock("./timezone-select", () => ({
|
|
browserTimezone: () => "America/Chicago",
|
|
}));
|
|
|
|
import { useViewingTimezone } from "./use-viewing-timezone";
|
|
|
|
describe("useViewingTimezone", () => {
|
|
beforeEach(() => {
|
|
userRef.current = null;
|
|
});
|
|
|
|
it("returns the stored preference when the user pinned one", () => {
|
|
userRef.current = { timezone: "Asia/Tokyo" };
|
|
const { result } = renderHook(() => useViewingTimezone());
|
|
expect(result.current).toBe("Asia/Tokyo");
|
|
});
|
|
|
|
it("falls back to the browser tz when there is no user", () => {
|
|
userRef.current = null;
|
|
const { result } = renderHook(() => useViewingTimezone());
|
|
expect(result.current).toBe("America/Chicago");
|
|
});
|
|
|
|
it("falls back to the browser tz when timezone is null", () => {
|
|
userRef.current = { timezone: null };
|
|
const { result } = renderHook(() => useViewingTimezone());
|
|
expect(result.current).toBe("America/Chicago");
|
|
});
|
|
|
|
it("falls back to the browser tz when timezone is blank", () => {
|
|
userRef.current = { timezone: " " };
|
|
const { result } = renderHook(() => useViewingTimezone());
|
|
expect(result.current).toBe("America/Chicago");
|
|
});
|
|
|
|
// The preferences clear-flow PATCHes timezone: "" and the server may echo
|
|
// the empty string back before normalising it to null. The hook must
|
|
// treat "" as "no preference" and fall back to the browser tz.
|
|
it("falls back to the browser tz when timezone is an empty string", () => {
|
|
userRef.current = { timezone: "" };
|
|
const { result } = renderHook(() => useViewingTimezone());
|
|
expect(result.current).toBe("America/Chicago");
|
|
});
|
|
|
|
// Auth store still initialising: user is undefined, not null.
|
|
it("falls back to the browser tz when the user is undefined", () => {
|
|
userRef.current = undefined as never;
|
|
const { result } = renderHook(() => useViewingTimezone());
|
|
expect(result.current).toBe("America/Chicago");
|
|
});
|
|
});
|