diff --git a/pkg/ui/workspaces/cluster-ui/src/components/tableMetadataLastUpdated/tableMetadataJobControl.spec.tsx b/pkg/ui/workspaces/cluster-ui/src/components/tableMetadataLastUpdated/tableMetadataJobControl.spec.tsx new file mode 100644 index 000000000000..817a050bb75f --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/components/tableMetadataLastUpdated/tableMetadataJobControl.spec.tsx @@ -0,0 +1,178 @@ +// Copyright 2024 The Cockroach Authors. +// +// Use of this software is governed by the CockroachDB Software License +// included in the /LICENSE file. + +import "@testing-library/jest-dom"; +import { render, screen, fireEvent, act } from "@testing-library/react"; +import moment from "moment-timezone"; +import React from "react"; + +import * as api from "src/api/databases/tableMetaUpdateJobApi"; +import { TimezoneContext } from "src/contexts"; + +import { TableMetadataJobControl } from "./tableMetadataJobControl"; + +jest.mock("src/api/databases/tableMetaUpdateJobApi"); + +describe("TableMetadataJobControl", () => { + const mockOnDataUpdated = jest.fn(); + const mockRefreshJobStatus = jest.fn(); + const mockLastCompletedTime = moment("2024-01-01T12:00:00Z"); + const mockLastUpdatedTime = moment("2024-01-01T12:05:00Z"); + const mockLastStartTime = moment("2024-01-01T11:59:00Z"); + + beforeEach(() => { + jest.useFakeTimers(); + jest.spyOn(api, "useTableMetaUpdateJob").mockReturnValue({ + jobStatus: { + dataValidDuration: moment.duration(1, "hour"), + currentStatus: api.TableMetadataJobStatus.NOT_RUNNING, + progress: 0, + lastCompletedTime: mockLastCompletedTime, + lastStartTime: mockLastStartTime, + lastUpdatedTime: mockLastUpdatedTime, + automaticUpdatesEnabled: false, + }, + refreshJobStatus: mockRefreshJobStatus, + isLoading: false, + }); + jest.spyOn(api, "triggerUpdateTableMetaJobApi").mockResolvedValue({ + message: "Job triggered", + job_triggered: true, + }); + }); + + afterEach(() => { + jest.useRealTimers(); + jest.clearAllMocks(); + }); + + it("renders the last refreshed time", () => { + render( + + + , + ); + + expect(screen.getByText(/Last refreshed:/)).toBeInTheDocument(); + expect( + screen.getByText(/Jan 01, 2024 at 12:00:00 UTC/), + ).toBeInTheDocument(); + }); + + it('renders "Never" when lastCompletedTime is null', () => { + jest.spyOn(api, "useTableMetaUpdateJob").mockReturnValue({ + jobStatus: { + lastCompletedTime: null, + dataValidDuration: moment.duration(1, "hour"), + currentStatus: api.TableMetadataJobStatus.NOT_RUNNING, + progress: 0, + lastStartTime: mockLastUpdatedTime, + lastUpdatedTime: mockLastUpdatedTime, + automaticUpdatesEnabled: false, + }, + refreshJobStatus: mockRefreshJobStatus, + isLoading: false, + }); + + render( + + + , + ); + + expect(screen.getByText(/Last refreshed: Never/)).toBeInTheDocument(); + }); + + it("triggers update when refresh button is clicked", async () => { + render( + + + , + ); + + const refreshButton = screen.getByRole("button"); + await act(async () => { + fireEvent.click(refreshButton); + }); + + expect(api.triggerUpdateTableMetaJobApi).toHaveBeenCalledWith({ + onlyIfStale: false, + }); + expect(mockRefreshJobStatus).toHaveBeenCalled(); + }); + + it("schedules next update after dataValidDuration", async () => { + render( + + + , + ); + + await act(async () => { + // Advance timer 1 hour and 30s. + jest.advanceTimersByTime(3600000 + 30000); + }); + + expect(api.triggerUpdateTableMetaJobApi).toHaveBeenCalledWith({ + onlyIfStale: true, + }); + }); + + it("calls onDataUpdated when lastCompletedTime changes", () => { + const { rerender } = render( + + + , + ); + + // Update the mock to return a new lastCompletedTime + jest.spyOn(api, "useTableMetaUpdateJob").mockReturnValue({ + jobStatus: { + lastCompletedTime: moment("2024-01-01T13:00:00Z"), + lastStartTime: moment("2024-01-01T13:00:00Z"), + lastUpdatedTime: moment.utc(), + dataValidDuration: moment.duration(1, "hour"), + currentStatus: api.TableMetadataJobStatus.NOT_RUNNING, + progress: 0, + automaticUpdatesEnabled: false, + }, + refreshJobStatus: mockRefreshJobStatus, + isLoading: false, + }); + + // Rerender the component with the updated mock + rerender( + + + , + ); + + expect(mockOnDataUpdated).toHaveBeenCalled(); + }); + + it("disables refresh button when job is running", () => { + jest.spyOn(api, "useTableMetaUpdateJob").mockReturnValue({ + jobStatus: { + lastCompletedTime: moment("2024-01-01T12:00:00Z"), + dataValidDuration: moment.duration(1, "hour"), + currentStatus: api.TableMetadataJobStatus.RUNNING, + progress: 0, + lastStartTime: moment.utc(), + lastUpdatedTime: moment.utc(), + automaticUpdatesEnabled: false, + }, + refreshJobStatus: mockRefreshJobStatus, + isLoading: false, + }); + + render( + + + , + ); + + expect(screen.getByRole("button")).toBeDisabled(); + }); +}); diff --git a/pkg/ui/workspaces/cluster-ui/src/components/tableMetadataLastUpdated/tableMetadataJobControl.tsx b/pkg/ui/workspaces/cluster-ui/src/components/tableMetadataLastUpdated/tableMetadataJobControl.tsx index 8f4e3233ada1..05f43ff91bff 100644 --- a/pkg/ui/workspaces/cluster-ui/src/components/tableMetadataLastUpdated/tableMetadataJobControl.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/components/tableMetadataLastUpdated/tableMetadataJobControl.tsx @@ -34,7 +34,7 @@ export const TableMetadataJobControl: React.FC< ); const lastUpdateCompletedUnixSecs = jobStatus?.lastCompletedTime?.unix(); const timezone = useContext(TimezoneContext); - const lastUpdatedText = jobStatus?.lastUpdatedTime + const lastUpdatedText = jobStatus?.lastCompletedTime ? FormatWithTimezone( jobStatus?.lastCompletedTime, DATE_WITH_SECONDS_FORMAT_24_TZ,