From 08f45ef207fb159bf0de49dd0a90f423c77965a7 Mon Sep 17 00:00:00 2001 From: Ville Brofeldt <33317356+villebro@users.noreply.github.com> Date: Tue, 10 Jan 2023 19:08:30 +0200 Subject: [PATCH] fix(viz-gallery): respect denylist in viz gallery (#22658) --- .../spec/helpers/reducerIndex.ts | 8 ++--- superset-frontend/spec/helpers/setup.ts | 2 +- superset-frontend/src/SqlLab/App.jsx | 4 +-- .../components/ScheduleQueryButton/index.tsx | 6 ++-- .../src/SqlLab/components/SqlEditor/index.jsx | 6 ++-- .../components/TabbedSqlEditors/index.jsx | 8 ++--- .../src/addSlice/AddSliceContainer.test.tsx | 2 +- .../src/addSlice/AddSliceContainer.tsx | 5 +++ .../src/dashboard/components/Dashboard.jsx | 4 +-- superset-frontend/src/embedded/api.tsx | 4 ++- superset-frontend/src/embedded/index.tsx | 3 +- .../VizTypeControl/VizTypeControl.test.tsx | 2 +- .../VizTypeControl/VizTypeGallery.tsx | 2 ++ .../controls/VizTypeControl/index.tsx | 4 +++ .../src/middleware/asyncEvent.ts | 17 ++-------- superset-frontend/src/preamble.ts | 31 ++++++------------- superset-frontend/src/profile/App.tsx | 8 ++--- .../src/profile/components/App.tsx | 4 +-- .../src/profile/components/CreatedContent.tsx | 6 ++-- .../src/profile/components/Favorites.tsx | 7 ++--- .../src/profile/components/RecentActivity.tsx | 6 ++-- .../src/profile/components/Security.tsx | 13 ++++---- .../src/profile/components/UserInfo.tsx | 19 ++++++------ .../src/showSavedQuery/index.jsx | 5 ++- .../src/utils/getBootstrapData.ts | 3 +- .../src/utils/hostNamesConfig.js | 5 +-- superset-frontend/src/views/App.tsx | 15 ++++----- .../src/views/CRUD/chart/ChartList.tsx | 6 ++-- .../views/CRUD/dashboard/DashboardList.tsx | 4 ++- .../src/views/RootContextProviders.tsx | 6 ++-- superset-frontend/src/views/menu.tsx | 7 ++--- superset-frontend/src/views/store.ts | 11 ++++--- superset/views/base.py | 1 + 33 files changed, 111 insertions(+), 123 deletions(-) diff --git a/superset-frontend/spec/helpers/reducerIndex.ts b/superset-frontend/spec/helpers/reducerIndex.ts index edfaf7bb5c..459a112e22 100644 --- a/superset-frontend/spec/helpers/reducerIndex.ts +++ b/superset-frontend/spec/helpers/reducerIndex.ts @@ -31,13 +31,13 @@ import explore from 'src/explore/reducers/exploreReducer'; import sqlLab from 'src/SqlLab/reducers/sqlLab'; import localStorageUsageInKilobytes from 'src/SqlLab/reducers/localStorageUsage'; import reports from 'src/reports/reducers/reports'; +import getBootstrapData from 'src/utils/getBootstrapData'; const impressionId = (state = '') => state; -const container = document.getElementById('app'); -const bootstrap = JSON.parse(container?.getAttribute('data-bootstrap') ?? '{}'); -const common = { ...bootstrap.common }; -const user = { ...bootstrap.user }; +const bootstrapData = getBootstrapData(); +const common = { ...bootstrapData.common }; +const user = { ...bootstrapData.user }; const noopReducer = (initialState: unknown) => diff --git a/superset-frontend/spec/helpers/setup.ts b/superset-frontend/spec/helpers/setup.ts index bd2961e23c..281ab4ae4b 100644 --- a/superset-frontend/spec/helpers/setup.ts +++ b/superset-frontend/spec/helpers/setup.ts @@ -25,5 +25,5 @@ configureTestingLibrary({ testIdAttribute: 'data-test', }); -document.body.innerHTML = '
'; +document.body.innerHTML = '
'; expect.extend(matchers); diff --git a/superset-frontend/src/SqlLab/App.jsx b/superset-frontend/src/SqlLab/App.jsx index 812202eec2..4a30095803 100644 --- a/superset-frontend/src/SqlLab/App.jsx +++ b/superset-frontend/src/SqlLab/App.jsx @@ -30,6 +30,7 @@ import { FeatureFlag, } from 'src/featureFlags'; import setupExtensions from 'src/setup/setupExtensions'; +import getBootstrapData from 'src/utils/getBootstrapData'; import getInitialState from './reducers/getInitialState'; import rootReducer from './reducers/index'; import { initEnhancer } from '../reduxUtils'; @@ -48,8 +49,7 @@ import { theme } from '../preamble'; setupApp(); setupExtensions(); -const appContainer = document.getElementById('app'); -const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap')); +const bootstrapData = getBootstrapData(); initFeatureFlags(bootstrapData.common.feature_flags); diff --git a/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx b/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx index 780bc4d96d..c78e2bd32d 100644 --- a/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx @@ -25,11 +25,9 @@ import * as chrono from 'chrono-node'; import ModalTrigger, { ModalTriggerRef } from 'src/components/ModalTrigger'; import { Form, FormItem } from 'src/components/Form'; import Button from 'src/components/Button'; +import getBootstrapData from 'src/utils/getBootstrapData'; -const appContainer = document.getElementById('app'); -const bootstrapData = JSON.parse( - appContainer?.getAttribute('data-bootstrap') || '{}', -); +const bootstrapData = getBootstrapData(); const scheduledQueriesConf = bootstrapData?.common?.conf?.SCHEDULED_QUERIES; const validators = { diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx index 8e48cbefbe..e27f6c1e0b 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx @@ -74,6 +74,7 @@ import { } from 'src/utils/localStorageHelpers'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { EmptyStateBig } from 'src/components/EmptyState'; +import getBootstrapData from 'src/utils/getBootstrapData'; import { isEmpty } from 'lodash'; import TemplateParamsEditor from '../TemplateParamsEditor'; import SouthPane from '../SouthPane'; @@ -86,10 +87,7 @@ import AceEditorWrapper from '../AceEditorWrapper'; import RunQueryActionButton from '../RunQueryActionButton'; import QueryLimitSelect from '../QueryLimitSelect'; -const appContainer = document.getElementById('app'); -const bootstrapData = JSON.parse( - appContainer.getAttribute('data-bootstrap') || '{}', -); +const bootstrapData = getBootstrapData(); const validatorMap = bootstrapData?.common?.conf?.SQL_VALIDATORS_BY_ENGINE || {}; const scheduledQueriesConf = bootstrapData?.common?.conf?.SCHEDULED_QUERIES; diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx index 63c7cc862c..ca53fed709 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx @@ -28,6 +28,7 @@ import { Tooltip } from 'src/components/Tooltip'; import { detectOS } from 'src/utils/common'; import * as Actions from 'src/SqlLab/actions/sqlLab'; import { EmptyStateBig } from 'src/components/EmptyState'; +import getBootstrapData from 'src/utils/getBootstrapData'; import SqlEditor from '../SqlEditor'; import SqlEditorTabHeader from '../SqlEditorTabHeader'; @@ -106,13 +107,10 @@ class TabbedSqlEditors extends React.PureComponent { } // merge post form data with GET search params - // Hack: this data should be comming from getInitialState + // Hack: this data should be coming from getInitialState // but for some reason this data isn't being passed properly through // the reducer. - const appContainer = document.getElementById('app'); - const bootstrapData = JSON.parse( - appContainer?.getAttribute('data-bootstrap') || '{}', - ); + const bootstrapData = getBootstrapData(); const query = { ...bootstrapData.requested_query, ...URI(window.location).search(true), diff --git a/superset-frontend/src/addSlice/AddSliceContainer.test.tsx b/superset-frontend/src/addSlice/AddSliceContainer.test.tsx index e5f62fd258..1e26c84995 100644 --- a/superset-frontend/src/addSlice/AddSliceContainer.test.tsx +++ b/superset-frontend/src/addSlice/AddSliceContainer.test.tsx @@ -84,7 +84,7 @@ async function getWrapper(user = mockUser) { return wrapper; } -test('renders a select and a VizTypeControl', async () => { +test('renders a select and a VizTypeGallery', async () => { const wrapper = await getWrapper(); expect(wrapper.find(AsyncSelect)).toExist(); expect(wrapper.find(VizTypeGallery)).toExist(); diff --git a/superset-frontend/src/addSlice/AddSliceContainer.tsx b/superset-frontend/src/addSlice/AddSliceContainer.tsx index f63c53f337..c50bcad9a2 100644 --- a/superset-frontend/src/addSlice/AddSliceContainer.tsx +++ b/superset-frontend/src/addSlice/AddSliceContainer.tsx @@ -39,6 +39,7 @@ import VizTypeGallery, { } from 'src/explore/components/controls/VizTypeControl/VizTypeGallery'; import { findPermission } from 'src/utils/findPermission'; import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; +import getBootstrapData from 'src/utils/getBootstrapData'; type Dataset = { id: number; @@ -62,6 +63,9 @@ export type AddSliceContainerState = { const ESTIMATED_NAV_HEIGHT = 56; const ELEMENTS_EXCEPT_VIZ_GALLERY = ESTIMATED_NAV_HEIGHT + 250; +const bootstrapData = getBootstrapData(); +const denyList: string[] = bootstrapData.common.conf.VIZ_TYPE_DENYLIST || []; + const StyledContainer = styled.div` ${({ theme }) => ` flex: 1 1 auto; @@ -395,6 +399,7 @@ export class AddSliceContainer extends React.PureComponent< description={ 0, diff --git a/superset-frontend/src/embedded/api.tsx b/superset-frontend/src/embedded/api.tsx index 675e97ac54..9d37daf2e0 100644 --- a/superset-frontend/src/embedded/api.tsx +++ b/superset-frontend/src/embedded/api.tsx @@ -16,10 +16,12 @@ * specific language governing permissions and limitations * under the License. */ +import getBootstrapData from 'src/utils/getBootstrapData'; import { store } from '../views/store'; -import { bootstrapData } from '../preamble'; import { getDashboardPermalink as getDashboardPermalinkUtil } from '../utils/urlUtils'; +const bootstrapData = getBootstrapData(); + type Size = { width: number; height: number; diff --git a/superset-frontend/src/embedded/index.tsx b/superset-frontend/src/embedded/index.tsx index 832c76a767..50c026fba8 100644 --- a/superset-frontend/src/embedded/index.tsx +++ b/superset-frontend/src/embedded/index.tsx @@ -21,7 +21,7 @@ import ReactDOM from 'react-dom'; import { BrowserRouter as Router, Route } from 'react-router-dom'; import { makeApi, t, logging } from '@superset-ui/core'; import Switchboard from '@superset-ui/switchboard'; -import { bootstrapData } from 'src/preamble'; +import getBootstrapData from 'src/utils/getBootstrapData'; import setupClient from 'src/setup/setupClient'; import { RootContextProviders } from 'src/views/RootContextProviders'; import { store, USER_LOADED } from 'src/views/store'; @@ -33,6 +33,7 @@ import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; import { embeddedApi } from './api'; const debugMode = process.env.WEBPACK_MODE === 'development'; +const bootstrapData = getBootstrapData(); function log(...info: unknown[]) { if (debugMode) { diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx index 2ee9bfab0e..034b339cda 100644 --- a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx +++ b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx @@ -40,7 +40,7 @@ import { } from '@superset-ui/plugin-chart-echarts'; import TableChartPlugin from '@superset-ui/plugin-chart-table'; import { LineChartPlugin } from '@superset-ui/preset-chart-xy'; -import TimeTableChartPlugin from '../../../../visualizations/TimeTable'; +import TimeTableChartPlugin from 'src/visualizations/TimeTable'; import VizTypeControl, { VIZ_TYPE_CONTROL_TEST_ID } from './index'; jest.useFakeTimers(); diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx index c11ae9f948..6b14660075 100644 --- a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx +++ b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx @@ -50,6 +50,7 @@ interface VizTypeGalleryProps { onDoubleClick: () => void; selectedViz: string | null; className?: string; + denyList: string[]; } type VizEntry = { @@ -506,6 +507,7 @@ export default function VizTypeGallery(props: VizTypeGalleryProps) { const chartMetadata: VizEntry[] = useMemo(() => { const result = Object.entries(mountedPluginMetadata) .map(([key, value]) => ({ key, value })) + .filter(({ key }) => !props.denyList.includes(key)) .filter( ({ value }) => nativeFilterGate(value.behaviors || []) && !value.deprecated, diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx index 2059e1138b..802c04f8a9 100644 --- a/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx +++ b/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx @@ -27,6 +27,7 @@ import { import { usePluginContext } from 'src/components/DynamicPlugins'; import Modal from 'src/components/Modal'; import { noOp } from 'src/utils/common'; +import getBootstrapData from 'src/utils/getBootstrapData'; import VizTypeGallery, { MAX_ADVISABLE_VIZ_GALLERY_WIDTH, } from './VizTypeGallery'; @@ -41,6 +42,8 @@ interface VizTypeControlProps { isModalOpenInit?: boolean; } +const bootstrapData = getBootstrapData(); +const denyList: string[] = bootstrapData.common.conf.VIZ_TYPE_DENYLIST || []; const metadataRegistry = getChartMetadataRegistry(); export const VIZ_TYPE_CONTROL_TEST_ID = 'viz-type-control'; @@ -141,6 +144,7 @@ const VizTypeControl = ({ selectedViz={selectedViz} onChange={setSelectedViz} onDoubleClick={onSubmit} + denyList={denyList} /> diff --git a/superset-frontend/src/middleware/asyncEvent.ts b/superset-frontend/src/middleware/asyncEvent.ts index f76b0a713f..f1eee02ab4 100644 --- a/superset-frontend/src/middleware/asyncEvent.ts +++ b/superset-frontend/src/middleware/asyncEvent.ts @@ -23,6 +23,7 @@ import { logging, } from '@superset-ui/core'; import { SupersetError } from 'src/components/ErrorMessage/types'; +import getBootstrapData from 'src/utils/getBootstrapData'; import { FeatureFlag, isFeatureEnabled } from '../featureFlags'; import { getClientErrorObject, @@ -235,21 +236,7 @@ export const init = (appConfig?: AppConfig) => { retriesByJobId = {}; lastReceivedEventId = null; - if (appConfig) { - config = appConfig; - } else { - // load bootstrap data from DOM - const appContainer = document.getElementById('app'); - if (appContainer) { - const bootstrapData = JSON.parse( - appContainer?.getAttribute('data-bootstrap') || '{}', - ); - config = bootstrapData?.common?.conf; - } else { - config = {}; - logging.warn('asyncEvent: app config data not found'); - } - } + config = appConfig || getBootstrapData().config; transport = config.GLOBAL_ASYNC_QUERIES_TRANSPORT || TRANSPORT_POLLING; pollingDelayMs = config.GLOBAL_ASYNC_QUERIES_POLLING_DELAY || 500; diff --git a/superset-frontend/src/preamble.ts b/superset-frontend/src/preamble.ts index c5026cdb8b..1d5c2ae10c 100644 --- a/superset-frontend/src/preamble.ts +++ b/superset-frontend/src/preamble.ts @@ -26,47 +26,34 @@ import setupClient from './setup/setupClient'; import setupColors from './setup/setupColors'; import setupFormatters from './setup/setupFormatters'; import setupDashboardComponents from './setup/setupDasboardComponents'; -import { BootstrapData, User } from './types/bootstrapTypes'; +import { User } from './types/bootstrapTypes'; import { initFeatureFlags } from './featureFlags'; -import { DEFAULT_COMMON_BOOTSTRAP_DATA } from './constants'; +import getBootstrapData from './utils/getBootstrapData'; if (process.env.WEBPACK_MODE === 'development') { setHotLoaderConfig({ logLevel: 'debug', trackTailUpdates: false }); } // eslint-disable-next-line import/no-mutable-exports -export let bootstrapData: BootstrapData = { - common: { - ...DEFAULT_COMMON_BOOTSTRAP_DATA, - }, -}; +const bootstrapData = getBootstrapData(); // Configure translation if (typeof window !== 'undefined') { - const root = document.getElementById('app'); - bootstrapData = root - ? JSON.parse(root.getAttribute('data-bootstrap') || '{}') - : {}; - if (bootstrapData?.common?.language_pack) { - const languagePack = bootstrapData.common.language_pack; - configure({ languagePack }); - moment.locale(bootstrapData.common.locale); - } else { - configure(); - } + configure({ languagePack: bootstrapData.common.language_pack }); + moment.locale(bootstrapData.common.locale); } else { configure(); } // Configure feature flags -initFeatureFlags(bootstrapData?.common?.feature_flags); +initFeatureFlags(bootstrapData.common.feature_flags); // Setup SupersetClient setupClient(); setupColors( - bootstrapData?.common?.extra_categorical_color_schemes, - bootstrapData?.common?.extra_sequential_color_schemes, + bootstrapData.common.extra_categorical_color_schemes, + bootstrapData.common.extra_sequential_color_schemes, ); // Setup number formatters @@ -76,7 +63,7 @@ setupDashboardComponents(); export const theme = merge( supersetTheme, - bootstrapData?.common?.theme_overrides ?? {}, + bootstrapData.common.theme_overrides ?? {}, ); const getMe = makeApi({ diff --git a/superset-frontend/src/profile/App.tsx b/superset-frontend/src/profile/App.tsx index 3704dcb4b5..ed331ce737 100644 --- a/superset-frontend/src/profile/App.tsx +++ b/superset-frontend/src/profile/App.tsx @@ -30,14 +30,12 @@ import setupApp from 'src/setup/setupApp'; import setupExtensions from 'src/setup/setupExtensions'; import { theme } from 'src/preamble'; import ToastContainer from 'src/components/MessageToasts/ToastContainer'; +import getBootstrapData from 'src/utils/getBootstrapData'; setupApp(); setupExtensions(); -const profileViewContainer = document.getElementById('app'); -const bootstrap = JSON.parse( - profileViewContainer?.getAttribute('data-bootstrap') ?? '{}', -); +const bootstrapData = getBootstrapData(); const store = createStore( combineReducers({ @@ -51,7 +49,7 @@ const Application = () => ( - + diff --git a/superset-frontend/src/profile/components/App.tsx b/superset-frontend/src/profile/components/App.tsx index e2c910abd2..08130176fe 100644 --- a/superset-frontend/src/profile/components/App.tsx +++ b/superset-frontend/src/profile/components/App.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { t, styled } from '@superset-ui/core'; import { Row, Col } from 'src/components'; import Tabs from 'src/components/Tabs'; -import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; +import { BootstrapUser } from 'src/types/bootstrapTypes'; import Favorites from './Favorites'; import UserInfo from './UserInfo'; import Security from './Security'; @@ -28,7 +28,7 @@ import RecentActivity from './RecentActivity'; import CreatedContent from './CreatedContent'; interface AppProps { - user: UserWithPermissionsAndRoles; + user: BootstrapUser; } const StyledTabPane = styled(Tabs.TabPane)` diff --git a/superset-frontend/src/profile/components/CreatedContent.tsx b/superset-frontend/src/profile/components/CreatedContent.tsx index e32fde86b1..a972ea5db9 100644 --- a/superset-frontend/src/profile/components/CreatedContent.tsx +++ b/superset-frontend/src/profile/components/CreatedContent.tsx @@ -22,13 +22,13 @@ import { t } from '@superset-ui/core'; import TableLoader from 'src/components/TableLoader'; import { - User, - DashboardResponse, + BootstrapUser, ChartResponse, + DashboardResponse, } from 'src/types/bootstrapTypes'; interface CreatedContentProps { - user: User; + user: BootstrapUser; } class CreatedContent extends React.PureComponent { diff --git a/superset-frontend/src/profile/components/Favorites.tsx b/superset-frontend/src/profile/components/Favorites.tsx index 1a7daa750f..1e28cd989a 100644 --- a/superset-frontend/src/profile/components/Favorites.tsx +++ b/superset-frontend/src/profile/components/Favorites.tsx @@ -20,13 +20,12 @@ import React from 'react'; import rison from 'rison'; import moment from 'moment'; import { t } from '@superset-ui/core'; - +import { DashboardResponse, BootstrapUser } from 'src/types/bootstrapTypes'; import TableLoader from '../../components/TableLoader'; import { Slice } from '../types'; -import { User, DashboardResponse } from '../../types/bootstrapTypes'; interface FavoritesProps { - user: User; + user: BootstrapUser; } export default class Favorites extends React.PureComponent { @@ -40,7 +39,7 @@ export default class Favorites extends React.PureComponent { })); return ( ); diff --git a/superset-frontend/src/profile/components/Security.tsx b/superset-frontend/src/profile/components/Security.tsx index 078fc42e4c..a102e55992 100644 --- a/superset-frontend/src/profile/components/Security.tsx +++ b/superset-frontend/src/profile/components/Security.tsx @@ -21,10 +21,10 @@ import Badge from 'src/components/Badge'; import { t } from '@superset-ui/core'; import Label from 'src/components/Label'; -import { UserWithPermissionsAndRoles } from '../../types/bootstrapTypes'; +import { BootstrapUser } from 'src/types/bootstrapTypes'; interface SecurityProps { - user: UserWithPermissionsAndRoles; + user: BootstrapUser; } export default function Security({ user }: SecurityProps) { @@ -32,15 +32,16 @@ export default function Security({ user }: SecurityProps) {

- {t('Roles')} + {t('Roles')}{' '} +

- {Object.keys(user.roles).map(role => ( + {Object.keys(user?.roles || {}).map(role => ( ))}
- {user.permissions.database_access && ( + {user?.permissions.database_access && (

{t('Databases')}{' '} @@ -54,7 +55,7 @@ export default function Security({ user }: SecurityProps) { )}

- {user.permissions.datasource_access && ( + {user?.permissions.datasource_access && (

{t('Datasets')}{' '} diff --git a/superset-frontend/src/profile/components/UserInfo.tsx b/superset-frontend/src/profile/components/UserInfo.tsx index 887b4ec50c..6b44e35cae 100644 --- a/superset-frontend/src/profile/components/UserInfo.tsx +++ b/superset-frontend/src/profile/components/UserInfo.tsx @@ -20,10 +20,10 @@ import React from 'react'; import Gravatar from 'react-gravatar'; import moment from 'moment'; import { t, styled } from '@superset-ui/core'; -import { UserWithPermissionsAndRoles } from '../../types/bootstrapTypes'; +import { BootstrapUser } from 'src/types/bootstrapTypes'; interface UserInfoProps { - user: UserWithPermissionsAndRoles; + user: BootstrapUser; } const StyledContainer = styled.div` @@ -37,7 +37,7 @@ export default function UserInfo({ user }: UserInfoProps) {

- {user.firstName} {user.lastName} + {user?.firstName} {user?.lastName}

- {user.username} + {user?.username}


{' '} - {t('joined')} {moment(user.createdOn, 'YYYYMMDD').fromNow()} + {t('joined')} {moment(user?.createdOn, 'YYYYMMDD').fromNow()}

- {user.email} + {user?.email}

- {Object.keys(user.roles).join(', ')} + {' '} + {Object.keys(user?.roles || {}).join(', ')}

  {t('id:')}  - {user.userId} + {user?.userId}

diff --git a/superset-frontend/src/showSavedQuery/index.jsx b/superset-frontend/src/showSavedQuery/index.jsx index 6b8adcd60d..10c09703f7 100644 --- a/superset-frontend/src/showSavedQuery/index.jsx +++ b/superset-frontend/src/showSavedQuery/index.jsx @@ -21,11 +21,10 @@ import ReactDom from 'react-dom'; import Form from 'react-jsonschema-form'; import { interpolate } from 'src/showSavedQuery/utils'; import { styled } from '@superset-ui/core'; +import getBootstrapData from 'src/utils/getBootstrapData'; const scheduleInfoContainer = document.getElementById('schedule-info'); -const bootstrapData = JSON.parse( - scheduleInfoContainer.getAttribute('data-bootstrap'), -); +const bootstrapData = getBootstrapData(); const config = bootstrapData.common.conf.SCHEDULED_QUERIES; const { query } = bootstrapData.common; const scheduleInfo = query.extra_json.schedule_info; diff --git a/superset-frontend/src/utils/getBootstrapData.ts b/superset-frontend/src/utils/getBootstrapData.ts index e8d708e40d..e426498e57 100644 --- a/superset-frontend/src/utils/getBootstrapData.ts +++ b/superset-frontend/src/utils/getBootstrapData.ts @@ -18,9 +18,10 @@ */ import { BootstrapData } from 'src/types/bootstrapTypes'; +import { DEFAULT_BOOTSTRAP_DATA } from 'src/constants'; export default function getBootstrapData(): BootstrapData { const appContainer = document.getElementById('app'); const dataBootstrap = appContainer?.getAttribute('data-bootstrap'); - return dataBootstrap ? JSON.parse(dataBootstrap) : {}; + return dataBootstrap ? JSON.parse(dataBootstrap) : DEFAULT_BOOTSTRAP_DATA; } diff --git a/superset-frontend/src/utils/hostNamesConfig.js b/superset-frontend/src/utils/hostNamesConfig.js index f2a1638cc7..2fda95c46a 100644 --- a/superset-frontend/src/utils/hostNamesConfig.js +++ b/superset-frontend/src/utils/hostNamesConfig.js @@ -21,6 +21,7 @@ import { isFeatureEnabled, FeatureFlag, } from 'src/featureFlags'; +import getBootstrapData from './getBootstrapData'; function getDomainsConfig() { const appContainer = document.getElementById('app'); @@ -38,11 +39,11 @@ function getDomainsConfig() { return Array.from(availableDomains); } - const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap')); + const bootstrapData = getBootstrapData(); // this module is a little special, it may be loaded before index.jsx, // where window.featureFlags get initialized // eslint-disable-next-line camelcase - initFeatureFlags(bootstrapData?.common?.feature_flags); + initFeatureFlags(bootstrapData.common.feature_flags); if ( isFeatureEnabled(FeatureFlag.ALLOW_DASHBOARD_DOMAIN_SHARDING) && diff --git a/superset-frontend/src/views/App.tsx b/superset-frontend/src/views/App.tsx index e780242dae..cacfaa1455 100644 --- a/superset-frontend/src/views/App.tsx +++ b/superset-frontend/src/views/App.tsx @@ -29,7 +29,7 @@ import { GlobalStyles } from 'src/GlobalStyles'; import ErrorBoundary from 'src/components/ErrorBoundary'; import Loading from 'src/components/Loading'; import Menu from 'src/views/components/Menu'; -import { bootstrapData } from 'src/preamble'; +import getBootstrapData from 'src/utils/getBootstrapData'; import ToastContainer from 'src/components/MessageToasts/ToastContainer'; import setupApp from 'src/setup/setupApp'; import setupPlugins from 'src/setup/setupPlugins'; @@ -46,10 +46,8 @@ setupApp(); setupPlugins(); setupExtensions(); -const user = { ...bootstrapData.user }; -const menu = { - ...bootstrapData.common.menu_data, -}; +const bootstrapData = getBootstrapData(); + let lastLocationPathname: string; const boundActions = bindActionCreators({ logEvent }, store.dispatch); @@ -78,13 +76,16 @@ const App = () => ( - + {routes.map(({ path, Component, props = {}, Fallback = Loading }) => ( }> - + diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 1bbaddb411..d449321686 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -65,7 +65,7 @@ import setupPlugins from 'src/setup/setupPlugins'; import InfoTooltip from 'src/components/InfoTooltip'; import CertifiedBadge from 'src/components/CertifiedBadge'; import { GenericLink } from 'src/components/GenericLink/GenericLink'; -import { bootstrapData } from 'src/preamble'; +import getBootstrapData from 'src/utils/getBootstrapData'; import Owner from 'src/types/Owner'; import ChartCard from './ChartCard'; @@ -157,6 +157,8 @@ const Actions = styled.div` color: ${({ theme }) => theme.colors.grayscale.base}; `; +const bootstrapData = getBootstrapData(); + function ChartList(props: ChartListProps) { const { addDangerToast, @@ -224,7 +226,7 @@ function ChartList(props: ChartListProps) { hasPerm('can_export') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }]; const enableBroadUserAccess = - bootstrapData?.common?.conf?.ENABLE_BROAD_ACTIVITY_ACCESS; + bootstrapData.common.conf.ENABLE_BROAD_ACTIVITY_ACCESS; const handleBulkChartExport = (chartsToExport: Chart[]) => { const ids = chartsToExport.map(({ id }) => id); handleResourceExport('chart', ids, () => { diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 23b9543423..6fe5bf2b20 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -49,7 +49,7 @@ import ImportModelsModal from 'src/components/ImportModal/index'; import Dashboard from 'src/dashboard/containers/Dashboard'; import CertifiedBadge from 'src/components/CertifiedBadge'; -import { bootstrapData } from 'src/preamble'; +import getBootstrapData from 'src/utils/getBootstrapData'; import DashboardCard from './DashboardCard'; import { DashboardStatus } from './types'; @@ -95,6 +95,8 @@ const Actions = styled.div` color: ${({ theme }) => theme.colors.grayscale.base}; `; +const bootstrapData = getBootstrapData(); + function DashboardList(props: DashboardListProps) { const { addDangerToast, diff --git a/superset-frontend/src/views/RootContextProviders.tsx b/superset-frontend/src/views/RootContextProviders.tsx index 99f620611b..795ea7c5ee 100644 --- a/superset-frontend/src/views/RootContextProviders.tsx +++ b/superset-frontend/src/views/RootContextProviders.tsx @@ -24,14 +24,14 @@ import { Provider as ReduxProvider } from 'react-redux'; import { QueryParamProvider } from 'use-query-params'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; - +import getBootstrapData from 'src/utils/getBootstrapData'; import { store } from './store'; import FlashProvider from '../components/FlashProvider'; -import { bootstrapData, theme } from '../preamble'; +import { theme } from '../preamble'; import { EmbeddedUiConfigProvider } from '../components/UiConfigContext'; import { DynamicPluginProvider } from '../components/DynamicPlugins'; -const common = { ...bootstrapData.common }; +const { common } = getBootstrapData(); const extensionsRegistry = getExtensionsRegistry(); diff --git a/superset-frontend/src/views/menu.tsx b/superset-frontend/src/views/menu.tsx index 4d27e3d6a4..287634a29b 100644 --- a/superset-frontend/src/views/menu.tsx +++ b/superset-frontend/src/views/menu.tsx @@ -26,6 +26,7 @@ import createCache from '@emotion/cache'; import { ThemeProvider } from '@superset-ui/core'; import Menu from 'src/views/components/Menu'; import { theme } from 'src/preamble'; +import getBootstrapData from 'src/utils/getBootstrapData'; import { Provider } from 'react-redux'; import { setupStore } from './store'; @@ -33,10 +34,8 @@ import { setupStore } from './store'; // Disable connecting to redux debugger so that the React app injected // Below the menu like SqlLab or Explore can connect its redux store to the debugger const store = setupStore(true); -const container = document.getElementById('app'); -const bootstrapJson = container?.getAttribute('data-bootstrap') ?? '{}'; -const bootstrap = JSON.parse(bootstrapJson); -const menu = { ...bootstrap.common.menu_data }; +const bootstrapData = getBootstrapData(); +const menu = { ...bootstrapData.common.menu_data }; const emotionCache = createCache({ key: 'menu', diff --git a/superset-frontend/src/views/store.ts b/superset-frontend/src/views/store.ts index 251feeec51..a143deb737 100644 --- a/superset-frontend/src/views/store.ts +++ b/superset-frontend/src/views/store.ts @@ -48,10 +48,12 @@ import { import shortid from 'shortid'; import { BootstrapUser, + UndefinedUser, UserWithPermissionsAndRoles, } from 'src/types/bootstrapTypes'; import { AnyDatasourcesAction } from 'src/explore/actions/datasourcesActions'; import { HydrateExplore } from 'src/explore/actions/hydrateExplore'; +import getBootstrapData from 'src/utils/getBootstrapData'; import { Dataset } from '@superset-ui/chart-controls'; // Some reducers don't do anything, and redux is just used to reference the initial "state". @@ -61,8 +63,7 @@ const noopReducer = (state: STATE = initialState) => state; -const container = document.getElementById('app'); -const bootstrap = JSON.parse(container?.getAttribute('data-bootstrap') ?? '{}'); +const bootstrapData = getBootstrapData(); export const USER_LOADED = 'USER_LOADED'; @@ -72,9 +73,9 @@ export type UserLoadedAction = { }; const userReducer = ( - user: BootstrapUser = bootstrap.user || {}, + user = bootstrapData.user || {}, action: UserLoadedAction, -): BootstrapUser => { +): BootstrapUser | UndefinedUser => { if (action.type === USER_LOADED) { return action.user; } @@ -104,7 +105,7 @@ const CombinedDatasourceReducers = ( // exported for tests export const rootReducer = combineReducers({ messageToasts: messageToastReducer, - common: noopReducer(bootstrap.common || {}), + common: noopReducer(bootstrapData.common), user: userReducer, impressionId: noopReducer(shortid.generate()), charts, diff --git a/superset/views/base.py b/superset/views/base.py index 74a2e2a2d7..d16baecc73 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -115,6 +115,7 @@ FRONTEND_CONF_KEYS = ( "HTML_SANITIZATION", "HTML_SANITIZATION_SCHEMA_EXTENSIONS", "WELCOME_PAGE_LAST_TAB", + "VIZ_TYPE_DENYLIST", ) logger = logging.getLogger(__name__)