diff --git a/superset-frontend/src/views/components/Menu.test.tsx b/superset-frontend/src/views/components/Menu.test.tsx index b990ddc1804a9..d13275fbc0b57 100644 --- a/superset-frontend/src/views/components/Menu.test.tsx +++ b/superset-frontend/src/views/components/Menu.test.tsx @@ -21,7 +21,64 @@ import * as reactRedux from 'react-redux'; import { render, screen } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; import { Menu } from './Menu'; -import { dropdownItems } from './MenuRight'; + +const dropdownItems = [ + { + label: 'Data', + icon: 'fa-database', + childs: [ + { + label: 'Connect Database', + name: 'dbconnect', + perm: true, + }, + { + label: 'Connect Google Sheet', + name: 'gsheets', + perm: true, + }, + { + label: 'Upload a CSV', + name: 'Upload a CSV', + url: '/csvtodatabaseview/form', + perm: true, + }, + { + label: 'Upload a Columnar File', + name: 'Upload a Columnar file', + url: '/columnartodatabaseview/form', + perm: true, + }, + { + label: 'Upload Excel', + name: 'Upload Excel', + url: '/exceltodatabaseview/form', + perm: true, + }, + ], + }, + { + label: 'SQL query', + url: '/superset/sqllab?new=true', + icon: 'fa-fw fa-search', + perm: 'can_sqllab', + view: 'Superset', + }, + { + label: 'Chart', + url: '/chart/add', + icon: 'fa-fw fa-bar-chart', + perm: 'can_write', + view: 'Chart', + }, + { + label: 'Dashboard', + url: '/dashboard/new', + icon: 'fa-fw fa-dashboard', + perm: 'can_write', + view: 'Dashboard', + }, +]; const user = { createdOn: '2021-04-27T18:12:38.952304', @@ -185,13 +242,13 @@ beforeEach(() => { test('should render', () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - const { container } = render(
); + const { container } = render(, { useRedux: true }); expect(container).toBeInTheDocument(); }); test('should render the navigation', () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(); + render(, { useRedux: true }); expect(screen.getByRole('navigation')).toBeInTheDocument(); }); @@ -202,7 +259,7 @@ test('should render the brand', () => { brand: { alt, icon }, }, } = mockedProps; - render(); + render(, { useRedux: true }); const image = screen.getByAltText(alt); expect(image).toHaveAttribute('src', icon); }); @@ -212,7 +269,7 @@ test('should render all the top navbar menu items', () => { const { data: { menu }, } = mockedProps; - render(); + render(, { useRedux: true }); menu.forEach(item => { expect(screen.getByText(item.label)).toBeInTheDocument(); }); @@ -223,7 +280,7 @@ test('should render the top navbar child menu items', async () => { const { data: { menu }, } = mockedProps; - render(); + render(, { useRedux: true }); const sources = screen.getByText('Sources'); userEvent.hover(sources); const datasets = await screen.findByText('Datasets'); @@ -237,7 +294,7 @@ test('should render the top navbar child menu items', async () => { test('should render the dropdown items', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(); + render(, { useRedux: true }); const dropdown = screen.getByTestId('new-dropdown-icon'); userEvent.hover(dropdown); // todo (philip): test data submenu @@ -263,14 +320,14 @@ test('should render the dropdown items', async () => { test('should render the Settings', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(); + render(, { useRedux: true }); const settings = await screen.findByText('Settings'); expect(settings).toBeInTheDocument(); }); test('should render the Settings menu item', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(); + render(, { useRedux: true }); userEvent.hover(screen.getByText('Settings')); const label = await screen.findByText('Security'); expect(label).toBeInTheDocument(); @@ -281,7 +338,7 @@ test('should render the Settings dropdown child menu items', async () => { const { data: { settings }, } = mockedProps; - render(); + render(, { useRedux: true }); userEvent.hover(screen.getByText('Settings')); const listUsers = await screen.findByText('List Users'); expect(listUsers).toHaveAttribute('href', settings[0].childs[0].url); @@ -289,13 +346,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(); + render(, { useRedux: true }); expect(screen.getByTestId('new-dropdown')).toBeInTheDocument(); }); test('should NOT render the plus menu (+) when user is anonymous', () => { useSelectorMock.mockReturnValue({ roles: user.roles }); - render(); + render(, { useRedux: true }); expect(screen.queryByTestId('new-dropdown')).not.toBeInTheDocument(); }); @@ -307,7 +364,7 @@ test('should render the user actions when user is not anonymous', async () => { }, } = mockedProps; - render(); + render(, { useRedux: true }); userEvent.hover(screen.getByText('Settings')); const user = await screen.findByText('User'); expect(user).toBeInTheDocument(); @@ -321,7 +378,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(); + render(, { useRedux: true }); expect(screen.queryByText('User')).not.toBeInTheDocument(); }); @@ -333,7 +390,7 @@ test('should render the Profile link when available', async () => { }, } = mockedProps; - render(); + render(, { useRedux: true }); userEvent.hover(screen.getByText('Settings')); const profile = await screen.findByText('Profile'); @@ -348,7 +405,7 @@ test('should render the About section and version_string, sha or build_number wh }, } = mockedProps; - render(); + render(, { useRedux: true }); userEvent.hover(screen.getByText('Settings')); const about = await screen.findByText('About'); const version = await screen.findByText(`Version: ${version_string}`); @@ -367,7 +424,7 @@ test('should render the Documentation link when available', async () => { navbar_right: { documentation_url }, }, } = mockedProps; - render(); + render(, { useRedux: true }); userEvent.hover(screen.getByText('Settings')); const doc = await screen.findByTitle('Documentation'); expect(doc).toHaveAttribute('href', documentation_url); @@ -381,7 +438,7 @@ test('should render the Bug Report link when available', async () => { }, } = mockedProps; - render(); + render(, { useRedux: true }); const bugReport = await screen.findByTitle('Report a bug'); expect(bugReport).toHaveAttribute('href', bug_report_url); }); @@ -394,19 +451,19 @@ test('should render the Login link when user is anonymous', () => { }, } = mockedProps; - render(); + render(, { useRedux: 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(); + render(, { useRedux: true }); expect(screen.getByLabelText('Languages')).toBeInTheDocument(); }); test('should hide create button without proper roles', () => { useSelectorMock.mockReturnValue({ roles: [] }); - render(); + render(, { useRedux: true }); expect(screen.queryByTestId('new-dropdown')).not.toBeInTheDocument(); }); diff --git a/superset-frontend/src/views/components/Menu.tsx b/superset-frontend/src/views/components/Menu.tsx index cc98130d4d400..5231d27f21256 100644 --- a/superset-frontend/src/views/components/Menu.tsx +++ b/superset-frontend/src/views/components/Menu.tsx @@ -79,7 +79,7 @@ interface MenuObjectChildProps { index?: number; url?: string; isFrontendRoute?: boolean; - perm?: string; + perm?: string | boolean; view?: string; } diff --git a/superset-frontend/src/views/components/MenuRight.tsx b/superset-frontend/src/views/components/MenuRight.tsx index db7e2fae63897..8222a978c008b 100644 --- a/superset-frontend/src/views/components/MenuRight.tsx +++ b/superset-frontend/src/views/components/MenuRight.tsx @@ -16,67 +16,22 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useState } from 'react'; import { MainNav as Menu } from 'src/common/components'; import { t, styled, css, SupersetTheme } from '@superset-ui/core'; import { Link } from 'react-router-dom'; import Icons from 'src/components/Icons'; import findPermission from 'src/dashboard/util/findPermission'; import { useSelector } from 'react-redux'; -import { - UserWithPermissionsAndRoles, - CommonBootstrapData, -} from 'src/types/bootstrapTypes'; +import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; import LanguagePicker from './LanguagePicker'; -import { NavBarProps, MenuObjectProps } from './Menu'; - -export const dropdownItems: MenuObjectProps[] = [ - { - label: t('Data'), - icon: 'fa-database', - childs: [ - { - icon: 'fa-upload', - label: t('Upload a CSV'), - name: 'Upload a CSV', - url: '/csvtodatabaseview/form', - }, - { - icon: 'fa-upload', - label: t('Upload a Columnar File'), - name: 'Upload a Columnar file', - url: '/columnartodatabaseview/form', - }, - { - icon: 'fa-upload', - label: t('Upload Excel'), - name: 'Upload Excel', - url: '/exceltodatabaseview/form', - }, - ], - }, - { - label: t('SQL query'), - url: '/superset/sqllab?new=true', - icon: 'fa-fw fa-search', - perm: 'can_sqllab', - view: 'Superset', - }, - { - label: t('Chart'), - url: '/chart/add', - icon: 'fa-fw fa-bar-chart', - perm: 'can_write', - view: 'Chart', - }, - { - label: t('Dashboard'), - url: '/dashboard/new', - icon: 'fa-fw fa-dashboard', - perm: 'can_write', - view: 'Dashboard', - }, -]; +import DatabaseModal from '../CRUD/data/database/DatabaseModal'; +import { + ExtentionConfigs, + GlobalMenuDataOptions, + RightMenuProps, +} from './types'; +import { MenuObjectProps } from './Menu'; const versionInfoStyles = (theme: SupersetTheme) => css` padding: ${theme.gridUnit * 1.5}px ${theme.gridUnit * 4}px @@ -107,13 +62,6 @@ const StyledAnchor = styled.a` const { SubMenu } = Menu; -interface RightMenuProps { - align: 'flex-start' | 'flex-end'; - settings: MenuObjectProps[]; - navbarRight: NavBarProps; - isFrontendRoute: (path?: string) => boolean; -} - const RightMenu = ({ align, settings, @@ -123,30 +71,106 @@ const RightMenu = ({ const { roles } = useSelector