[Charts] Use the Edit Properties modal throughout React views (#9267)

* typescriptification

* use the chart edit modal on the react list view

* linting

* typings don't work on old react-bootstrap version

* lint

* remove duplicate field
This commit is contained in:
David Aaron Suddjian
2020-03-13 15:14:50 -07:00
committed by GitHub
parent 91f3cb9878
commit f6f40c815a
10 changed files with 255 additions and 55 deletions

View File

@@ -17,6 +17,8 @@
* under the License.
*/
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import SyntaxHighlighter, {
registerLanguage,
@@ -47,6 +49,7 @@ import Button from '../../components/Button';
import RowCountLabel from './RowCountLabel';
import { prepareCopyToClipboardTabularData } from '../../utils/common';
import PropertiesModal from './PropertiesModal';
import { sliceUpdated } from '../actions/exploreActions';
registerLanguage('markdown', markdownSyntax);
registerLanguage('html', htmlSyntax);
@@ -65,7 +68,7 @@ const defaultProps = {
animation: true,
};
export default class DisplayQueryButton extends React.PureComponent {
export class DisplayQueryButton extends React.PureComponent {
constructor(props) {
super(props);
const { datasource } = props.latestQueryFormData;
@@ -220,6 +223,7 @@ export default class DisplayQueryButton extends React.PureComponent {
return null;
}
render() {
const { animation, slice } = this.props;
return (
<DropdownButton
noCaret
@@ -233,22 +237,23 @@ export default class DisplayQueryButton extends React.PureComponent {
pullRight
id="query"
>
{this.props.slice && (
{slice && (
<>
<MenuItem onClick={this.openPropertiesModal}>
{t('Edit properties')}
</MenuItem>
<PropertiesModal
slice={this.props.slice}
slice={slice}
show={this.state.isPropertiesModalOpen}
onHide={this.closePropertiesModal}
animation={this.props.animation}
onSave={this.props.sliceUpdated}
animation={animation}
/>
</>
)}
<ModalTrigger
isMenuItem
animation={this.props.animation}
animation={animation}
triggerNode={<span>{t('View query')}</span>}
modalTitle={t('View query')}
bsSize="large"
@@ -257,7 +262,7 @@ export default class DisplayQueryButton extends React.PureComponent {
/>
<ModalTrigger
isMenuItem
animation={this.props.animation}
animation={animation}
triggerNode={<span>{t('View results')}</span>}
modalTitle={t('View results')}
bsSize="large"
@@ -266,7 +271,7 @@ export default class DisplayQueryButton extends React.PureComponent {
/>
<ModalTrigger
isMenuItem
animation={this.props.animation}
animation={animation}
triggerNode={<span>{t('View samples')}</span>}
modalTitle={t('View samples')}
bsSize="large"
@@ -285,3 +290,9 @@ export default class DisplayQueryButton extends React.PureComponent {
DisplayQueryButton.propTypes = propTypes;
DisplayQueryButton.defaultProps = defaultProps;
function mapDispatchToProps(dispatch) {
return bindActionCreators({ sliceUpdated }, dispatch);
}
export default connect(null, mapDispatchToProps)(DisplayQueryButton);

View File

@@ -17,6 +17,8 @@
* under the License.
*/
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import { t } from '@superset-ui/translation';
@@ -29,6 +31,8 @@ import FaveStar from '../../components/FaveStar';
import TooltipWrapper from '../../components/TooltipWrapper';
import Timer from '../../components/Timer';
import CachedLabel from '../../components/CachedLabel';
import PropertiesModal from './PropertiesModal';
import { sliceUpdated } from '../actions/exploreActions';
const CHART_STATUS_MAP = {
failed: 'danger',
@@ -49,7 +53,16 @@ const propTypes = {
chart: chartPropShape,
};
class ExploreChartHeader extends React.PureComponent {
export class ExploreChartHeader extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isPropertiesModalOpen: false,
};
this.openProperiesModal = this.openProperiesModal.bind(this);
this.closePropertiesModal = this.closePropertiesModal.bind(this);
}
postChartFormData() {
this.props.actions.postChartFormData(
this.props.form_data,
@@ -93,6 +106,18 @@ class ExploreChartHeader extends React.PureComponent {
});
}
openProperiesModal() {
this.setState({
isPropertiesModalOpen: true,
});
}
closePropertiesModal() {
this.setState({
isPropertiesModalOpen: false,
});
}
renderChartTitle() {
let title;
if (this.props.slice) {
@@ -131,17 +156,24 @@ class ExploreChartHeader extends React.PureComponent {
saveFaveStar={this.props.actions.saveFaveStar}
isStarred={this.props.isStarred}
/>
<PropertiesModal
show={this.state.isPropertiesModalOpen}
onHide={this.closePropertiesModal}
onSave={this.props.sliceUpdated}
slice={this.props.slice}
/>
<TooltipWrapper
label="edit-desc"
tooltip={t('Edit chart properties')}
>
<a
<span
role="button"
tabIndex={0}
className="edit-desc-icon"
href={`/chart/edit/${this.props.slice.slice_id}`}
onClick={this.openProperiesModal}
>
<i className="fa fa-edit" />
</a>
</span>
</TooltipWrapper>
</span>
)}
@@ -187,4 +219,8 @@ class ExploreChartHeader extends React.PureComponent {
ExploreChartHeader.propTypes = propTypes;
export default ExploreChartHeader;
function mapDispatchToProps(dispatch) {
return bindActionCreators({ sliceUpdated }, dispatch);
}
export default connect(null, mapDispatchToProps)(ExploreChartHeader);

View File

@@ -17,8 +17,6 @@
* under the License.
*/
import React, { useState, useEffect, useRef } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
Button,
Modal,
@@ -26,16 +24,41 @@ import {
Col,
FormControl,
FormGroup,
// @ts-ignore
} from 'react-bootstrap';
// @ts-ignore
import Dialog from 'react-bootstrap-dialog';
import Select from 'react-select';
import { t } from '@superset-ui/translation';
import { SupersetClient } from '@superset-ui/connection';
import { sliceUpdated } from '../actions/exploreActions';
import { SupersetClient, Json } from '@superset-ui/connection';
import Chart from 'src/types/Chart';
import getClientErrorObject from '../../utils/getClientErrorObject';
function PropertiesModalWrapper({ show, onHide, animation, slice, onSave }) {
export type Slice = {
slice_id: number;
slice_name: string;
description: string | null;
cache_timeout: number | null;
};
type InternalProps = {
slice: Slice;
onHide: () => void;
onSave: (chart: Chart) => void;
};
export type WrapperProps = InternalProps & {
show: boolean;
animation?: boolean; // for the modal
};
export default function PropertiesModalWrapper({
show,
onHide,
animation,
slice,
onSave,
}: WrapperProps) {
// The wrapper is a separate component so that hooks only run when the modal opens
return (
<Modal show={show} onHide={onHide} animation={animation} bsSize="large">
@@ -44,9 +67,9 @@ function PropertiesModalWrapper({ show, onHide, animation, slice, onSave }) {
);
}
function PropertiesModal({ slice, onHide, onSave }) {
function PropertiesModal({ slice, onHide, onSave }: InternalProps) {
const [submitting, setSubmitting] = useState(false);
const errorDialog = useRef();
const errorDialog = useRef<any>(null);
const [ownerOptions, setOwnerOptions] = useState(null);
// values of form inputs
@@ -55,9 +78,9 @@ function PropertiesModal({ slice, onHide, onSave }) {
const [cacheTimeout, setCacheTimeout] = useState(
slice.cache_timeout != null ? slice.cache_timeout : '',
);
const [owners, setOwners] = useState(null);
const [owners, setOwners] = useState<any[] | null>(null);
function showError({ error, statusText }) {
function showError({ error, statusText }: any) {
errorDialog.current.show({
title: 'Error',
bsSize: 'medium',
@@ -72,8 +95,9 @@ function PropertiesModal({ slice, onHide, onSave }) {
const response = await SupersetClient.get({
endpoint: `/api/v1/chart/${slice.slice_id}`,
});
const chart = (response.json as Json).result;
setOwners(
response.json.result.owners.map(owner => ({
chart.owners.map((owner: any) => ({
value: owner.id,
label: owner.username,
})),
@@ -94,8 +118,9 @@ function PropertiesModal({ slice, onHide, onSave }) {
SupersetClient.get({
endpoint: `/api/v1/chart/related/owners`,
}).then(res => {
const { result } = res.json as Json;
setOwnerOptions(
res.json.result.map(item => ({
result.map((item: any) => ({
value: item.value,
label: item.text,
})),
@@ -103,7 +128,7 @@ function PropertiesModal({ slice, onHide, onSave }) {
});
}, []);
const onSubmit = async event => {
const onSubmit = async (event: React.FormEvent) => {
event.stopPropagation();
event.preventDefault();
setSubmitting(true);
@@ -111,7 +136,7 @@ function PropertiesModal({ slice, onHide, onSave }) {
slice_name: name || null,
description: description || null,
cache_timeout: cacheTimeout || null,
owners: owners.map(o => o.value),
owners: owners!.map(o => o.value),
};
try {
const res = await SupersetClient.put({
@@ -120,7 +145,11 @@ function PropertiesModal({ slice, onHide, onSave }) {
body: JSON.stringify(payload),
});
// update the redux state
onSave(res.json.result);
const updatedChart = {
...(res.json as Json).result,
id: slice.slice_id,
};
onSave(updatedChart);
onHide();
} catch (res) {
const clientError = await getClientErrorObject(res);
@@ -147,7 +176,9 @@ function PropertiesModal({ slice, onHide, onSave }) {
type="text"
bsSize="sm"
value={name}
onChange={event => setName(event.target.value)}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setName(event.target.value)
}
/>
</FormGroup>
<FormGroup>
@@ -160,7 +191,9 @@ function PropertiesModal({ slice, onHide, onSave }) {
componentClass="textarea"
bsSize="sm"
value={description}
onChange={event => setDescription(event.target.value)}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setDescription(event.target.value)
}
style={{ maxWidth: '100%' }}
/>
<p className="help-block">
@@ -181,7 +214,7 @@ function PropertiesModal({ slice, onHide, onSave }) {
type="text"
bsSize="sm"
value={cacheTimeout}
onChange={event =>
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setCacheTimeout(event.target.value.replace(/[^0-9]/, ''))
}
/>
@@ -218,7 +251,7 @@ function PropertiesModal({ slice, onHide, onSave }) {
bsSize="sm"
bsStyle="primary"
className="m-r-5"
disabled={submitting}
disabled={!owners || submitting}
>
{t('Save')}
</Button>
@@ -230,10 +263,3 @@ function PropertiesModal({ slice, onHide, onSave }) {
</form>
);
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ onSave: sliceUpdated }, dispatch);
}
export { PropertiesModalWrapper };
export default connect(null, mapDispatchToProps)(PropertiesModalWrapper);