diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx index df4ef3cf02a4..b9c5b4a8465f 100644 --- a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx @@ -20,6 +20,8 @@ import { SupersetClient, t, styled } from '@superset-ui/core'; import React, { useState, useMemo, useEffect } from 'react'; import rison from 'rison'; import { useSelector } from 'react-redux'; +import { useQueryParams, BooleanParam } from 'use-query-params'; + import Loading from 'src/components/Loading'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import { useListViewResource } from 'src/views/CRUD/hooks'; @@ -91,6 +93,10 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) { state => state.user, ); + const [query, setQuery] = useQueryParams({ + databaseAdded: BooleanParam, + }); + const [databaseModalOpen, setDatabaseModalOpen] = useState(false); const [databaseCurrentlyDeleting, setDatabaseCurrentlyDeleting] = useState(null); @@ -110,6 +116,13 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) { ALLOWED_EXTENSIONS, } = useSelector(state => state.common.conf); + useEffect(() => { + if (query?.databaseAdded) { + setQuery({ databaseAdded: undefined }); + refreshData(); + } + }, [query, setQuery, refreshData]); + const openDatabaseDeleteModal = (database: DatabaseObject) => SupersetClient.get({ endpoint: `/api/v1/database/${database.id}/related_objects/`, diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx index 392df834dd7b..2b97ed791b79 100644 --- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx @@ -519,7 +519,6 @@ const DatabaseModal: FunctionComponent = ({ setImportingModal(false); setPasswords({}); setConfirmedOverwrite(false); - if (onDatabaseAdd) onDatabaseAdd(); onHide(); }; @@ -652,6 +651,7 @@ const DatabaseModal: FunctionComponent = ({ confirmedOverwrite, ); if (dbId) { + if (onDatabaseAdd) onDatabaseAdd(); onClose(); addSuccessToast(t('Database connected')); } diff --git a/superset-frontend/src/views/components/Menu.test.tsx b/superset-frontend/src/views/components/Menu.test.tsx index a80a43a22f02..0d84d2c663cc 100644 --- a/superset-frontend/src/views/components/Menu.test.tsx +++ b/superset-frontend/src/views/components/Menu.test.tsx @@ -248,13 +248,16 @@ beforeEach(() => { test('should render', () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - const { container } = render(, { useRedux: true }); + const { container } = render(, { + useRedux: true, + useQueryParams: true, + }); expect(container).toBeInTheDocument(); }); test('should render the navigation', () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); expect(screen.getByRole('navigation')).toBeInTheDocument(); }); @@ -265,7 +268,7 @@ test('should render the brand', () => { brand: { alt, icon }, }, } = mockedProps; - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); const image = screen.getByAltText(alt); expect(image).toHaveAttribute('src', icon); }); @@ -275,7 +278,7 @@ test('should render all the top navbar menu items', () => { const { data: { menu }, } = mockedProps; - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); menu.forEach(item => { expect(screen.getByText(item.label)).toBeInTheDocument(); }); @@ -286,7 +289,7 @@ test('should render the top navbar child menu items', async () => { const { data: { menu }, } = mockedProps; - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); const sources = screen.getByText('Sources'); userEvent.hover(sources); const datasets = await screen.findByText('Datasets'); @@ -300,7 +303,7 @@ test('should render the top navbar child menu items', async () => { test('should render the dropdown items', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); const dropdown = screen.getByTestId('new-dropdown-icon'); userEvent.hover(dropdown); // todo (philip): test data submenu @@ -326,14 +329,14 @@ test('should render the dropdown items', async () => { test('should render the Settings', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); const settings = await screen.findByText('Settings'); expect(settings).toBeInTheDocument(); }); test('should render the Settings menu item', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); userEvent.hover(screen.getByText('Settings')); const label = await screen.findByText('Security'); expect(label).toBeInTheDocument(); @@ -344,7 +347,7 @@ test('should render the Settings dropdown child menu items', async () => { const { data: { settings }, } = mockedProps; - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); userEvent.hover(screen.getByText('Settings')); const listUsers = await screen.findByText('List Users'); expect(listUsers).toHaveAttribute('href', settings[0].childs[0].url); @@ -352,13 +355,13 @@ test('should render the Settings dropdown child menu items', async () => { test('should render the plus menu (+) when user is not anonymous', () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); expect(screen.getByTestId('new-dropdown')).toBeInTheDocument(); }); test('should NOT render the plus menu (+) when user is anonymous', () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); expect(screen.queryByTestId('new-dropdown')).not.toBeInTheDocument(); }); @@ -370,7 +373,7 @@ test('should render the user actions when user is not anonymous', async () => { }, } = mockedProps; - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); userEvent.hover(screen.getByText('Settings')); const user = await screen.findByText('User'); expect(user).toBeInTheDocument(); @@ -384,7 +387,7 @@ test('should render the user actions when user is not anonymous', async () => { test('should NOT render the user actions when user is anonymous', () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); expect(screen.queryByText('User')).not.toBeInTheDocument(); }); @@ -396,7 +399,7 @@ test('should render the Profile link when available', async () => { }, } = mockedProps; - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); userEvent.hover(screen.getByText('Settings')); const profile = await screen.findByText('Profile'); @@ -411,7 +414,7 @@ test('should render the About section and version_string, sha or build_number wh }, } = mockedProps; - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); userEvent.hover(screen.getByText('Settings')); const about = await screen.findByText('About'); const version = await screen.findByText(`Version: ${version_string}`); @@ -430,7 +433,7 @@ test('should render the Documentation link when available', async () => { navbar_right: { documentation_url }, }, } = mockedProps; - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); userEvent.hover(screen.getByText('Settings')); const doc = await screen.findByTitle('Documentation'); expect(doc).toHaveAttribute('href', documentation_url); @@ -444,7 +447,7 @@ test('should render the Bug Report link when available', async () => { }, } = mockedProps; - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); const bugReport = await screen.findByTitle('Report a bug'); expect(bugReport).toHaveAttribute('href', bug_report_url); }); @@ -457,19 +460,19 @@ test('should render the Login link when user is anonymous', () => { }, } = mockedProps; - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); const login = screen.getByText('Login'); expect(login).toHaveAttribute('href', user_login_url); }); test('should render the Language Picker', () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); expect(screen.getByLabelText('Languages')).toBeInTheDocument(); }); test('should hide create button without proper roles', () => { useSelectorMock.mockReturnValue({ roles: [] }); - render(, { useRedux: true }); + render(, { useRedux: true, useQueryParams: true }); expect(screen.queryByTestId('new-dropdown')).not.toBeInTheDocument(); }); diff --git a/superset-frontend/src/views/components/MenuRight.tsx b/superset-frontend/src/views/components/MenuRight.tsx index 4c34b883491c..61bc6de0d692 100644 --- a/superset-frontend/src/views/components/MenuRight.tsx +++ b/superset-frontend/src/views/components/MenuRight.tsx @@ -20,6 +20,8 @@ import React, { Fragment, useState, useEffect } from 'react'; import rison from 'rison'; import { useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; +import { useQueryParams, BooleanParam } from 'use-query-params'; + import { t, styled, @@ -94,6 +96,10 @@ const RightMenu = ({ state => state.dashboardInfo?.id, ); + const [, setQuery] = useQueryParams({ + databaseAdded: BooleanParam, + }); + const { roles } = user; const { CSV_EXTENSIONS, @@ -250,6 +256,8 @@ const RightMenu = ({ return null; }; + const handleDatabaseAdd = () => setQuery({ databaseAdded: true }); + return ( {canDatabase && ( @@ -257,6 +265,7 @@ const RightMenu = ({ onHide={handleOnHideModal} show={showModal} dbEngine={engine} + onDatabaseAdd={handleDatabaseAdd} /> )}