From 2786907e3ae75e0cdc28afd2fab931fec5481c71 Mon Sep 17 00:00:00 2001 From: Reese <10563996+reesercollins@users.noreply.github.com> Date: Thu, 14 Jul 2022 10:03:33 -0400 Subject: [PATCH] Added environment tag (#152) * Added environment tag and relevant tests * Reorganize imports --- .../src/views/components/Menu.test.tsx | 13 +++++++++++++ superset-frontend/src/views/components/Menu.tsx | 13 ++++++++++++- .../src/views/components/MenuRight.tsx | 7 +++++++ superset-frontend/src/views/components/types.ts | 4 ++++ superset/config.py | 15 +++++++++++++++ superset/views/base.py | 8 ++++++++ 6 files changed, 59 insertions(+), 1 deletion(-) diff --git a/superset-frontend/src/views/components/Menu.test.tsx b/superset-frontend/src/views/components/Menu.test.tsx index d13275fbc0b57..68291a88bd411 100644 --- a/superset-frontend/src/views/components/Menu.test.tsx +++ b/superset-frontend/src/views/components/Menu.test.tsx @@ -175,6 +175,10 @@ const mockedProps = { tooltip: '', text: '', }, + environment_tag: { + text: 'Production', + color: '#000', + }, navbar_right: { show_watermark: false, bug_report_url: '/report/', @@ -264,6 +268,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 ab62add511d6c..c3cf5c48f13a4 100644 --- a/superset-frontend/src/views/components/Menu.tsx +++ b/superset-frontend/src/views/components/Menu.tsx @@ -63,6 +63,10 @@ export interface MenuProps { brand: BrandProps; navbar_right: NavBarProps; settings: MenuObjectProps[]; + environment_tag: { + text: string; + color: string; + }; }; isFrontendRoute?: (path?: string) => boolean; } @@ -189,7 +193,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'); @@ -313,6 +323,7 @@ export function Menu({ settings={settings} navbarRight={navbarRight} isFrontendRoute={isFrontendRoute} + environmentTag={environmentTag} /> diff --git a/superset-frontend/src/views/components/MenuRight.tsx b/superset-frontend/src/views/components/MenuRight.tsx index 6495b62912796..fe12ee96577b8 100644 --- a/superset-frontend/src/views/components/MenuRight.tsx +++ b/superset-frontend/src/views/components/MenuRight.tsx @@ -24,6 +24,7 @@ import Icons from 'src/components/Icons'; import findPermission from 'src/dashboard/util/findPermission'; import { useSelector } from 'react-redux'; import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; +import { Tag } from 'antd'; import LanguagePicker from './LanguagePicker'; import DatabaseModal from '../CRUD/data/database/DatabaseModal'; import { uploadUserPerms } from '../CRUD/utils'; @@ -68,6 +69,7 @@ const RightMenu = ({ settings, navbarRight, isFrontendRoute, + environmentTag, }: RightMenuProps) => { const { roles } = useSelector( state => state.user, @@ -182,6 +184,11 @@ const RightMenu = ({ show={showModal} dbEngine={engine} /> + {environmentTag.text && ( + + {environmentTag.text} + + )} {!navbarRight.user_is_anonymous && showActionDropdown && ( boolean; + environmentTag: { + text: string; + color: string; + }; } export enum GlobalMenuDataOptions { diff --git a/superset/config.py b/superset/config.py index e2702165b51c1..d785ae500287d 100644 --- a/superset/config.py +++ b/superset/config.py @@ -1371,6 +1371,21 @@ def SQL_QUERY_MUTATOR( # pylint: disable=invalid-name,unused-argument # Set to False to only allow viewing own recent activity ENABLE_BROAD_ACTIVITY_ACCESS = True +# Configuration for environment tag shown on the navbar. Setting 'text' to '' will hide the tag. +ENVIRONMENT_TAG_CONFIG = { + "variable": "FLASK_ENV", + "values": { + "development": { + "color": "#c73d2e", + "text": "Development", + }, + "production": { + "color": "#039dfc", + "text": "Production", + }, + }, +} + # ------------------------------------------------------------------- # * WARNING: STOP EDITING HERE * # ------------------------------------------------------------------- diff --git a/superset/views/base.py b/superset/views/base.py index e505af530058c..ed27b936cc71d 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, TYPE_CHECKING, Union @@ -311,6 +312,12 @@ def menu_data() -> Dict[str, Any]: if callable(brand_text): brand_text = brand_text() build_number = appbuilder.app.config["BUILD_NUMBER"] + environment_tag = ( + appbuilder.app.config["ENVIRONMENT_TAG_CONFIG"]["values"][ + os.environ.get(appbuilder.app.config["ENVIRONMENT_TAG_CONFIG"]["variable"]) + ] + or {} + ) return { "menu": menu, "brand": { @@ -321,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),