fix(dashboard): Only fetch CSS templates for dashboard header menu when in edit mode (#27411)

Co-authored-by: Michael S. Molina <michael.s.molina@gmail.com>
This commit is contained in:
Mark Skelton 2024-03-08 07:21:36 -06:00 committed by GitHub
parent 9ced2552db
commit fde93dcf08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 46 deletions

View File

@ -21,6 +21,7 @@ import { render, screen, waitFor } from 'spec/helpers/testing-library';
import { CssEditor as AceCssEditor } from 'src/components/AsyncAceEditor';
import { IAceEditorProps } from 'react-ace';
import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
import CssEditor from '.';
jest.mock('src/components/AsyncAceEditor', () => ({
@ -33,46 +34,59 @@ jest.mock('src/components/AsyncAceEditor', () => ({
}));
const templates = [
{ label: 'Template A', css: 'background-color: red;' },
{ label: 'Template B', css: 'background-color: blue;' },
{ label: 'Template C', css: 'background-color: yellow;' },
{ template_name: 'Template A', css: 'background-color: red;' },
{ template_name: 'Template B', css: 'background-color: blue;' },
{ template_name: 'Template C', css: 'background-color: yellow;' },
];
fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {
result: templates,
});
AceCssEditor.preload = () => new Promise(() => {});
test('renders with default props', () => {
render(<CssEditor triggerNode={<>Click</>} />);
const defaultProps = {
triggerNode: <>Click</>,
addDangerToast: jest.fn(),
};
test('renders with default props', async () => {
await waitFor(() => render(<CssEditor {...defaultProps} />));
expect(screen.getByRole('button', { name: 'Click' })).toBeInTheDocument();
});
test('renders with initial CSS', () => {
test('renders with initial CSS', async () => {
const initialCss = 'margin: 10px;';
render(<CssEditor triggerNode={<>Click</>} initialCss={initialCss} />);
await waitFor(() =>
render(<CssEditor {...defaultProps} initialCss={initialCss} />),
);
userEvent.click(screen.getByRole('button', { name: 'Click' }));
expect(screen.getByText(initialCss)).toBeInTheDocument();
});
test('renders with templates', async () => {
render(<CssEditor triggerNode={<>Click</>} templates={templates} />);
await waitFor(() => render(<CssEditor {...defaultProps} />));
userEvent.click(screen.getByRole('button', { name: 'Click' }));
userEvent.hover(screen.getByText('Load a CSS template'));
await waitFor(() => {
templates.forEach(template =>
expect(screen.getByText(template.label)).toBeInTheDocument(),
expect(screen.getByText(template.template_name)).toBeInTheDocument(),
);
});
});
test('triggers onChange when using the editor', () => {
test('triggers onChange when using the editor', async () => {
const onChange = jest.fn();
const initialCss = 'margin: 10px;';
const additionalCss = 'color: red;';
render(
<CssEditor
triggerNode={<>Click</>}
initialCss={initialCss}
onChange={onChange}
/>,
await waitFor(() =>
render(
<CssEditor
{...defaultProps}
initialCss={initialCss}
onChange={onChange}
/>,
),
);
userEvent.click(screen.getByRole('button', { name: 'Click' }));
expect(onChange).not.toHaveBeenCalled();
@ -82,12 +96,8 @@ test('triggers onChange when using the editor', () => {
test('triggers onChange when selecting a template', async () => {
const onChange = jest.fn();
render(
<CssEditor
triggerNode={<>Click</>}
templates={templates}
onChange={onChange}
/>,
await waitFor(() =>
render(<CssEditor {...defaultProps} onChange={onChange} />),
);
userEvent.click(screen.getByRole('button', { name: 'Click' }));
userEvent.click(screen.getByText('Load a CSS template'));

View File

@ -21,7 +21,7 @@ import PropTypes from 'prop-types';
import { AntdDropdown } from 'src/components';
import { Menu } from 'src/components/Menu';
import Button from 'src/components/Button';
import { t, styled } from '@superset-ui/core';
import { t, styled, SupersetClient } from '@superset-ui/core';
import ModalTrigger from 'src/components/ModalTrigger';
import { CssEditor as AceCssEditor } from 'src/components/AsyncAceEditor';
@ -47,7 +47,7 @@ const propTypes = {
initialCss: PropTypes.string,
triggerNode: PropTypes.node.isRequired,
onChange: PropTypes.func,
templates: PropTypes.array,
addDangerToast: PropTypes.func.isRequired,
};
const defaultProps = {
@ -60,6 +60,7 @@ class CssEditor extends React.PureComponent {
super(props);
this.state = {
css: props.initialCss,
templates: [],
};
this.changeCss = this.changeCss.bind(this);
this.changeCssTemplate = this.changeCssTemplate.bind(this);
@ -67,6 +68,22 @@ class CssEditor extends React.PureComponent {
componentDidMount() {
AceCssEditor.preload();
SupersetClient.get({ endpoint: '/csstemplateasyncmodelview/api/read' })
.then(({ json }) => {
const templates = json.result.map(row => ({
value: row.template_name,
css: row.css,
label: row.template_name,
}));
this.setState({ templates });
})
.catch(() => {
this.props.addDangerToast(
t('An error occurred while fetching available CSS templates'),
);
});
}
changeCss(css) {
@ -80,10 +97,10 @@ class CssEditor extends React.PureComponent {
}
renderTemplateSelector() {
if (this.props.templates) {
if (this.state.templates) {
const menu = (
<Menu onClick={this.changeCssTemplate}>
{this.props.templates.map(template => (
{this.state.templates.map(template => (
<Menu.Item key={template.css}>{template.label}</Menu.Item>
))}
</Menu>

View File

@ -19,7 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { isEmpty } from 'lodash';
import { SupersetClient, t } from '@superset-ui/core';
import { t } from '@superset-ui/core';
import { Menu } from 'src/components/Menu';
import { URL_PARAMS } from 'src/constants';
import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems';
@ -99,7 +99,6 @@ class HeaderActionsDropdown extends React.PureComponent {
super(props);
this.state = {
css: props.customCss,
cssTemplates: [],
showReportSubMenu: null,
};
@ -109,23 +108,6 @@ class HeaderActionsDropdown extends React.PureComponent {
this.setShowReportSubMenu = this.setShowReportSubMenu.bind(this);
}
UNSAFE_componentWillMount() {
SupersetClient.get({ endpoint: '/csstemplateasyncmodelview/api/read' })
.then(({ json }) => {
const cssTemplates = json.result.map(row => ({
value: row.template_name,
css: row.css,
label: row.template_name,
}));
this.setState({ cssTemplates });
})
.catch(() => {
this.props.addDangerToast(
t('An error occurred while fetching available CSS templates'),
);
});
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props.customCss !== nextProps.customCss) {
this.setState({ css: nextProps.customCss }, () => {
@ -257,8 +239,8 @@ class HeaderActionsDropdown extends React.PureComponent {
<CssEditor
triggerNode={<span>{t('Edit CSS')}</span>}
initialCss={this.state.css}
templates={this.state.cssTemplates}
onChange={this.changeCss}
addDangerToast={addDangerToast}
/>
</Menu.Item>
)}