[SIP-4] replace explorer ajax calls with `SupersetClient` (#5869)

* [superset-client] initialize SupersetClient in app setup

* [core] replace explore ajax calls with SupersetClient

* [core] fix SupersetClient explore tests

* [core] remove _packages mistake directory

* remove unused files

* add yarn.lock

* always render modal

* [superset-client][jest] fix SaveModal_spec

* [lint] remove unnecessary AbortController global

* yarn.lock
This commit is contained in:
Chris Williams 2018-10-16 14:54:31 -07:00 committed by GitHub
parent dcfbae1ab9
commit af0ffa44ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 215 additions and 2117 deletions

View File

@ -5,12 +5,12 @@ import thunk from 'redux-thunk';
import { shallow, mount } from 'enzyme';
import { Modal, Button, Radio } from 'react-bootstrap';
import sinon from 'sinon';
import fetchMock from 'fetch-mock';
import * as exploreUtils from '../../../../src/explore/exploreUtils';
import * as saveModalActions from '../../../../src/explore/actions/saveModalActions';
import SaveModal from '../../../../src/explore/components/SaveModal';
const $ = window.$ = require('jquery');
import setupSupersetClient from '../../../helpers/setupSupersetClient';
describe('SaveModal', () => {
const middlewares = [thunk];
@ -44,9 +44,11 @@ describe('SaveModal', () => {
},
value: 'mock value',
};
const getWrapper = () => (shallow(<SaveModal {...defaultProps} />, {
context: { store },
}).dive());
const getWrapper = () =>
shallow(<SaveModal {...defaultProps} />, {
context: { store },
}).dive();
it('renders a Modal with 7 inputs and 2 buttons', () => {
const wrapper = getWrapper();
@ -115,13 +117,17 @@ describe('SaveModal', () => {
describe('saveOrOverwrite', () => {
beforeEach(() => {
sinon.stub(exploreUtils, 'getExploreUrlAndPayload').callsFake(() => ({ url: 'mockURL', payload: defaultProps.form_data }));
sinon.stub(saveModalActions, 'saveSlice').callsFake(() => {
const d = $.Deferred();
d.resolve('done');
return d.promise();
});
sinon
.stub(exploreUtils, 'getExploreUrlAndPayload')
.callsFake(() => ({ url: 'mockURL', payload: defaultProps.form_data }));
sinon
.stub(saveModalActions, 'saveSlice')
.callsFake(() =>
Promise.resolve({ data: { dashboard: '/mock/', slice: { slice_url: '/mock/' } } }),
);
});
afterEach(() => {
exploreUtils.getExploreUrlAndPayload.restore();
saveModalActions.saveSlice.restore();
@ -133,6 +139,7 @@ describe('SaveModal', () => {
const args = saveModalActions.saveSlice.getCall(0).args;
expect(args[0]).toEqual(defaultProps.form_data);
});
it('existing dashboard', () => {
const wrapper = getWrapper();
const saveToDashboardId = 100;
@ -146,6 +153,7 @@ describe('SaveModal', () => {
const args = saveModalActions.saveSlice.getCall(0).args;
expect(args[1].save_to_dashboard_id).toBe(saveToDashboardId);
});
it('new dashboard', () => {
const wrapper = getWrapper();
const newDashboardName = 'new dashboard name';
@ -161,52 +169,68 @@ describe('SaveModal', () => {
});
});
describe('should fetchDashboards', () => {
describe('fetchDashboards', () => {
let dispatch;
let request;
let ajaxStub;
let actionThunk;
const userID = 1;
const mockDashboardData = {
pks: ['id'],
result: [{ id: 'id', dashboard_title: 'dashboard title' }],
};
const saveEndpoint = `glob:*/dashboardasync/api/read?_flt_0_owners=${1}`;
beforeAll(() => {
setupSupersetClient();
fetchMock.get(saveEndpoint, mockDashboardData);
});
afterAll(fetchMock.restore);
beforeEach(() => {
dispatch = sinon.spy();
ajaxStub = sinon.stub($, 'ajax');
});
afterEach(() => {
ajaxStub.restore();
fetchMock.resetHistory();
});
const mockDashboardData = {
pks: ['value'],
result: [
{ dashboard_title: 'dashboard title' },
],
};
const makeRequest = () => {
request = saveModalActions.fetchDashboards(userID);
request(dispatch);
actionThunk = saveModalActions.fetchDashboards(userID);
return actionThunk(dispatch);
};
it('makes the ajax request', () => {
makeRequest();
expect(ajaxStub.callCount).toBe(1);
});
it('makes the fetch request', () => (
makeRequest().then(() => {
expect(fetchMock.calls(saveEndpoint)).toHaveLength(1);
it('calls correct url', () => {
const url = '/dashboardasync/api/read?_flt_0_owners=' + userID;
makeRequest();
expect(ajaxStub.getCall(0).args[0].url).toBe(url);
});
return Promise.resolve();
})
));
it('calls correct actions on success', () => (
makeRequest().then(() => {
expect(dispatch.callCount).toBe(1);
expect(dispatch.getCall(0).args[0].type).toBe(
saveModalActions.FETCH_DASHBOARDS_SUCCEEDED,
);
return Promise.resolve();
})
));
it('calls correct actions on error', () => {
ajaxStub.yieldsTo('error', { responseJSON: { error: 'error text' } });
makeRequest();
expect(dispatch.callCount).toBe(1);
expect(dispatch.getCall(0).args[0].type).toBe(saveModalActions.FETCH_DASHBOARDS_FAILED);
});
fetchMock.get(saveEndpoint, { throws: 'error' }, { overwriteRoutes: true });
it('calls correct actions on success', () => {
ajaxStub.yieldsTo('success', mockDashboardData);
makeRequest();
expect(dispatch.callCount).toBe(1);
expect(dispatch.getCall(0).args[0].type).toBe(saveModalActions.FETCH_DASHBOARDS_SUCCEEDED);
return makeRequest().then(() => {
expect(dispatch.callCount).toBe(1);
expect(dispatch.getCall(0).args[0].type).toBe(saveModalActions.FETCH_DASHBOARDS_FAILED);
fetchMock.get(saveEndpoint, mockDashboardData, { overwriteRoutes: true });
return Promise.resolve();
});
});
});

View File

@ -1,7 +1,7 @@
/* eslint global-require: 0, no-console: 0 */
import $ from 'jquery';
import { SupersetClient } from '@superset-ui/core';
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
import { SupersetClient } from '@superset-ui/core';
import airbnb from './modules/colorSchemes/airbnb';
import categoricalSchemes from './modules/colorSchemes/categorical';

View File

@ -42,13 +42,13 @@ export default class ModalTrigger extends React.Component {
}
close() {
this.setState({ showModal: false });
this.setState(() => ({ showModal: false }));
}
open(e) {
e.preventDefault();
this.props.beforeOpen();
this.setState({ showModal: true });
this.setState(() => ({ showModal: true }));
}
renderModal() {
return (

View File

@ -1,5 +1,7 @@
/* eslint camelcase: 0 */
const $ = window.$ = require('jquery');
import { SupersetClient } from '@superset-ui/core';
import { addDangerToast } from '../../messageToasts/actions';
import { t } from '../../locales';
const FAVESTAR_BASE_URL = '/superset/favstar/slice';
@ -50,24 +52,6 @@ export function resetControls() {
return { type: RESET_FIELDS };
}
export function fetchDatasources() {
return function (dispatch) {
dispatch(fetchDatasourcesStarted());
const url = '/superset/datasources/';
$.ajax({
type: 'GET',
url,
success: (data) => {
dispatch(setDatasources(data));
dispatch(fetchDatasourcesSucceeded());
},
error(error) {
dispatch(fetchDatasourcesFailed(error.responseJSON.error));
},
});
};
}
export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
export function toggleFaveStar(isStarred) {
return { type: TOGGLE_FAVE_STAR, isStarred };
@ -76,9 +60,8 @@ export function toggleFaveStar(isStarred) {
export const FETCH_FAVE_STAR = 'FETCH_FAVE_STAR';
export function fetchFaveStar(sliceId) {
return function (dispatch) {
const url = `${FAVESTAR_BASE_URL}/${sliceId}/count`;
$.get(url, (data) => {
if (data.count > 0) {
SupersetClient.get({ endpoint: `${FAVESTAR_BASE_URL}/${sliceId}/count` }).then(({ json }) => {
if (json.count > 0) {
dispatch(toggleFaveStar(true));
}
});
@ -89,9 +72,9 @@ export const SAVE_FAVE_STAR = 'SAVE_FAVE_STAR';
export function saveFaveStar(sliceId, isStarred) {
return function (dispatch) {
const urlSuffix = isStarred ? 'unselect' : 'select';
const url = `${FAVESTAR_BASE_URL}/${sliceId}/${urlSuffix}/`;
$.get(url);
dispatch(toggleFaveStar(!isStarred));
SupersetClient.get({ endpoint: `${FAVESTAR_BASE_URL}/${sliceId}/${urlSuffix}/` })
.then(() => dispatch(toggleFaveStar(!isStarred)))
.catch(() => dispatch(addDangerToast(t('An error occurred while starring this chart'))));
};
}

View File

@ -1,7 +1,6 @@
import { SupersetClient } from '@superset-ui/core';
import { getExploreUrlAndPayload } from '../exploreUtils';
const $ = window.$ = require('jquery');
export const FETCH_DASHBOARDS_SUCCEEDED = 'FETCH_DASHBOARDS_SUCCEEDED';
export function fetchDashboardsSucceeded(choices) {
return { type: FETCH_DASHBOARDS_SUCCEEDED, choices };
@ -13,22 +12,19 @@ export function fetchDashboardsFailed(userId) {
}
export function fetchDashboards(userId) {
return function (dispatch) {
const url = '/dashboardasync/api/read?_flt_0_owners=' + userId;
return $.ajax({
type: 'GET',
url,
success: (data) => {
const choices = [];
for (let i = 0; i < data.pks.length; i++) {
choices.push({ value: data.pks[i], label: data.result[i].dashboard_title });
}
dispatch(fetchDashboardsSucceeded(choices));
},
error: () => {
dispatch(fetchDashboardsFailed(userId));
},
});
return function fetchDashboardsThunk(dispatch) {
return SupersetClient.get({
endpoint: `/dashboardasync/api/read?_flt_0_owners=${userId}`,
})
.then(({ json }) => {
const choices = json.pks.map((id, index) => ({
value: id,
label: (json.result[index] || {}).dashboard_title,
}));
return dispatch(fetchDashboardsSucceeded(choices));
})
.catch(() => dispatch(fetchDashboardsFailed(userId)));
};
}
@ -55,18 +51,9 @@ export function saveSlice(formData, requestParams) {
curUrl: null,
requestParams,
});
return $.ajax({
type: 'POST',
url,
data: {
form_data: JSON.stringify(payload),
},
success: ((data) => {
dispatch(saveSliceSuccess(data));
}),
error: (() => {
dispatch(saveSliceFailed());
}),
});
return SupersetClient.post({ url, postPayload: { form_data: payload } })
.then(({ json }) => dispatch(saveSliceSuccess(json)))
.catch(() => dispatch(saveSliceFailed()));
};
}

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormGroup } from 'react-bootstrap';
import VirtualizedSelect from 'react-virtualized-select';
import { SupersetClient } from '@superset-ui/core';
import AdhocFilter, { EXPRESSION_TYPES, CLAUSES } from '../AdhocFilter';
import adhocMetricType from '../propTypes/adhocMetricType';
@ -19,16 +20,16 @@ import OnPasteSelect from '../../components/OnPasteSelect';
import SelectControl from './controls/SelectControl';
import VirtualizedRendererWrap from '../../components/VirtualizedRendererWrap';
const $ = require('jquery');
const propTypes = {
adhocFilter: PropTypes.instanceOf(AdhocFilter).isRequired,
onChange: PropTypes.func.isRequired,
options: PropTypes.arrayOf(PropTypes.oneOfType([
columnType,
PropTypes.shape({ saved_metric_name: PropTypes.string.isRequired }),
adhocMetricType,
])).isRequired,
options: PropTypes.arrayOf(
PropTypes.oneOfType([
columnType,
PropTypes.shape({ saved_metric_name: PropTypes.string.isRequired }),
adhocMetricType,
]),
).isRequired,
onHeightChange: PropTypes.func.isRequired,
datasource: PropTypes.object,
};
@ -64,6 +65,7 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
this.state = {
suggestions: [],
multiComparatorHeight: SINGLE_LINE_SELECT_CONTROL_HEIGHT,
abortActiveRequest: null,
};
this.selectProps = {
@ -102,11 +104,13 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
subject = option.saved_metric_name || option.label;
clause = CLAUSES.HAVING;
}
this.props.onChange(this.props.adhocFilter.duplicateWith({
subject,
clause,
expressionType: EXPRESSION_TYPES.SIMPLE,
}));
this.props.onChange(
this.props.adhocFilter.duplicateWith({
subject,
clause,
expressionType: EXPRESSION_TYPES.SIMPLE,
}),
);
}
onOperatorChange(operator) {
@ -115,17 +119,19 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
// convert between list of comparators and individual comparators
// (e.g. `in ('North America', 'Africa')` to `== 'North America'`)
if (MULTI_OPERATORS.indexOf(operator.operator) >= 0) {
newComparator = Array.isArray(currentComparator) ?
currentComparator :
[currentComparator].filter(element => element);
newComparator = Array.isArray(currentComparator)
? currentComparator
: [currentComparator].filter(element => element);
} else {
newComparator = Array.isArray(currentComparator) ? currentComparator[0] : currentComparator;
}
this.props.onChange(this.props.adhocFilter.duplicateWith({
operator: operator && operator.operator,
comparator: newComparator,
expressionType: EXPRESSION_TYPES.SIMPLE,
}));
this.props.onChange(
this.props.adhocFilter.duplicateWith({
operator: operator && operator.operator,
comparator: newComparator,
expressionType: EXPRESSION_TYPES.SIMPLE,
}),
);
}
onInputComparatorChange(event) {
@ -133,23 +139,26 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
}
onComparatorChange(comparator) {
this.props.onChange(this.props.adhocFilter.duplicateWith({
comparator,
expressionType: EXPRESSION_TYPES.SIMPLE,
}));
this.props.onChange(
this.props.adhocFilter.duplicateWith({
comparator,
expressionType: EXPRESSION_TYPES.SIMPLE,
}),
);
}
handleMultiComparatorInputHeightChange() {
if (this.multiComparatorComponent) {
/* eslint-disable no-underscore-dangle */
const multiComparatorDOMNode = this.multiComparatorComponent._selectRef &&
const multiComparatorDOMNode =
this.multiComparatorComponent._selectRef &&
this.multiComparatorComponent._selectRef.select &&
this.multiComparatorComponent._selectRef.select.control;
if (multiComparatorDOMNode) {
if (multiComparatorDOMNode.clientHeight !== this.state.multiComparatorHeight) {
this.props.onHeightChange((
multiComparatorDOMNode.clientHeight - this.state.multiComparatorHeight
));
this.props.onHeightChange(
multiComparatorDOMNode.clientHeight - this.state.multiComparatorHeight,
);
this.setState({ multiComparatorHeight: multiComparatorDOMNode.clientHeight });
}
}
@ -162,15 +171,19 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
const having = this.props.adhocFilter.clause === CLAUSES.HAVING;
if (col && datasource && datasource.filter_select && !having) {
if (this.state.activeRequest) {
this.state.activeRequest.abort();
if (this.state.abortActiveRequest) {
this.state.abortActiveRequest();
}
this.setState({
activeRequest: $.ajax({
type: 'GET',
url: `/superset/filter/${datasource.type}/${datasource.id}/${col}/`,
success: data => this.setState({ suggestions: data, activeRequest: null }),
}),
const controller = new AbortController();
const { signal } = controller;
this.setState({ abortActiveRequest: controller.abort });
SupersetClient.get({
signal,
endpoint: `/superset/filter/${datasource.type}/${datasource.id}/${col}/`,
}).then(({ json }) => {
this.setState(() => ({ suggestions: json, abortActiveRequest: null }));
});
}
}
@ -179,10 +192,8 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
return !(
(this.props.datasource.type === 'druid' && TABLE_ONLY_OPERATORS.indexOf(operator) >= 0) ||
(this.props.datasource.type === 'table' && DRUID_ONLY_OPERATORS.indexOf(operator) >= 0) ||
(
this.props.adhocFilter.clause === CLAUSES.HAVING &&
HAVING_OPERATORS.indexOf(operator) === -1
)
(this.props.adhocFilter.clause === CLAUSES.HAVING &&
HAVING_OPERATORS.indexOf(operator) === -1)
);
}
@ -204,9 +215,7 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
let subjectSelectProps = {
value: adhocFilter.subject ? { value: adhocFilter.subject } : undefined,
onChange: this.onSubjectChange,
optionRenderer: VirtualizedRendererWrap(option => (
<FilterDefinitionOption option={option} />
)),
optionRenderer: VirtualizedRendererWrap(option => <FilterDefinitionOption option={option} />),
valueRenderer: option => <span>{option.value}</span>,
valueKey: 'filterOptionName',
noResultsText: t('No such column found. To filter on a metric, try the Custom SQL tab.'),
@ -224,28 +233,23 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
// becomes a rather complicated problem)
subjectSelectProps = {
...subjectSelectProps,
placeholder: adhocFilter.clause === CLAUSES.WHERE ?
t('%s column(s)', options.length) :
t('To filter on a metric, use Custom SQL tab.'),
placeholder:
adhocFilter.clause === CLAUSES.WHERE
? t('%s column(s)', options.length)
: t('To filter on a metric, use Custom SQL tab.'),
options: options.filter(option => option.column_name),
};
}
const operatorSelectProps = {
placeholder: t('%s operators(s)', Object.keys(OPERATORS).length),
options: Object.keys(OPERATORS).filter(this.isOperatorRelevant).map((
operator => ({ operator })
)),
options: Object.keys(OPERATORS)
.filter(this.isOperatorRelevant)
.map(operator => ({ operator })),
value: adhocFilter.operator,
onChange: this.onOperatorChange,
optionRenderer: VirtualizedRendererWrap((
operator => translateOperator(operator.operator)
)),
valueRenderer: operator => (
<span>
{translateOperator(operator.operator)}
</span>
),
optionRenderer: VirtualizedRendererWrap(operator => translateOperator(operator.operator)),
valueRenderer: operator => <span>{translateOperator(operator.operator)}</span>,
valueKey: 'operator',
};

View File

@ -1,14 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import SyntaxHighlighter, { registerLanguage } from 'react-syntax-highlighter/dist/light';
import html from 'react-syntax-highlighter/languages/hljs/htmlbars';
import markdown from 'react-syntax-highlighter/languages/hljs/markdown';
import sql from 'react-syntax-highlighter/languages/hljs/sql';
import json from 'react-syntax-highlighter/languages/hljs/json';
import SyntaxHighlighter, { registerLanguage } from 'react-syntax-highlighter/light';
import htmlSyntax from 'react-syntax-highlighter/languages/hljs/htmlbars';
import markdownSyntax from 'react-syntax-highlighter/languages/hljs/markdown';
import sqlSyntax from 'react-syntax-highlighter/languages/hljs/sql';
import jsonSyntax from 'react-syntax-highlighter/languages/hljs/json';
import github from 'react-syntax-highlighter/styles/hljs/github';
import { DropdownButton, MenuItem, Row, Col, FormControl } from 'react-bootstrap';
import { Table } from 'reactable';
import $ from 'jquery';
import { SupersetClient } from '@superset-ui/core';
import CopyToClipboard from './../../components/CopyToClipboard';
import { getExploreUrlAndPayload } from '../exploreUtils';
@ -19,10 +19,10 @@ import Button from '../../components/Button';
import { t } from '../../locales';
import RowCountLabel from './RowCountLabel';
registerLanguage('markdown', markdown);
registerLanguage('html', html);
registerLanguage('sql', sql);
registerLanguage('json', json);
registerLanguage('markdown', markdownSyntax);
registerLanguage('html', htmlSyntax);
registerLanguage('sql', sqlSyntax);
registerLanguage('json', jsonSyntax);
const propTypes = {
onOpenInEditor: PropTypes.func,
@ -57,28 +57,25 @@ export default class DisplayQueryButton extends React.PureComponent {
formData: this.props.latestQueryFormData,
endpointType,
});
$.ajax({
type: 'POST',
SupersetClient.post({
url,
data: {
form_data: JSON.stringify(payload),
},
success: (data) => {
postPayload: { form_data: payload },
})
.then(({ json }) => {
this.setState({
language: data.language,
query: data.query,
data: data.data,
language: json.language,
query: json.query,
data: json.data,
isLoading: false,
error: null,
});
},
error: (data) => {
})
.catch((error) => {
this.setState({
error: data.responseJSON ? data.responseJSON.error : t('Error...'),
error: error.error || error.statusText || t('Sorry, An error occurred'),
isLoading: false,
});
},
});
});
}
changeFilterText(event) {
this.setState({ filterText: event.target.value });
@ -113,11 +110,7 @@ export default class DisplayQueryButton extends React.PureComponent {
}
renderResultsModalBody() {
if (this.state.isLoading) {
return (<img
className="loading"
alt={t('Loading...')}
src="/static/assets/images/loading.gif"
/>);
return <Loading />;
} else if (this.state.error) {
return <pre>{this.state.error}</pre>;
} else if (this.state.data) {
@ -213,12 +206,14 @@ export default class DisplayQueryButton extends React.PureComponent {
modalBody={this.renderSamplesModalBody()}
eventKey="2"
/>
{this.state.sqlSupported && <MenuItem
eventKey="3"
onClick={this.redirectSQLLab.bind(this)}
>
{t('Run in SQL Lab')}
</MenuItem>}
{this.state.sqlSupported && (
<MenuItem
eventKey="3"
onClick={this.redirectSQLLab.bind(this)}
>
{t('Run in SQL Lab')}
</MenuItem>
)}
</DropdownButton>
);
}

View File

@ -85,7 +85,7 @@ class SaveModal extends React.Component {
sliceParams.add_to_dash = addToDash;
let dashboard = null;
switch (addToDash) {
case ('existing'):
case 'existing':
dashboard = this.state.saveToDashboardId;
if (!dashboard) {
this.setState({ alert: t('Please select a dashboard') });
@ -93,7 +93,7 @@ class SaveModal extends React.Component {
}
sliceParams.save_to_dashboard_id = dashboard;
break;
case ('new'):
case 'new':
dashboard = this.state.newDashboardName;
if (dashboard === '') {
this.setState({ alert: t('Please enter a dashboard name') });
@ -106,15 +106,14 @@ class SaveModal extends React.Component {
}
sliceParams.goto_dash = gotodash;
this.props.actions.saveSlice(this.props.form_data, sliceParams)
.then((data) => {
// Go to new slice url or dashboard url
if (gotodash) {
window.location = supersetURL(data.dashboard);
} else {
window.location = data.slice.slice_url;
}
});
this.props.actions.saveSlice(this.props.form_data, sliceParams).then(({ data }) => {
// Go to new slice url or dashboard url
if (gotodash) {
window.location = supersetURL(data.dashboard);
} else {
window.location = data.slice.slice_url;
}
});
this.props.onHide();
}
removeAlert() {
@ -126,18 +125,12 @@ class SaveModal extends React.Component {
render() {
const canNotSaveToDash = EXPLORE_ONLY_VIZ_TYPE.indexOf(this.state.vizType) > -1;
return (
<Modal
show
onHide={this.props.onHide}
bsStyle="large"
>
<Modal show onHide={this.props.onHide} bsStyle="large">
<Modal.Header closeButton>
<Modal.Title>
{t('Save A Chart')}
</Modal.Title>
<Modal.Title>{t('Save A Chart')}</Modal.Title>
</Modal.Header>
<Modal.Body>
{(this.state.alert || this.props.alert) &&
{(this.state.alert || this.props.alert) && (
<Alert>
{this.state.alert ? this.state.alert : this.props.alert}
<i
@ -146,8 +139,8 @@ class SaveModal extends React.Component {
style={{ cursor: 'pointer' }}
/>
</Alert>
}
{this.props.slice &&
)}
{this.props.slice && (
<Radio
id="overwrite-radio"
disabled={!this.props.can_overwrite}
@ -156,14 +149,16 @@ class SaveModal extends React.Component {
>
{t('Overwrite chart %s', this.props.slice.slice_name)}
</Radio>
}
)}
<Radio
id="saveas-radio"
inline
checked={this.state.action === 'saveas'}
onChange={this.changeAction.bind(this, 'saveas')}
> {t('Save as')} &nbsp;
>
{' '}
{t('Save as')} &nbsp;
</Radio>
<input
name="new_slice_name"
@ -172,7 +167,6 @@ class SaveModal extends React.Component {
onFocus={this.changeAction.bind(this, 'saveas')}
/>
<br />
<hr />
@ -199,7 +193,7 @@ class SaveModal extends React.Component {
onChange={this.onChange.bind(this, 'saveToDashboardId')}
autoSize={false}
value={this.state.saveToDashboardId}
placeholder={t('Select Dashboard')}
placeholder="Select Dashboard"
/>
<Radio
@ -217,7 +211,6 @@ class SaveModal extends React.Component {
onFocus={this.changeDash.bind(this, 'new')}
placeholder={t('[dashboard name]')}
/>
</Modal.Body>
<Modal.Footer>
@ -258,4 +251,7 @@ function mapStateToProps({ explore, saveModal }) {
}
export { SaveModal };
export default connect(mapStateToProps, () => ({}))(SaveModal);
export default connect(
mapStateToProps,
() => ({}),
)(SaveModal);

File diff suppressed because it is too large Load Diff