diff --git a/superset-frontend/src/views/components/Menu.test.tsx b/superset-frontend/src/views/components/Menu.test.tsx index 3fea75eb4657f..69ae51520d3dc 100644 --- a/superset-frontend/src/views/components/Menu.test.tsx +++ b/superset-frontend/src/views/components/Menu.test.tsx @@ -178,6 +178,10 @@ const mockedProps = { tooltip: '', text: '', }, + environment_tag: { + text: 'Production', + color: '#000', + }, navbar_right: { show_watermark: false, bug_report_url: '/report/', @@ -275,6 +279,15 @@ test('should render the brand', () => { expect(image).toHaveAttribute('src', icon); }); +test('should render the environment tag', () => { + useSelectorMock.mockReturnValue({ roles: user.roles }); + const { + data: { environment_tag }, + } = mockedProps; + render(, { useRedux: true }); + expect(screen.getByText(environment_tag.text)).toBeInTheDocument(); +}); + test('should render all the top navbar menu items', () => { useSelectorMock.mockReturnValue({ roles: user.roles }); const { diff --git a/superset-frontend/src/views/components/Menu.tsx b/superset-frontend/src/views/components/Menu.tsx index 739d7258c6edc..4182cf73ee4ad 100644 --- a/superset-frontend/src/views/components/Menu.tsx +++ b/superset-frontend/src/views/components/Menu.tsx @@ -62,6 +62,10 @@ export interface MenuProps { brand: BrandProps; navbar_right: NavBarProps; settings: MenuObjectProps[]; + environment_tag: { + text: string; + color: string; + }; }; isFrontendRoute?: (path?: string) => boolean; } @@ -199,7 +203,13 @@ const { SubMenu } = DropdownMenu; const { useBreakpoint } = Grid; export function Menu({ - data: { menu, brand, navbar_right: navbarRight, settings }, + data: { + menu, + brand, + navbar_right: navbarRight, + settings, + environment_tag: environmentTag, + }, isFrontendRoute = () => false, }: MenuProps) { const [showMenu, setMenu] = useState('horizontal'); @@ -323,6 +333,7 @@ export function Menu({ settings={settings} navbarRight={navbarRight} isFrontendRoute={isFrontendRoute} + environmentTag={environmentTag} /> diff --git a/superset-frontend/src/views/components/RightMenu.tsx b/superset-frontend/src/views/components/RightMenu.tsx index 516d920739c94..fabf071eb60b9 100644 --- a/superset-frontend/src/views/components/RightMenu.tsx +++ b/superset-frontend/src/views/components/RightMenu.tsx @@ -29,10 +29,12 @@ import { SupersetTheme, SupersetClient, getExtensionsRegistry, + useTheme, } from '@superset-ui/core'; import { MainNav as Menu } from 'src/components/Menu'; import { Tooltip } from 'src/components/Tooltip'; import Icons from 'src/components/Icons'; +import Label from 'src/components/Label'; import { findPermission } from 'src/utils/findPermission'; import { isUserAdmin } from 'src/dashboard/util/permissionUtils'; import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; @@ -85,6 +87,10 @@ const StyledAnchor = styled.a` padding-left: ${({ theme }) => theme.gridUnit}px; `; +const tagStyles = (theme: SupersetTheme) => css` + color: ${theme.colors.grayscale.light5}; +`; + const { SubMenu } = Menu; const RightMenu = ({ @@ -92,6 +98,7 @@ const RightMenu = ({ settings, navbarRight, isFrontendRoute, + environmentTag, setQuery, }: RightMenuProps & { setQuery: ({ databaseAdded }: { databaseAdded: boolean }) => void; @@ -262,6 +269,8 @@ const RightMenu = ({ const handleDatabaseAdd = () => setQuery({ databaseAdded: true }); + const theme = useTheme(); + return ( {canDatabase && ( @@ -272,6 +281,20 @@ const RightMenu = ({ onDatabaseAdd={handleDatabaseAdd} /> )} + {environmentTag.text && ( + + )} boolean; + environmentTag: { + text: string; + color: string; + }; } export enum GlobalMenuDataOptions { diff --git a/superset/config.py b/superset/config.py index 6167bd4dae597..bc51c6895dbb9 100644 --- a/superset/config.py +++ b/superset/config.py @@ -1307,6 +1307,21 @@ def SQL_QUERY_MUTATOR( # pylint: disable=invalid-name,unused-argument "port": internet_port, } +# Configuration for environment tag shown on the navbar. Setting 'text' to '' will hide the tag. +# 'color' can either be a hex color code, or a dot-indexed theme color (e.g. error.base) +ENVIRONMENT_TAG_CONFIG = { + "variable": "FLASK_ENV", + "values": { + "development": { + "color": "error.base", + "text": "Development", + }, + "production": { + "color": "", + "text": "", + }, + }, +} # ------------------------------------------------------------------- # * WARNING: STOP EDITING HERE * diff --git a/superset/views/base.py b/superset/views/base.py index fb56af3518a1f..b73a67c56eda5 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -17,6 +17,7 @@ import dataclasses import functools import logging +import os import traceback from datetime import datetime from typing import Any, Callable, cast, Dict, List, Optional, Union @@ -306,6 +307,18 @@ def menu_data() -> Dict[str, Any]: if callable(brand_text): brand_text = brand_text() build_number = appbuilder.app.config["BUILD_NUMBER"] + try: + environment_tag = ( + appbuilder.app.config["ENVIRONMENT_TAG_CONFIG"]["values"][ + os.environ.get( + appbuilder.app.config["ENVIRONMENT_TAG_CONFIG"]["variable"] + ) + ] + or {} + ) + except KeyError: + environment_tag = {} + return { "menu": menu, "brand": { @@ -315,6 +328,7 @@ def menu_data() -> Dict[str, Any]: "tooltip": appbuilder.app.config["LOGO_TOOLTIP"], "text": brand_text, }, + "environment_tag": environment_tag, "navbar_right": { # show the watermark if the default app icon has been overriden "show_watermark": ("superset-logo-horiz" not in appbuilder.app_icon),