mirror of https://github.com/apache/superset.git
fix(viz-gallery): respect denylist in viz gallery (#22658)
This commit is contained in:
parent
1e3746be21
commit
08f45ef207
|
@ -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) =>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>({
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)`
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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')}{' '}
|
||||
|
|
|
@ -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" />
|
||||
|
||||
<span className="text-muted">{t('id:')}</span>
|
||||
<span className="user-id">{user.userId}</span>
|
||||
<span className="user-id">{user?.userId}</span>
|
||||
</p>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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, () => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -115,6 +115,7 @@ FRONTEND_CONF_KEYS = (
|
|||
"HTML_SANITIZATION",
|
||||
"HTML_SANITIZATION_SCHEMA_EXTENSIONS",
|
||||
"WELCOME_PAGE_LAST_TAB",
|
||||
"VIZ_TYPE_DENYLIST",
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
Loading…
Reference in New Issue