fix(viz-gallery): respect denylist in viz gallery (#22658)

This commit is contained in:
Ville Brofeldt 2023-01-10 19:08:30 +02:00 committed by GitHub
parent 1e3746be21
commit 08f45ef207
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 111 additions and 123 deletions

View File

@ -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) =>

View File

@ -25,5 +25,5 @@ configureTestingLibrary({
testIdAttribute: 'data-test',
});
document.body.innerHTML = '<div id="app" data-bootstrap="{}"></div>';
document.body.innerHTML = '<div id="app" data-bootstrap=""></div>';
expect.extend(matchers);

View File

@ -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);

View File

@ -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 = {

View File

@ -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;

View File

@ -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),

View File

@ -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();

View File

@ -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={
<StyledStepDescription>
<VizTypeGallery
denyList={denyList}
className="viz-gallery"
onChange={this.changeVizType}
onDoubleClick={this.onVizTypeDoubleClick}

View File

@ -22,6 +22,7 @@ import { isFeatureEnabled, t, FeatureFlag } from '@superset-ui/core';
import { PluginContext } from 'src/components/DynamicPlugins';
import Loading from 'src/components/Loading';
import getBootstrapData from 'src/utils/getBootstrapData';
import getChartIdsFromLayout from '../util/getChartIdsFromLayout';
import getLayoutComponentFromChartId from '../util/getLayoutComponentFromChartId';
import DashboardBuilder from './DashboardBuilder/DashboardBuilder';
@ -97,8 +98,7 @@ class Dashboard extends React.PureComponent {
}
componentDidMount() {
const appContainer = document.getElementById('app');
const bootstrapData = appContainer?.getAttribute('data-bootstrap') || '{}';
const bootstrapData = getBootstrapData();
const { dashboardState, layout } = this.props;
const eventData = {
is_soft_navigation: Logger.timeOriginOffset > 0,

View File

@ -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;

View File

@ -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) {

View File

@ -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();

View File

@ -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,

View File

@ -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}
/>
</UnpaddedModal>
</>

View File

@ -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;

View File

@ -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<void, User>({

View File

@ -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 = () => (
<Provider store={store}>
<ThemeProvider theme={theme}>
<GlobalStyles />
<App user={bootstrap.user} />
<App user={bootstrapData.user} />
<ToastContainer />
</ThemeProvider>
</Provider>

View File

@ -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)`

View File

@ -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<CreatedContentProps> {

View File

@ -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<FavoritesProps> {
@ -40,7 +39,7 @@ export default class Favorites extends React.PureComponent<FavoritesProps> {
}));
return (
<TableLoader
dataEndpoint={`/superset/fave_slices/${this.props.user.userId}/`}
dataEndpoint={`/superset/fave_slices/${this.props.user?.userId}/`}
className="table-condensed"
columns={['slice', 'creator', 'favorited']}
mutator={mutator}

View File

@ -21,10 +21,10 @@ import moment from 'moment';
import TableLoader from '../../components/TableLoader';
import { Activity } from '../types';
import { User } from '../../types/bootstrapTypes';
import { BootstrapUser } from '../../types/bootstrapTypes';
interface RecentActivityProps {
user: User;
user: BootstrapUser;
}
export default function RecentActivity({ user }: RecentActivityProps) {
@ -45,7 +45,7 @@ export default function RecentActivity({ user }: RecentActivityProps) {
className="table-condensed"
mutator={mutator}
sortable
dataEndpoint={`/superset/recent_activity/${user.userId}/?limit=${rowLimit}`}
dataEndpoint={`/superset/recent_activity/${user?.userId}/?limit=${rowLimit}`}
/>
</div>
);

View File

@ -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) {
<div>
<div className="roles">
<h4>
{t('Roles')} <Badge count={Object.keys(user.roles).length} showZero />
{t('Roles')}{' '}
<Badge count={Object.keys(user?.roles || {}).length} showZero />
</h4>
{Object.keys(user.roles).map(role => (
{Object.keys(user?.roles || {}).map(role => (
<Label key={role}>{role}</Label>
))}
<hr />
</div>
<div className="databases">
{user.permissions.database_access && (
{user?.permissions.database_access && (
<div>
<h4>
{t('Databases')}{' '}
@ -54,7 +55,7 @@ export default function Security({ user }: SecurityProps) {
)}
</div>
<div className="datasources">
{user.permissions.datasource_access && (
{user?.permissions.datasource_access && (
<div>
<h4>
{t('Datasets')}{' '}

View File

@ -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) {
<StyledContainer>
<a href="https://en.gravatar.com/">
<Gravatar
email={user.email}
email={user?.email}
width="100%"
height=""
size={220}
@ -51,29 +51,30 @@ export default function UserInfo({ user }: UserInfoProps) {
<div className="header">
<h3>
<strong>
{user.firstName} {user.lastName}
{user?.firstName} {user?.lastName}
</strong>
</h3>
<h4 className="username">
<i className="fa fa-user-o" /> {user.username}
<i className="fa fa-user-o" /> {user?.username}
</h4>
</div>
<hr />
<p>
<i className="fa fa-clock-o" data-test="clock-icon-test" />{' '}
{t('joined')} {moment(user.createdOn, 'YYYYMMDD').fromNow()}
{t('joined')} {moment(user?.createdOn, 'YYYYMMDD').fromNow()}
</p>
<p className="email">
<i className="fa fa-envelope-o" /> {user.email}
<i className="fa fa-envelope-o" /> {user?.email}
</p>
<p className="roles">
<i className="fa fa-lock" /> {Object.keys(user.roles).join(', ')}
<i className="fa fa-lock" />{' '}
{Object.keys(user?.roles || {}).join(', ')}
</p>
<p>
<i className="fa fa-key" />
&nbsp;
<span className="text-muted">{t('id:')}</span>&nbsp;
<span className="user-id">{user.userId}</span>
<span className="user-id">{user?.userId}</span>
</p>
</div>
</StyledContainer>

View File

@ -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;

View File

@ -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;
}

View File

@ -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) &&

View File

@ -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 = () => (
<LocationPathnameLogger />
<RootContextProviders>
<GlobalStyles />
<Menu data={menu} isFrontendRoute={isFrontendRoute} />
<Menu
data={bootstrapData.common.menu_data}
isFrontendRoute={isFrontendRoute}
/>
<Switch>
{routes.map(({ path, Component, props = {}, Fallback = Loading }) => (
<Route path={path} key={path}>
<Suspense fallback={<Fallback />}>
<ErrorBoundary>
<Component user={user} {...props} />
<Component user={bootstrapData.user} {...props} />
</ErrorBoundary>
</Suspense>
</Route>

View File

@ -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, () => {

View File

@ -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,

View File

@ -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();

View File

@ -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',

View File

@ -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,

View File

@ -115,6 +115,7 @@ FRONTEND_CONF_KEYS = (
"HTML_SANITIZATION",
"HTML_SANITIZATION_SCHEMA_EXTENSIONS",
"WELCOME_PAGE_LAST_TAB",
"VIZ_TYPE_DENYLIST",
)
logger = logging.getLogger(__name__)