You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by yj...@apache.org on 2020/10/25 04:41:06 UTC
[incubator-superset] branch master updated: refactor: typing for
explore Control and messageToasts (#11416)
This is an automated email from the ASF dual-hosted git repository.
yjc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to refs/heads/master by this push:
new 8aecffd refactor: typing for explore Control and messageToasts (#11416)
8aecffd is described below
commit 8aecffd83ba994c73d10c6879f9ab274cef11153
Author: Jesse Yang <je...@airbnb.com>
AuthorDate: Sat Oct 24 21:40:36 2020 -0700
refactor: typing for explore Control and messageToasts (#11416)
---
.../dashboard/actions/dashboardLayout_spec.js | 29 ++++---
.../javascripts/messageToasts/mockMessageToasts.js | 6 +-
.../utils/getToastsFromPyFlashMessages_spec.js | 15 ++--
superset-frontend/src/chart/Chart.jsx | 4 +-
.../src/datasource/DatasourceEditor.jsx | 1 +
.../{exploreActions.js => exploreActions.ts} | 91 +++++++++++++---------
.../components/{Control.jsx => Control.tsx} | 76 ++++++++----------
.../explore/components/ControlPanelsContainer.jsx | 4 +-
.../src/explore/components/PropertiesModal.tsx | 51 ++++++------
.../src/explore/reducers/exploreReducer.js | 13 ----
.../messageToasts/actions/{index.js => index.ts} | 38 ++++-----
.../src/messageToasts/components/Toast.tsx | 39 +++++-----
.../messageToasts/components/ToastPresenter.tsx | 37 ++++-----
.../messageToasts/{constants.js => constants.ts} | 13 +++-
superset-frontend/src/messageToasts/propShapes.js | 39 ----------
superset-frontend/src/messageToasts/types.ts | 17 ++--
.../utils/getToastsFromPyFlashMessages.js | 6 +-
superset-frontend/src/types/Chart.ts | 8 ++
.../src/views/CRUD/chart/ChartList.tsx | 4 +-
19 files changed, 226 insertions(+), 265 deletions(-)
diff --git a/superset-frontend/spec/javascripts/dashboard/actions/dashboardLayout_spec.js b/superset-frontend/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
index 6f072fe..4a98667 100644
--- a/superset-frontend/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
+++ b/superset-frontend/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
@@ -40,7 +40,7 @@ import {
import { setUnsavedChanges } from 'src/dashboard/actions/dashboardState';
import * as dashboardFilters from 'src/dashboard/actions/dashboardFilters';
-import { addWarningToast, ADD_TOAST } from 'src/messageToasts/actions';
+import { ADD_TOAST } from 'src/messageToasts/actions';
import {
DASHBOARD_GRID_TYPE,
@@ -349,24 +349,27 @@ describe('dashboardLayout actions', () => {
const { getState, dispatch } = setup({
dashboardLayout: {
present: {
- source: { type: ROW_TYPE },
- destination: { type: ROW_TYPE, children: ['rowChild'] },
- dragging: { type: CHART_TYPE, meta: { width: 1 } },
- rowChild: { type: CHART_TYPE, meta: { width: 12 } },
+ source: { id: 'source', type: ROW_TYPE, children: ['dragging'] },
+ destination: {
+ id: 'destination',
+ type: ROW_TYPE,
+ children: ['rowChild'],
+ },
+ dragging: { id: 'dragging', type: CHART_TYPE, meta: { width: 1 } },
+ rowChild: { id: 'rowChild', type: CHART_TYPE, meta: { width: 12 } },
},
},
});
const dropResult = {
source: { id: 'source', type: ROW_TYPE },
destination: { id: 'destination', type: ROW_TYPE },
- dragging: { id: 'dragging', type: CHART_TYPE },
+ dragging: { id: 'dragging', type: CHART_TYPE, meta: { width: 1 } },
};
const thunk = handleComponentDrop(dropResult);
thunk(dispatch, getState);
- expect(dispatch.getCall(0).args[0].type).toEqual(
- addWarningToast('').type,
- );
+
+ expect(dispatch.getCall(0).args[0].type).toEqual(ADD_TOAST);
expect(dispatch.callCount).toBe(1);
});
@@ -479,13 +482,9 @@ describe('dashboardLayout actions', () => {
},
};
- const thunk1 = handleComponentDrop(dropResult);
- thunk1(dispatch, getState);
-
- const thunk2 = dispatch.getCall(0).args[0];
- thunk2(dispatch, getState);
+ handleComponentDrop(dropResult)(dispatch, getState);
- expect(dispatch.getCall(1).args[0].type).toEqual(ADD_TOAST);
+ expect(dispatch.getCall(0).args[0].type).toEqual(ADD_TOAST);
});
});
diff --git a/superset-frontend/spec/javascripts/messageToasts/mockMessageToasts.js b/superset-frontend/spec/javascripts/messageToasts/mockMessageToasts.js
index e009ba5..311e626 100644
--- a/superset-frontend/spec/javascripts/messageToasts/mockMessageToasts.js
+++ b/superset-frontend/spec/javascripts/messageToasts/mockMessageToasts.js
@@ -16,9 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { INFO_TOAST, DANGER_TOAST } from 'src/messageToasts/constants';
+import { ToastType } from 'src/messageToasts/constants';
export default [
- { id: 'info_id', toastType: INFO_TOAST, text: 'info toast' },
- { id: 'danger_id', toastType: DANGER_TOAST, text: 'danger toast' },
+ { id: 'info_id', toastType: ToastType.INFO, text: 'info toast' },
+ { id: 'danger_id', toastType: ToastType.DANGER, text: 'danger toast' },
];
diff --git a/superset-frontend/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js b/superset-frontend/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js
index edabc0a..59f8a45 100644
--- a/superset-frontend/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js
+++ b/superset-frontend/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js
@@ -16,18 +16,17 @@
* specific language governing permissions and limitations
* under the License.
*/
-import {
- DANGER_TOAST,
- INFO_TOAST,
- SUCCESS_TOAST,
-} from 'src/messageToasts/constants';
+import { ToastType } from 'src/messageToasts/constants';
import getToastsFromPyFlashMessages from 'src/messageToasts/utils/getToastsFromPyFlashMessages';
describe('getToastsFromPyFlashMessages', () => {
it('should return an info toast', () => {
const toast = getToastsFromPyFlashMessages([['info', 'info test']])[0];
- expect(toast).toMatchObject({ toastType: INFO_TOAST, text: 'info test' });
+ expect(toast).toMatchObject({
+ toastType: ToastType.INFO,
+ text: 'info test',
+ });
});
it('should return a success toast', () => {
@@ -35,7 +34,7 @@ describe('getToastsFromPyFlashMessages', () => {
['success', 'success test'],
])[0];
expect(toast).toMatchObject({
- toastType: SUCCESS_TOAST,
+ toastType: ToastType.SUCCESS,
text: 'success test',
});
});
@@ -43,7 +42,7 @@ describe('getToastsFromPyFlashMessages', () => {
it('should return a danger toast', () => {
const toast = getToastsFromPyFlashMessages([['danger', 'danger test']])[0];
expect(toast).toMatchObject({
- toastType: DANGER_TOAST,
+ toastType: ToastType.DANGER,
text: 'danger test',
});
});
diff --git a/superset-frontend/src/chart/Chart.jsx b/superset-frontend/src/chart/Chart.jsx
index 41f2785..1d386ec 100644
--- a/superset-frontend/src/chart/Chart.jsx
+++ b/superset-frontend/src/chart/Chart.jsx
@@ -49,7 +49,9 @@ const propTypes = {
timeout: PropTypes.number,
vizType: PropTypes.string.isRequired,
triggerRender: PropTypes.bool,
- owners: PropTypes.arrayOf(PropTypes.string),
+ owners: PropTypes.arrayOf(
+ PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ ),
// state
chartAlert: PropTypes.string,
chartStatus: PropTypes.string,
diff --git a/superset-frontend/src/datasource/DatasourceEditor.jsx b/superset-frontend/src/datasource/DatasourceEditor.jsx
index 01684b1..3cfba94 100644
--- a/superset-frontend/src/datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/datasource/DatasourceEditor.jsx
@@ -618,6 +618,7 @@ class DatasourceEditor extends React.PureComponent {
<div className="m-l-10 m-t-20 m-b-10">
{DATASOURCE_TYPES_ARR.map(type => (
<Radio
+ key={type.key}
value={type.key}
inline
onChange={this.onDatasourceTypeChange.bind(this, type.key)}
diff --git a/superset-frontend/src/explore/actions/exploreActions.js b/superset-frontend/src/explore/actions/exploreActions.ts
similarity index 67%
rename from superset-frontend/src/explore/actions/exploreActions.js
rename to superset-frontend/src/explore/actions/exploreActions.ts
index 56c112b..af71199 100644
--- a/superset-frontend/src/explore/actions/exploreActions.js
+++ b/superset-frontend/src/explore/actions/exploreActions.ts
@@ -17,23 +17,31 @@
* under the License.
*/
/* eslint camelcase: 0 */
-import { t, SupersetClient } from '@superset-ui/core';
-import { addDangerToast } from '../../messageToasts/actions';
+import { DatasourceMeta } from '@superset-ui/chart-controls';
+import {
+ t,
+ SupersetClient,
+ DatasourceType,
+ QueryFormData,
+} from '@superset-ui/core';
+import { Dispatch } from 'redux';
+import { addDangerToast } from 'src/messageToasts/actions';
+import { Slice } from 'src/types/Chart';
const FAVESTAR_BASE_URL = '/superset/favstar/slice';
export const SET_DATASOURCE_TYPE = 'SET_DATASOURCE_TYPE';
-export function setDatasourceType(datasourceType) {
+export function setDatasourceType(datasourceType: DatasourceType) {
return { type: SET_DATASOURCE_TYPE, datasourceType };
}
export const SET_DATASOURCE = 'SET_DATASOURCE';
-export function setDatasource(datasource) {
+export function setDatasource(datasource: DatasourceMeta) {
return { type: SET_DATASOURCE, datasource };
}
export const SET_DATASOURCES = 'SET_DATASOURCES';
-export function setDatasources(datasources) {
+export function setDatasources(datasources: DatasourceMeta[]) {
return { type: SET_DATASOURCES, datasources };
}
@@ -53,29 +61,19 @@ export function fetchDatasourcesSucceeded() {
return { type: FETCH_DATASOURCES_SUCCEEDED };
}
-export const FETCH_DATASOURCES_FAILED = 'FETCH_DATASOURCES_FAILED';
-export function fetchDatasourcesFailed(error) {
- return { type: FETCH_DATASOURCES_FAILED, error };
-}
-
-export const POST_DATASOURCES_FAILED = 'POST_DATASOURCES_FAILED';
-export function postDatasourcesFailed(error) {
- return { type: POST_DATASOURCES_FAILED, error };
-}
-
export const RESET_FIELDS = 'RESET_FIELDS';
export function resetControls() {
return { type: RESET_FIELDS };
}
export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
-export function toggleFaveStar(isStarred) {
+export function toggleFaveStar(isStarred: boolean) {
return { type: TOGGLE_FAVE_STAR, isStarred };
}
export const FETCH_FAVE_STAR = 'FETCH_FAVE_STAR';
-export function fetchFaveStar(sliceId) {
- return function (dispatch) {
+export function fetchFaveStar(sliceId: string) {
+ return function (dispatch: Dispatch<ReturnType<typeof toggleFaveStar>>) {
SupersetClient.get({
endpoint: `${FAVESTAR_BASE_URL}/${sliceId}/count`,
}).then(({ json }) => {
@@ -87,33 +85,32 @@ export function fetchFaveStar(sliceId) {
}
export const SAVE_FAVE_STAR = 'SAVE_FAVE_STAR';
-export function saveFaveStar(sliceId, isStarred) {
- return function (dispatch) {
+export function saveFaveStar(sliceId: string, isStarred: boolean) {
+ return function (dispatch: Dispatch<ReturnType<typeof addDangerToast>>) {
const urlSuffix = isStarred ? 'unselect' : 'select';
SupersetClient.get({
endpoint: `${FAVESTAR_BASE_URL}/${sliceId}/${urlSuffix}/`,
})
.then(() => dispatch(toggleFaveStar(!isStarred)))
- .catch(() =>
+ .catch(() => {
dispatch(
addDangerToast(t('An error occurred while starring this chart')),
- ),
- );
+ );
+ });
};
}
export const SET_FIELD_VALUE = 'SET_FIELD_VALUE';
-export function setControlValue(controlName, value, validationErrors) {
+export function setControlValue(
+ controlName: string,
+ value: any,
+ validationErrors: any[],
+) {
return { type: SET_FIELD_VALUE, controlName, value, validationErrors };
}
-export const UPDATE_EXPLORE_ENDPOINTS = 'UPDATE_EXPLORE_ENDPOINTS';
-export function updateExploreEndpoints(jsonUrl, csvUrl, standaloneUrl) {
- return { type: UPDATE_EXPLORE_ENDPOINTS, jsonUrl, csvUrl, standaloneUrl };
-}
-
export const SET_EXPLORE_CONTROLS = 'UPDATE_EXPLORE_CONTROLS';
-export function setExploreControls(formData) {
+export function setExploreControls(formData: QueryFormData) {
return { type: SET_EXPLORE_CONTROLS, formData };
}
@@ -123,17 +120,17 @@ export function removeControlPanelAlert() {
}
export const UPDATE_CHART_TITLE = 'UPDATE_CHART_TITLE';
-export function updateChartTitle(sliceName) {
+export function updateChartTitle(sliceName: string) {
return { type: UPDATE_CHART_TITLE, sliceName };
}
export const CREATE_NEW_SLICE = 'CREATE_NEW_SLICE';
export function createNewSlice(
- can_add,
- can_download,
- can_overwrite,
- slice,
- form_data,
+ can_add: boolean,
+ can_download: boolean,
+ can_overwrite: boolean,
+ slice: Slice,
+ form_data: QueryFormData,
) {
return {
type: CREATE_NEW_SLICE,
@@ -146,6 +143,26 @@ export function createNewSlice(
}
export const SLICE_UPDATED = 'SLICE_UPDATED';
-export function sliceUpdated(slice) {
+export function sliceUpdated(slice: Slice) {
return { type: SLICE_UPDATED, slice };
}
+
+export const exploreActions = {
+ setDatasourceType,
+ setDatasource,
+ setDatasources,
+ fetchDatasourcesStarted,
+ fetchDatasourcesSucceeded,
+ resetControls,
+ toggleFaveStar,
+ fetchFaveStar,
+ saveFaveStar,
+ setControlValue,
+ setExploreControls,
+ removeControlPanelAlert,
+ updateChartTitle,
+ createNewSlice,
+ sliceUpdated,
+};
+
+export type ExploreActions = typeof exploreActions;
diff --git a/superset-frontend/src/explore/components/Control.jsx b/superset-frontend/src/explore/components/Control.tsx
similarity index 60%
rename from superset-frontend/src/explore/components/Control.jsx
rename to superset-frontend/src/explore/components/Control.tsx
index 68f0e65..8fc6583 100644
--- a/superset-frontend/src/explore/components/Control.jsx
+++ b/superset-frontend/src/explore/components/Control.tsx
@@ -16,52 +16,41 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import './Control.less';
+import React, { ReactNode } from 'react';
+import { ControlType } from '@superset-ui/chart-controls';
+import { JsonValue, QueryFormData } from '@superset-ui/core';
+import { ExploreActions } from '../actions/exploreActions';
import controlMap from './controls';
-const controlTypes = Object.keys(controlMap);
+import './Control.less';
-const propTypes = {
- actions: PropTypes.object.isRequired,
- name: PropTypes.string.isRequired,
- type: PropTypes.oneOfType([
- PropTypes.oneOf(controlTypes).isRequired,
- PropTypes.func.isRequired,
- ]),
- hidden: PropTypes.bool,
- label: PropTypes.string.isRequired,
- choices: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.array),
- PropTypes.func,
- ]),
- description: PropTypes.string,
- tooltipOnClick: PropTypes.func,
- places: PropTypes.number,
- validationErrors: PropTypes.array,
- renderTrigger: PropTypes.bool,
- rightNode: PropTypes.node,
- formData: PropTypes.object,
- value: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number,
- PropTypes.object,
- PropTypes.bool,
- PropTypes.array,
- PropTypes.func,
- ]),
+export type ControlProps = {
+ // the actual action dispatcher (via bindActionCreators) has identical
+ // signature to the original action factory.
+ actions: ExploreActions;
+ type: ControlType;
+ label: string;
+ name: string;
+ description?: string;
+ tooltipOnClick?: () => ReactNode;
+ places?: number;
+ rightNode?: ReactNode;
+ formData?: QueryFormData | null;
+ value?: JsonValue;
+ validationErrors?: any[];
+ hidden?: boolean;
+ renderTrigger?: boolean;
};
-const defaultProps = {
- renderTrigger: false,
- hidden: false,
- validationErrors: [],
-};
+export default class Control extends React.PureComponent<
+ ControlProps,
+ { hovered: boolean }
+> {
+ onMouseEnter: () => void;
+
+ onMouseLeave: () => void;
-export default class Control extends React.PureComponent {
- constructor(props) {
+ constructor(props: ControlProps) {
super(props);
this.state = { hovered: false };
this.onChange = this.onChange.bind(this);
@@ -69,11 +58,11 @@ export default class Control extends React.PureComponent {
this.onMouseLeave = this.setHover.bind(this, false);
}
- onChange(value, errors) {
+ onChange(value: any, errors: any[]) {
this.props.actions.setControlValue(this.props.name, value, errors);
}
- setHover(hovered) {
+ setHover(hovered: boolean) {
this.setState({ hovered });
}
@@ -98,6 +87,3 @@ export default class Control extends React.PureComponent {
);
}
}
-
-Control.propTypes = propTypes;
-Control.defaultProps = defaultProps;
diff --git a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
index e79e71a..e834c6d 100644
--- a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
+++ b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
@@ -28,7 +28,7 @@ import ControlPanelSection from './ControlPanelSection';
import ControlRow from './ControlRow';
import Control from './Control';
import { sectionsToRender } from '../controlUtils';
-import * as exploreActions from '../actions/exploreActions';
+import { exploreActions } from '../actions/exploreActions';
const propTypes = {
actions: PropTypes.object.isRequired,
@@ -106,8 +106,8 @@ class ControlPanelsContainer extends React.Component {
return (
<Control
- name={name}
key={`control-${name}`}
+ name={name}
validationErrors={validationErrors}
actions={actions}
formData={provideFormDataToProps ? formData : null}
diff --git a/superset-frontend/src/explore/components/PropertiesModal.tsx b/superset-frontend/src/explore/components/PropertiesModal.tsx
index 3c83e0b..12e006d 100644
--- a/superset-frontend/src/explore/components/PropertiesModal.tsx
+++ b/superset-frontend/src/explore/components/PropertiesModal.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState, useEffect, useRef, useCallback } from 'react';
import {
Modal,
Row,
@@ -31,18 +31,10 @@ import { OptionsType } from 'react-select/src/types';
import { AsyncSelect } from 'src/components/Select';
import rison from 'rison';
import { t, SupersetClient } from '@superset-ui/core';
-import Chart from 'src/types/Chart';
+import Chart, { Slice } from 'src/types/Chart';
import FormLabel from 'src/components/FormLabel';
import getClientErrorObject from '../../utils/getClientErrorObject';
-export type Slice = {
- id?: number;
- slice_id: number;
- slice_name: string;
- description: string | null;
- cache_timeout: number | null;
-};
-
type InternalProps = {
slice: Slice;
onHide: () => void;
@@ -81,28 +73,31 @@ function PropertiesModal({ slice, onHide, onSave }: InternalProps) {
});
}
- async function fetchChartData() {
- try {
- const response = await SupersetClient.get({
- endpoint: `/api/v1/chart/${slice.slice_id}`,
- });
- const chart = response.json.result;
- setOwners(
- chart.owners.map((owner: any) => ({
- value: owner.id,
- label: `${owner.first_name} ${owner.last_name}`,
- })),
- );
- } catch (response) {
- const clientError = await getClientErrorObject(response);
- showError(clientError);
- }
- }
+ const fetchChartData = useCallback(
+ async function fetchChartData() {
+ try {
+ const response = await SupersetClient.get({
+ endpoint: `/api/v1/chart/${slice.slice_id}`,
+ });
+ const chart = response.json.result;
+ setOwners(
+ chart.owners.map((owner: any) => ({
+ value: owner.id,
+ label: `${owner.first_name} ${owner.last_name}`,
+ })),
+ );
+ } catch (response) {
+ const clientError = await getClientErrorObject(response);
+ showError(clientError);
+ }
+ },
+ [slice.slice_id],
+ );
// get the owners of this slice
useEffect(() => {
fetchChartData();
- }, []);
+ }, [fetchChartData]);
const loadOptions = (input = '') => {
const query = rison.encode({
diff --git a/superset-frontend/src/explore/reducers/exploreReducer.js b/superset-frontend/src/explore/reducers/exploreReducer.js
index 83cd62e..36d7908 100644
--- a/superset-frontend/src/explore/reducers/exploreReducer.js
+++ b/superset-frontend/src/explore/reducers/exploreReducer.js
@@ -72,19 +72,6 @@ export default function exploreReducer(state = {}, action) {
isDatasourcesLoading: true,
};
},
- [actions.FETCH_DATASOURCES_SUCCEEDED]() {
- return {
- ...state,
- isDatasourcesLoading: false,
- };
- },
- [actions.FETCH_DATASOURCES_FAILED]() {
- return {
- ...state,
- isDatasourcesLoading: false,
- controlPanelAlert: action.error,
- };
- },
[actions.SET_DATASOURCES]() {
return {
...state,
diff --git a/superset-frontend/src/messageToasts/actions/index.js b/superset-frontend/src/messageToasts/actions/index.ts
similarity index 64%
rename from superset-frontend/src/messageToasts/actions/index.js
rename to superset-frontend/src/messageToasts/actions/index.ts
index bfed047..4eeb2e1 100644
--- a/superset-frontend/src/messageToasts/actions/index.js
+++ b/superset-frontend/src/messageToasts/actions/index.ts
@@ -17,20 +17,18 @@
* under the License.
*/
import shortid from 'shortid';
+import { ToastType, ToastMeta } from '../types';
-import {
- INFO_TOAST,
- SUCCESS_TOAST,
- WARNING_TOAST,
- DANGER_TOAST,
-} from '../constants';
-
-export function getToastUuid(type) {
+export function getToastUuid(type: ToastType) {
return `${type}-${shortid.generate()}`;
}
export const ADD_TOAST = 'ADD_TOAST';
-export function addToast({ toastType, text, duration = 8000 }) {
+export function addToast({
+ toastType,
+ text,
+ duration = 8000,
+}: Omit<ToastMeta, 'id'>) {
return {
type: ADD_TOAST,
payload: {
@@ -43,7 +41,7 @@ export function addToast({ toastType, text, duration = 8000 }) {
}
export const REMOVE_TOAST = 'REMOVE_TOAST';
-export function removeToast(id) {
+export function removeToast(id: string) {
return {
type: REMOVE_TOAST,
payload: {
@@ -54,25 +52,21 @@ export function removeToast(id) {
// Different types of toasts
export const ADD_INFO_TOAST = 'ADD_INFO_TOAST';
-export function addInfoToast(text) {
- return dispatch =>
- dispatch(addToast({ text, toastType: INFO_TOAST, duration: 4000 }));
+export function addInfoToast(text: string) {
+ return addToast({ text, toastType: ToastType.INFO, duration: 4000 });
}
export const ADD_SUCCESS_TOAST = 'ADD_SUCCESS_TOAST';
-export function addSuccessToast(text) {
- return dispatch =>
- dispatch(addToast({ text, toastType: SUCCESS_TOAST, duration: 4000 }));
+export function addSuccessToast(text: string) {
+ return addToast({ text, toastType: ToastType.SUCCESS, duration: 4000 });
}
export const ADD_WARNING_TOAST = 'ADD_WARNING_TOAST';
-export function addWarningToast(text) {
- return dispatch =>
- dispatch(addToast({ text, toastType: WARNING_TOAST, duration: 6000 }));
+export function addWarningToast(text: string) {
+ return addToast({ text, toastType: ToastType.WARNING, duration: 6000 });
}
export const ADD_DANGER_TOAST = 'ADD_DANGER_TOAST';
-export function addDangerToast(text) {
- return dispatch =>
- dispatch(addToast({ text, toastType: DANGER_TOAST, duration: 8000 }));
+export function addDangerToast(text: string) {
+ return addToast({ text, toastType: ToastType.DANGER, duration: 8000 });
}
diff --git a/superset-frontend/src/messageToasts/components/Toast.tsx b/superset-frontend/src/messageToasts/components/Toast.tsx
index 5b06fa6..f9b9bf7 100644
--- a/superset-frontend/src/messageToasts/components/Toast.tsx
+++ b/superset-frontend/src/messageToasts/components/Toast.tsx
@@ -20,11 +20,10 @@ import { Alert } from 'react-bootstrap';
import { styled } from '@superset-ui/core';
import cx from 'classnames';
import Interweave from 'interweave';
-import React, { useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
import Icon from 'src/components/Icon';
-import { ToastType } from 'src/messageToasts/types';
-
-import { SUCCESS_TOAST, WARNING_TOAST, DANGER_TOAST } from '../constants';
+import { ToastType } from 'src/messageToasts/constants';
+import { ToastMeta } from '../types';
const ToastContianer = styled.div`
display: flex;
@@ -37,19 +36,21 @@ const ToastContianer = styled.div`
`;
interface ToastPresenterProps {
- toast: { id: string; toastType: ToastType; text: string; duration: number };
+ toast: ToastMeta;
onCloseToast: (id: string) => void;
}
export default function Toast({ toast, onCloseToast }: ToastPresenterProps) {
- let hideTimer: ReturnType<typeof setTimeout>;
+ const hideTimer = useRef<ReturnType<typeof setTimeout>>();
const [visible, setVisible] = useState(false);
const showToast = () => {
setVisible(true);
};
- const handleClosePress = () => {
- clearTimeout(hideTimer);
+ const handleClosePress = useCallback(() => {
+ if (hideTimer.current) {
+ clearTimeout(hideTimer.current);
+ }
// Wait for the transition
setVisible(() => {
setTimeout(() => {
@@ -57,18 +58,20 @@ export default function Toast({ toast, onCloseToast }: ToastPresenterProps) {
}, 150);
return false;
});
- };
+ }, [onCloseToast, toast.id]);
useEffect(() => {
setTimeout(showToast);
if (toast.duration > 0) {
- hideTimer = setTimeout(handleClosePress, toast.duration);
+ hideTimer.current = setTimeout(handleClosePress, toast.duration);
}
return () => {
- clearTimeout(hideTimer);
+ if (hideTimer.current) {
+ clearTimeout(hideTimer.current);
+ }
};
- }, []);
+ }, [handleClosePress, toast.duration]);
return (
<Alert
@@ -77,17 +80,17 @@ export default function Toast({ toast, onCloseToast }: ToastPresenterProps) {
'alert',
'toast',
visible && 'toast--visible',
- toast.toastType === SUCCESS_TOAST && 'toast--success',
- toast.toastType === WARNING_TOAST && 'toast--warning',
- toast.toastType === DANGER_TOAST && 'toast--danger',
+ toast.toastType === ToastType.SUCCESS && 'toast--success',
+ toast.toastType === ToastType.WARNING && 'toast--warning',
+ toast.toastType === ToastType.DANGER && 'toast--danger',
)}
>
<ToastContianer>
- {toast.toastType === SUCCESS_TOAST && (
+ {toast.toastType === ToastType.SUCCESS && (
<Icon name="circle-check-solid" />
)}
- {toast.toastType === WARNING_TOAST ||
- (toast.toastType === DANGER_TOAST && <Icon name="error-solid" />)}
+ {toast.toastType === ToastType.WARNING ||
+ (toast.toastType === ToastType.DANGER && <Icon name="error-solid" />)}
<Interweave content={toast.text} />
</ToastContianer>
</Alert>
diff --git a/superset-frontend/src/messageToasts/components/ToastPresenter.tsx b/superset-frontend/src/messageToasts/components/ToastPresenter.tsx
index 4ce4898..aba922c 100644
--- a/superset-frontend/src/messageToasts/components/ToastPresenter.tsx
+++ b/superset-frontend/src/messageToasts/components/ToastPresenter.tsx
@@ -18,7 +18,7 @@
*/
import React from 'react';
import { styled } from '@superset-ui/core';
-import { ToastType } from 'src/messageToasts/types';
+import { ToastMeta } from 'src/messageToasts/types';
import Toast from './Toast';
const StyledToastPresenter = styled.div`
@@ -67,25 +67,22 @@ const StyledToastPresenter = styled.div`
}
`;
-type ToastShape = {
- id: string;
- toastType: ToastType;
- text: string;
- duration: number;
-};
-
-interface ToastPresenterProps {
- toasts: Array<ToastShape>;
+type ToastPresenterProps = {
+ toasts: Array<ToastMeta>;
removeToast: () => void;
-}
+};
-const ToastPresenter = ({ toasts, removeToast }: ToastPresenterProps) =>
- toasts.length > 0 && (
- <StyledToastPresenter id="toast-presenter">
- {toasts.map(toast => (
- <Toast key={toast.id} toast={toast} onCloseToast={removeToast} />
- ))}
- </StyledToastPresenter>
+export default function ToastPresenter({
+ toasts,
+ removeToast,
+}: ToastPresenterProps) {
+ return (
+ toasts.length > 0 && (
+ <StyledToastPresenter id="toast-presenter">
+ {toasts.map(toast => (
+ <Toast key={toast.id} toast={toast} onCloseToast={removeToast} />
+ ))}
+ </StyledToastPresenter>
+ )
);
-
-export default ToastPresenter;
+}
diff --git a/superset-frontend/src/messageToasts/constants.js b/superset-frontend/src/messageToasts/constants.ts
similarity index 74%
rename from superset-frontend/src/messageToasts/constants.js
rename to superset-frontend/src/messageToasts/constants.ts
index 91f41c4..e22616c 100644
--- a/superset-frontend/src/messageToasts/constants.js
+++ b/superset-frontend/src/messageToasts/constants.ts
@@ -17,7 +17,12 @@
* under the License.
*/
// Toast types
-export const INFO_TOAST = 'INFO_TOAST';
-export const SUCCESS_TOAST = 'SUCCESS_TOAST';
-export const WARNING_TOAST = 'WARNING_TOAST';
-export const DANGER_TOAST = 'DANGER_TOAST';
+import { ToastType } from './types';
+
+export { ToastType } from './types';
+
+// for backward compatibility
+export const INFO_TOAST = ToastType.INFO;
+export const SUCCES_TOAST = ToastType.SUCCESS;
+export const WARNING_TOAST = ToastType.WARNING;
+export const DANGER_TOAST = ToastType.DANGER;
diff --git a/superset-frontend/src/messageToasts/propShapes.js b/superset-frontend/src/messageToasts/propShapes.js
deleted file mode 100644
index 0ce2aea..0000000
--- a/superset-frontend/src/messageToasts/propShapes.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import PropTypes from 'prop-types';
-
-import {
- INFO_TOAST,
- SUCCESS_TOAST,
- WARNING_TOAST,
- DANGER_TOAST,
-} from './constants';
-
-// eslint-disable-next-line import/prefer-default-export
-export const toastShape = PropTypes.shape({
- id: PropTypes.string.isRequired,
- toastType: PropTypes.oneOf([
- INFO_TOAST,
- SUCCESS_TOAST,
- WARNING_TOAST,
- DANGER_TOAST,
- ]).isRequired,
- text: PropTypes.string.isRequired,
- duration: PropTypes.number,
-});
diff --git a/superset-frontend/src/messageToasts/types.ts b/superset-frontend/src/messageToasts/types.ts
index 471a3a0..0a72ec3 100644
--- a/superset-frontend/src/messageToasts/types.ts
+++ b/superset-frontend/src/messageToasts/types.ts
@@ -16,9 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
+export enum ToastType {
+ INFO = 'INFO_TOAST',
+ SUCCESS = 'SUCCESS_TOAST',
+ WARNING = 'WARNING_TOAST',
+ DANGER = 'DANGER_TOAST',
+}
-export type ToastType =
- | 'INFO_TOAST'
- | 'SUCCESS_TOAST'
- | 'WARNING_TOAST'
- | 'DANGER_TOAST';
+export interface ToastMeta {
+ id: string;
+ toastType: ToastType;
+ text: string;
+ duration: number;
+}
diff --git a/superset-frontend/src/messageToasts/utils/getToastsFromPyFlashMessages.js b/superset-frontend/src/messageToasts/utils/getToastsFromPyFlashMessages.js
index 26fe7b7..6f921c4 100644
--- a/superset-frontend/src/messageToasts/utils/getToastsFromPyFlashMessages.js
+++ b/superset-frontend/src/messageToasts/utils/getToastsFromPyFlashMessages.js
@@ -17,7 +17,7 @@
* under the License.
*/
import { addToast } from '../actions';
-import { INFO_TOAST, SUCCESS_TOAST, DANGER_TOAST } from '../constants';
+import { ToastType } from '../constants';
export default function toastsFromPyFlashMessages(flashMessages = []) {
const toasts = [];
@@ -25,8 +25,8 @@ export default function toastsFromPyFlashMessages(flashMessages = []) {
flashMessages.forEach(([messageType, message]) => {
const toastType =
messageType === 'danger'
- ? DANGER_TOAST
- : (messageType === 'success' && SUCCESS_TOAST) || INFO_TOAST;
+ ? ToastType.DANGER
+ : (messageType === 'success' && ToastType.SUCCESS) || ToastType.INFO;
const toast = addToast({
text: message,
diff --git a/superset-frontend/src/types/Chart.ts b/superset-frontend/src/types/Chart.ts
index e78d810..5148d32 100644
--- a/superset-frontend/src/types/Chart.ts
+++ b/superset-frontend/src/types/Chart.ts
@@ -37,3 +37,11 @@ export default interface Chart {
owners?: Owner[];
datasource_name_text?: string;
}
+
+export type Slice = {
+ id?: number;
+ slice_id: number;
+ slice_name: string;
+ description: string | null;
+ cache_timeout: number | null;
+};
diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx
index 4323f46..a22c54d 100644
--- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx
+++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx
@@ -34,8 +34,8 @@ import ListView, {
SelectOption,
} from 'src/components/ListView';
import withToasts from 'src/messageToasts/enhancers/withToasts';
-import PropertiesModal, { Slice } from 'src/explore/components/PropertiesModal';
-import Chart from 'src/types/Chart';
+import PropertiesModal from 'src/explore/components/PropertiesModal';
+import Chart, { Slice } from 'src/types/Chart';
import ListViewCard from 'src/components/ListViewCard';
import Label from 'src/components/Label';
import { Dropdown, Menu } from 'src/common/components';