diff --git a/frontend/src/metabase-types/api/database.ts b/frontend/src/metabase-types/api/database.ts index 654615c0247173b16aa2fa7168b797e24f8605d7..7624c48fb13b4f750ef3819388e7406e468ca257 100644 --- a/frontend/src/metabase-types/api/database.ts +++ b/frontend/src/metabase-types/api/database.ts @@ -6,5 +6,6 @@ export interface Database { engine: string; is_sample: boolean; creator_id?: number; + created_at: string; initial_sync_status: InitialSyncStatus; } diff --git a/frontend/src/metabase-types/api/mocks/database.ts b/frontend/src/metabase-types/api/mocks/database.ts index 5c9b691e62f81a478de93f6cd8361b94980fb418..dd486b8b4b0e83072117d67642e48b7afb437d66 100644 --- a/frontend/src/metabase-types/api/mocks/database.ts +++ b/frontend/src/metabase-types/api/mocks/database.ts @@ -6,6 +6,7 @@ export const createMockDatabase = (opts?: Partial<Database>): Database => ({ engine: "H2", is_sample: false, creator_id: undefined, + created_at: "2015-01-01T20:10:30.200", initial_sync_status: "complete", ...opts, }); diff --git a/frontend/src/metabase/home/homepage/components/SyncingSection/SyncingSection.tsx b/frontend/src/metabase/home/homepage/components/SyncingSection/SyncingSection.tsx index b17903fadd7d1571cff3e431960facfb81c7301a..4a3ad91f58760231ca161ab7f65385deac4e408c 100644 --- a/frontend/src/metabase/home/homepage/components/SyncingSection/SyncingSection.tsx +++ b/frontend/src/metabase/home/homepage/components/SyncingSection/SyncingSection.tsx @@ -1,9 +1,18 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { + useCallback, + useEffect, + useLayoutEffect, + useState, +} from "react"; +import moment, { Moment } from "moment"; import { isSyncInProgress } from "metabase/lib/syncing"; import Modal from "metabase/components/Modal"; import SyncingModal from "metabase/containers/SyncingModal"; import { Database, User } from "metabase-types/api"; +const SYNC_TIMEOUT = 30000; +const CLOCK_TIMEOUT = 5000; + export interface SyncingSectionProps { user: User; databases: Database[]; @@ -17,18 +26,19 @@ const SyncingSection = ({ showSyncingModal, onHideSyncingModal, }: SyncingSectionProps): JSX.Element => { - const isSyncing = isUserSyncingDatabase(user, databases); - const [isOpened, setIsOpened] = useState(isSyncing && showSyncingModal); + const [isOpened, setIsOpened] = useState(false); + const isOpening = useSyncingModal(databases, user, showSyncingModal); const handleClose = useCallback(() => { setIsOpened(false); }, []); - useEffect(() => { - if (isOpened) { + useLayoutEffect(() => { + if (isOpening) { + setIsOpened(isOpening); onHideSyncingModal?.(); } - }, [isOpened, onHideSyncingModal]); + }, [isOpening, onHideSyncingModal]); return ( <Modal isOpen={isOpened} small full={false} onClose={handleClose}> @@ -37,10 +47,48 @@ const SyncingSection = ({ ); }; -const isUserSyncingDatabase = (user: User, databases: Database[]): boolean => { - return databases.some( +const useClock = (isEnabled: boolean): Moment => { + const [now, setNow] = useState(() => moment()); + + useEffect(() => { + if (isEnabled) { + const timeout = setTimeout(() => setNow(moment()), CLOCK_TIMEOUT); + return () => clearTimeout(timeout); + } + }, [now, isEnabled]); + + return now; +}; + +const useSyncingModal = ( + databases: Database[], + user: User, + showSyncingModal = false, +): boolean => { + const database = getSyncingDatabase(databases, user); + const isSyncing = database != null; + const now = useClock(isSyncing); + const isElapsed = database ? isSyncingForLongTime(database, now) : false; + + return isSyncing && isElapsed && showSyncingModal; +}; + +const getSyncingDatabase = ( + databases: Database[], + user: User, +): Database | undefined => { + return databases.find( d => !d.is_sample && d.creator_id === user.id && isSyncInProgress(d), ); }; +const isSyncingForLongTime = (database: Database, now: Moment): boolean => { + if (isSyncInProgress(database)) { + const createdAt = moment.utc(database.created_at); + return now.diff(createdAt, "ms") > SYNC_TIMEOUT; + } else { + return false; + } +}; + export default SyncingSection; diff --git a/frontend/src/metabase/home/homepage/components/SyncingSection/SyncingSection.unit.spec.tsx b/frontend/src/metabase/home/homepage/components/SyncingSection/SyncingSection.unit.spec.tsx index 799a4d0b7032eb647b37297b9be038253ffb2c1c..78e1cae7b8d270fb1cf8809c00cdec919c61d5af 100644 --- a/frontend/src/metabase/home/homepage/components/SyncingSection/SyncingSection.unit.spec.tsx +++ b/frontend/src/metabase/home/homepage/components/SyncingSection/SyncingSection.unit.spec.tsx @@ -7,7 +7,15 @@ const SyncingModal = () => <div>Explore sample data</div>; jest.mock("metabase/containers/SyncingModal", () => SyncingModal); describe("SyncingSection", () => { - it("should display a modal for a syncing database", () => { + beforeEach(() => { + jest.useFakeTimers("modern"); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("should display a modal for a syncing database after timeout", () => { const user = createMockUser({ id: 1 }); const databases = [ createMockDatabase({ is_sample: true }), diff --git a/frontend/test/metabase/scenarios/onboarding/home/homepage.cy.spec.js b/frontend/test/metabase/scenarios/onboarding/home/homepage.cy.spec.js index 852c942af1e311dde457719cf0b10a27444e82b7..1578e839a5fa2ad36d081b41f0d60b2393e0de00 100644 --- a/frontend/test/metabase/scenarios/onboarding/home/homepage.cy.spec.js +++ b/frontend/test/metabase/scenarios/onboarding/home/homepage.cy.spec.js @@ -64,7 +64,7 @@ describe("scenarios > home > homepage", () => { cy.findByText("Try these x-rays based on your data").should("not.exist"); }); - it("should show a modal when there is a newly created database", () => { + it("should show a modal when there is a newly created syncing database", () => { mockSyncingDatabase(); cy.visit("/"); @@ -142,6 +142,7 @@ const mockSyncingDatabase = () => { id: sampleDatabase.id + 1, name: "H2", creator_id: user.id, + created_at: "2015-01-01T20:10:30.200", is_sample: false, initial_sync_status: "incomplete", }; diff --git a/package.json b/package.json index 2e91800d3c11ae5dd8bae1de65af72b48e01e184..aa3e69956c5211b8ba4d3ca4778a80e63f017f2f 100644 --- a/package.json +++ b/package.json @@ -120,8 +120,8 @@ "@storybook/addon-actions": "^6.3.12", "@storybook/addon-essentials": "^6.3.12", "@storybook/addon-links": "^6.3.12", - "@storybook/client-api": "6.3.12", "@storybook/builder-webpack5": "^6.3.12", + "@storybook/client-api": "6.3.12", "@storybook/manager-webpack5": "^6.3.12", "@storybook/react": "^6.3.12", "@testing-library/cypress": "^5.0.2",