mirror of
https://github.com/apache/superset.git
synced 2024-09-06 22:07:34 -04:00
refactor: typing for explore Control and messageToasts (#11416)
This commit is contained in:
parent
d02a61f21c
commit
8aecffd83b
@ -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);
|
||||
handleComponentDrop(dropResult)(dispatch, getState);
|
||||
|
||||
const thunk2 = dispatch.getCall(0).args[0];
|
||||
thunk2(dispatch, getState);
|
||||
|
||||
expect(dispatch.getCall(1).args[0].type).toEqual(ADD_TOAST);
|
||||
expect(dispatch.getCall(0).args[0].type).toEqual(ADD_TOAST);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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' },
|
||||
];
|
||||
|
@ -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',
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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)}
|
||||
|
@ -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;
|
@ -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;
|
||||
|
||||
export default class Control extends React.PureComponent {
|
||||
constructor(props) {
|
||||
onMouseLeave: () => void;
|
||||
|
||||
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;
|
@ -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}
|
||||
|
@ -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({
|
||||
|
@ -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,
|
||||
|
@ -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 });
|
||||
}
|
@ -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>
|
||||
|
@ -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;
|
||||
type ToastPresenterProps = {
|
||||
toasts: Array<ToastMeta>;
|
||||
removeToast: () => void;
|
||||
};
|
||||
|
||||
interface ToastPresenterProps {
|
||||
toasts: Array<ToastShape>;
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
@ -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,
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user