diff --git a/superset-frontend/src/views/App.tsx b/superset-frontend/src/views/App.tsx index b996fa708a929..4e193bbf776aa 100644 --- a/superset-frontend/src/views/App.tsx +++ b/superset-frontend/src/views/App.tsx @@ -38,9 +38,7 @@ import { RootContextProviders } from './RootContextProviders'; setupApp(); const user = { ...bootstrapData.user }; -const menu = { - ...bootstrapData.common.menu_data, -}; +const menu = { ...bootstrapData.common.menu_data }; let lastLocationPathname: string; initFeatureFlags(bootstrapData.common.feature_flags); diff --git a/superset-frontend/src/views/components/Menu.test.tsx b/superset-frontend/src/views/components/Menu.test.tsx index b990ddc1804a9..8dd8f56b89024 100644 --- a/superset-frontend/src/views/components/Menu.test.tsx +++ b/superset-frontend/src/views/components/Menu.test.tsx @@ -89,26 +89,6 @@ const mockedProps = { url: '/dashboard/list/', index: 4, }, - { - name: 'Data', - icon: 'fa-database', - label: 'Data', - childs: [ - { - name: 'Databases', - icon: 'fa-database', - label: 'Databases', - url: '/databaseview/list/', - }, - { - name: 'Datasets', - icon: 'fa-table', - label: 'Datasets', - url: '/tablemodelview/list/', - }, - '-', - ], - }, ], brand: { path: '/superset/profile/admin/', @@ -240,11 +220,13 @@ test('should render the dropdown items', async () => { render(); const dropdown = screen.getByTestId('new-dropdown-icon'); userEvent.hover(dropdown); - // todo (philip): test data submenu - expect(await screen.findByText(dropdownItems[1].label)).toHaveAttribute( + expect(await screen.findByText(dropdownItems[0].label)).toHaveAttribute( 'href', - dropdownItems[1].url, + dropdownItems[0].url, ); + expect( + screen.getByTestId(`menu-item-${dropdownItems[0].label}`), + ).toBeInTheDocument(); expect(await screen.findByText(dropdownItems[1].label)).toHaveAttribute( 'href', dropdownItems[1].url, diff --git a/superset-frontend/src/views/components/Menu.tsx b/superset-frontend/src/views/components/Menu.tsx index 0b562e6ba81bf..c3a475b02fc90 100644 --- a/superset-frontend/src/views/components/Menu.tsx +++ b/superset-frontend/src/views/components/Menu.tsx @@ -17,7 +17,7 @@ * under the License. */ import React, { useState, useEffect } from 'react'; -import { styled, css, useTheme, SupersetTheme } from '@superset-ui/core'; +import { styled, css } from '@superset-ui/core'; import { debounce } from 'lodash'; import { Global } from '@emotion/react'; import { getUrlParam } from 'src/utils/urlUtils'; @@ -70,12 +70,10 @@ export interface MenuProps { interface MenuObjectChildProps { label: string; name?: string; - icon?: string; - index?: number; + icon: string; + index: number; url?: string; isFrontendRoute?: boolean; - perm?: string; - view?: string; } export interface MenuObjectProps extends MenuObjectChildProps { @@ -169,21 +167,7 @@ const StyledHeader = styled.header` } } `; -const globalStyles = (theme: SupersetTheme) => css` - .ant-menu-submenu.ant-menu-submenu-popup.ant-menu.ant-menu-light.ant-menu-submenu-placement-bottomLeft { - border-radius: 0px; - } - .ant-menu-submenu.ant-menu-submenu-popup.ant-menu.ant-menu-light { - border-radius: 0px; - } - .ant-menu-vertical > .ant-menu-submenu.data-menu > .ant-menu-submenu-title { - height: 28px; - i { - padding-right: ${theme.gridUnit * 2}px; - margin-left: ${theme.gridUnit * 1.75}px; - } - } -`; + const { SubMenu } = DropdownMenu; const { useBreakpoint } = Grid; @@ -195,7 +179,6 @@ export function Menu({ const [showMenu, setMenu] = useState('horizontal'); const screens = useBreakpoint(); const uiConig = useUiConfig(); - const theme = useTheme(); useEffect(() => { function handleResize() { @@ -263,7 +246,16 @@ export function Menu({ }; return ( - + ( state => state.user, ); - // @ts-ignore - const { CSV_EXTENSIONS, COLUMNAR_EXTENSIONS, EXCEL_EXTENSIONS } = useSelector< - any, - CommonBootstrapData - >(state => state.common.conf); + // if user has any of these roles the dropdown will appear - const configMap = { - 'Upload a CSV': CSV_EXTENSIONS, - 'Upload a Columnar file': COLUMNAR_EXTENSIONS, - 'Upload Excel': EXCEL_EXTENSIONS, - }; const canSql = findPermission('can_sqllab', 'Superset', roles); const canDashboard = findPermission('can_write', 'Dashboard', roles); const canChart = findPermission('can_write', 'Chart', roles); const showActionDropdown = canSql || canChart || canDashboard; - const menuIconAndLabel = (menu: MenuObjectProps) => ( - <> - - {menu.label} - - ); return ( @@ -155,32 +113,9 @@ const RightMenu = ({ } icon={} > - {dropdownItems.map(menu => { - if (menu.childs) { - return ( - - {menu.childs.map(item => - typeof item !== 'string' && - item.name && - configMap[item.name] === true ? ( - - {item.label} - - ) : null, - )} - - ); - } - return ( - findPermission( - menu.perm as string, - menu.view as string, - roles, - ) && ( + {dropdownItems.map( + menu => + findPermission(menu.perm, menu.view, roles) && ( - ) - ); - })} + ), + )} )} None: category_icon="fa-table", ) appbuilder.add_separator("Data") + appbuilder.add_link( + "Upload a CSV", + label=__("Upload a CSV"), + href="/csvtodatabaseview/form", + icon="fa-upload", + category="Data", + category_label=__("Data"), + category_icon="fa-wrench", + cond=lambda: bool( + self.config["CSV_EXTENSIONS"].intersection( + self.config["ALLOWED_EXTENSIONS"] + ) + ), + ) + appbuilder.add_link( + "Upload a Columnar file", + label=__("Upload a Columnar File"), + href="/columnartodatabaseview/form", + icon="fa-upload", + category="Data", + category_label=__("Data"), + category_icon="fa-wrench", + cond=lambda: bool( + self.config["COLUMNAR_EXTENSIONS"].intersection( + self.config["ALLOWED_EXTENSIONS"] + ) + ), + ) + try: + import xlrd # pylint: disable=unused-import + + appbuilder.add_link( + "Upload Excel", + label=__("Upload Excel"), + href="/exceltodatabaseview/form", + icon="fa-upload", + category="Data", + category_label=__("Data"), + category_icon="fa-wrench", + cond=lambda: bool( + self.config["EXCEL_EXTENSIONS"].intersection( + self.config["ALLOWED_EXTENSIONS"] + ) + ), + ) + except ImportError: + pass appbuilder.add_api(LogRestApi) appbuilder.add_view( diff --git a/superset/views/base.py b/superset/views/base.py index 30acd51c8b6c0..4244c66131f28 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -346,16 +346,6 @@ def common_bootstrap_payload() -> Dict[str, Any]: # should not expose API TOKEN to frontend frontend_config = {k: conf.get(k) for k in FRONTEND_CONF_KEYS} - frontend_config["EXCEL_EXTENSIONS"] = bool( - bool(conf["EXCEL_EXTENSIONS"].intersection(conf["ALLOWED_EXTENSIONS"])), - ) - frontend_config["CSV_EXTENSIONS"] = bool( - bool(conf["CSV_EXTENSIONS"].intersection(conf["ALLOWED_EXTENSIONS"])), - ) - frontend_config["COLUMNAR_EXTENSIONS"] = bool( - bool(conf["COLUMNAR_EXTENSIONS"].intersection(conf["ALLOWED_EXTENSIONS"])), - ) - if conf.get("SLACK_API_TOKEN"): frontend_config["ALERT_REPORTS_NOTIFICATION_METHODS"] = [ ReportRecipientType.EMAIL, diff --git a/tests/integration_tests/security_tests.py b/tests/integration_tests/security_tests.py index 2ebced8069a3d..167931cc631f3 100644 --- a/tests/integration_tests/security_tests.py +++ b/tests/integration_tests/security_tests.py @@ -705,6 +705,7 @@ def assert_can_alpha(self, perm_set): self.assert_can_menu("Manage", perm_set) self.assert_can_menu("Annotation Layers", perm_set) self.assert_can_menu("CSS Templates", perm_set) + self.assert_can_menu("Upload a CSV", perm_set) self.assertIn(("all_datasource_access", "all_datasource_access"), perm_set) def assert_cannot_alpha(self, perm_set):