Skip to content

Commit

Permalink
Merge #131788
Browse files Browse the repository at this point in the history
131788: cluster-ui: use job lastCompleted time when formatting last refresh time  r=xinhaoz a=xinhaoz

This commit adds the pkg `@testing-library/jest-dom` to
take advantage of the many test utils provided by jest-dom.

Epic: none

Release note: None


--------------------------


This fixes a small bug in the TableMetadataJobControl component
where we were reading the last updated time of the job to
determine whether the job completed. This lead to `Invalid Date`
being shown when the job never completed before.

Epic: [CRDB-37558](https://cockroachlabs.atlassian.net/browse/CRDB-37558)

Release note: None

Co-authored-by: Xin Hao Zhang <[email protected]>
  • Loading branch information
craig[bot] and xinhaoz committed Oct 3, 2024
2 parents 6a3db0e + 25f9845 commit 2e408b0
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 1 deletion.
36 changes: 36 additions & 0 deletions pkg/ui/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/ui/workspaces/cluster-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"@storybook/addons": "^6.3.1",
"@storybook/react": "^6.3.1",
"@testing-library/dom": "^8.11.1",
"@testing-library/jest-dom": "6.5.0",
"@testing-library/react": "^12.1.0",
"@testing-library/user-event": "^13.5.0",
"@types/chai": "^4.2.11",
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
<TimezoneContext.Provider value="UTC">
<TableMetadataJobControl onDataUpdated={mockOnDataUpdated} />
</TimezoneContext.Provider>,
);

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(
<TimezoneContext.Provider value="UTC">
<TableMetadataJobControl onDataUpdated={mockOnDataUpdated} />
</TimezoneContext.Provider>,
);

expect(screen.getByText(/Last refreshed: Never/)).toBeInTheDocument();
});

it("triggers update when refresh button is clicked", async () => {
render(
<TimezoneContext.Provider value="UTC">
<TableMetadataJobControl onDataUpdated={mockOnDataUpdated} />
</TimezoneContext.Provider>,
);

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(
<TimezoneContext.Provider value="UTC">
<TableMetadataJobControl onDataUpdated={mockOnDataUpdated} />
</TimezoneContext.Provider>,
);

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(
<TimezoneContext.Provider value="UTC">
<TableMetadataJobControl onDataUpdated={mockOnDataUpdated} />
</TimezoneContext.Provider>,
);

// 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(
<TimezoneContext.Provider value="UTC">
<TableMetadataJobControl onDataUpdated={mockOnDataUpdated} />
</TimezoneContext.Provider>,
);

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(
<TimezoneContext.Provider value="UTC">
<TableMetadataJobControl onDataUpdated={mockOnDataUpdated} />
</TimezoneContext.Provider>,
);

expect(screen.getByRole("button")).toBeDisabled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 2e408b0

Please sign in to comment.