blob: fc31a8501abcdd4c15a88b2511c239327b95958b [file] [log] [blame]
/**
* 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.
*/
/* eslint camelcase: 0 */
import { ActionCreators as UndoActionCreators } from 'redux-undo';
import { t, SupersetClient } from '@superset-ui/core';
import { addChart, removeChart, refreshChart } from '../../chart/chartAction';
import { chart as initChart } from '../../chart/chartReducer';
import { fetchDatasourceMetadata } from './datasources';
import {
addFilter,
removeFilter,
updateDirectPathToFilter,
} from './dashboardFilters';
import { applyDefaultFormData } from '../../explore/store';
import { getClientErrorObject } from '../../utils/getClientErrorObject';
import { SAVE_TYPE_OVERWRITE } from '../util/constants';
import {
addSuccessToast,
addWarningToast,
addDangerToast,
} from '../../messageToasts/actions';
import { UPDATE_COMPONENTS_PARENTS_LIST } from './dashboardLayout';
import serializeActiveFilterValues from '../util/serializeActiveFilterValues';
import serializeFilterScopes from '../util/serializeFilterScopes';
import { getActiveFilters } from '../util/activeDashboardFilters';
import { safeStringify } from '../../utils/safeStringify';
export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
export function setUnsavedChanges(hasUnsavedChanges) {
return { type: SET_UNSAVED_CHANGES, payload: { hasUnsavedChanges } };
}
export const ADD_SLICE = 'ADD_SLICE';
export function addSlice(slice) {
return { type: ADD_SLICE, slice };
}
export const REMOVE_SLICE = 'REMOVE_SLICE';
export function removeSlice(sliceId) {
return { type: REMOVE_SLICE, sliceId };
}
const FAVESTAR_BASE_URL = '/superset/favstar/Dashboard';
export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
export function toggleFaveStar(isStarred) {
return { type: TOGGLE_FAVE_STAR, isStarred };
}
export const FETCH_FAVE_STAR = 'FETCH_FAVE_STAR';
export function fetchFaveStar(id) {
return function fetchFaveStarThunk(dispatch) {
return SupersetClient.get({
endpoint: `${FAVESTAR_BASE_URL}/${id}/count/`,
})
.then(({ json }) => {
if (json.count > 0) dispatch(toggleFaveStar(true));
})
.catch(() =>
dispatch(
addDangerToast(
t(
'There was an issue fetching the favorite status of this dashboard.',
),
),
),
);
};
}
export const SAVE_FAVE_STAR = 'SAVE_FAVE_STAR';
export function saveFaveStar(id, isStarred) {
return function saveFaveStarThunk(dispatch) {
const urlSuffix = isStarred ? 'unselect' : 'select';
return SupersetClient.get({
endpoint: `${FAVESTAR_BASE_URL}/${id}/${urlSuffix}/`,
})
.then(() => {
dispatch(toggleFaveStar(!isStarred));
})
.catch(() =>
dispatch(
addDangerToast(t('There was an issue favoriting this dashboard.')),
),
);
};
}
export const TOGGLE_PUBLISHED = 'TOGGLE_PUBLISHED';
export function togglePublished(isPublished) {
return { type: TOGGLE_PUBLISHED, isPublished };
}
export function savePublished(id, isPublished) {
return function savePublishedThunk(dispatch) {
return SupersetClient.put({
endpoint: `/api/v1/dashboard/${id}`,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
published: isPublished,
}),
})
.then(() => {
const nowPublished = isPublished ? 'published' : 'hidden';
dispatch(addSuccessToast(t(`This dashboard is now ${nowPublished}`)));
dispatch(togglePublished(isPublished));
})
.catch(() => {
dispatch(
addDangerToast(
t('You do not have permissions to edit this dashboard.'),
),
);
});
};
}
export const TOGGLE_EXPAND_SLICE = 'TOGGLE_EXPAND_SLICE';
export function toggleExpandSlice(sliceId) {
return { type: TOGGLE_EXPAND_SLICE, sliceId };
}
export const UPDATE_CSS = 'UPDATE_CSS';
export function updateCss(css) {
return { type: UPDATE_CSS, css };
}
export const SET_EDIT_MODE = 'SET_EDIT_MODE';
export function setEditMode(editMode) {
return { type: SET_EDIT_MODE, editMode };
}
export const ON_CHANGE = 'ON_CHANGE';
export function onChange() {
return { type: ON_CHANGE };
}
export const ON_SAVE = 'ON_SAVE';
export function onSave(lastModifiedTime) {
return { type: ON_SAVE, lastModifiedTime };
}
export const SET_REFRESH_FREQUENCY = 'SET_REFRESH_FREQUENCY';
export function setRefreshFrequency(refreshFrequency, isPersistent = false) {
return { type: SET_REFRESH_FREQUENCY, refreshFrequency, isPersistent };
}
export function saveDashboardRequestSuccess(lastModifiedTime) {
return dispatch => {
dispatch(onSave(lastModifiedTime));
// clear layout undo history
dispatch(UndoActionCreators.clearHistory());
};
}
export function saveDashboardRequest(data, id, saveType) {
const path = saveType === SAVE_TYPE_OVERWRITE ? 'save_dash' : 'copy_dash';
return (dispatch, getState) => {
dispatch({ type: UPDATE_COMPONENTS_PARENTS_LIST });
const { dashboardFilters, dashboardLayout } = getState();
const layout = dashboardLayout.present;
Object.values(dashboardFilters).forEach(filter => {
const { chartId } = filter;
const componentId = filter.directPathToFilter.slice().pop();
const directPathToFilter = (layout[componentId].parents || []).slice();
directPathToFilter.push(componentId);
dispatch(updateDirectPathToFilter(chartId, directPathToFilter));
});
// serialize selected values for each filter field, grouped by filter id
const serializedFilters = serializeActiveFilterValues(getActiveFilters());
// serialize filter scope for each filter field, grouped by filter id
const serializedFilterScopes = serializeFilterScopes(dashboardFilters);
return SupersetClient.post({
endpoint: `/superset/${path}/${id}/`,
postPayload: {
data: {
...data,
default_filters: safeStringify(serializedFilters),
filter_scopes: safeStringify(serializedFilterScopes),
},
},
})
.then(response => {
dispatch(saveDashboardRequestSuccess(response.json.last_modified_time));
dispatch(addSuccessToast(t('This dashboard was saved successfully.')));
return response;
})
.catch(response =>
getClientErrorObject(response).then(({ error }) =>
dispatch(
addDangerToast(
`${t(
'Sorry, there was an error saving this dashboard: ',
)} ${error}`,
),
),
),
);
};
}
export function fetchCharts(
chartList = [],
force = false,
interval = 0,
dashboardId,
) {
return (dispatch, getState) => {
if (!interval) {
chartList.forEach(chartKey =>
dispatch(refreshChart(chartKey, force, dashboardId)),
);
return;
}
const { metadata: meta } = getState().dashboardInfo;
const refreshTime = Math.max(interval, meta.stagger_time || 5000); // default 5 seconds
if (typeof meta.stagger_refresh !== 'boolean') {
meta.stagger_refresh =
meta.stagger_refresh === undefined
? true
: meta.stagger_refresh === 'true';
}
const delay = meta.stagger_refresh
? refreshTime / (chartList.length - 1)
: 0;
chartList.forEach((chartKey, i) => {
setTimeout(
() => dispatch(refreshChart(chartKey, force, dashboardId)),
delay * i,
);
});
};
}
export const SHOW_BUILDER_PANE = 'SHOW_BUILDER_PANE';
export function showBuilderPane() {
return { type: SHOW_BUILDER_PANE };
}
export function addSliceToDashboard(id, component) {
return (dispatch, getState) => {
const { sliceEntities } = getState();
const selectedSlice = sliceEntities.slices[id];
if (!selectedSlice) {
return dispatch(
addWarningToast(
'Sorry, there is no chart definition associated with the chart trying to be added.',
),
);
}
const form_data = {
...selectedSlice.form_data,
slice_id: selectedSlice.slice_id,
};
const newChart = {
...initChart,
id,
form_data,
formData: applyDefaultFormData(form_data),
};
return Promise.all([
dispatch(addChart(newChart, id)),
dispatch(fetchDatasourceMetadata(form_data.datasource)),
]).then(() => {
dispatch(addSlice(selectedSlice));
if (selectedSlice && selectedSlice.viz_type === 'filter_box') {
dispatch(addFilter(id, component, selectedSlice.form_data));
}
});
};
}
export function removeSliceFromDashboard(id) {
return (dispatch, getState) => {
const sliceEntity = getState().sliceEntities.slices[id];
if (sliceEntity && sliceEntity.viz_type === 'filter_box') {
dispatch(removeFilter(id));
}
dispatch(removeSlice(id));
dispatch(removeChart(id));
};
}
export const SET_COLOR_SCHEME = 'SET_COLOR_SCHEME';
export function setColorScheme(colorScheme) {
return { type: SET_COLOR_SCHEME, colorScheme };
}
export function setColorSchemeAndUnsavedChanges(colorScheme) {
return dispatch => {
dispatch(setColorScheme(colorScheme));
dispatch(setUnsavedChanges(true));
};
}
export const SET_DIRECT_PATH = 'SET_DIRECT_PATH';
export function setDirectPathToChild(path) {
return { type: SET_DIRECT_PATH, path };
}
export const SET_FOCUSED_FILTER_FIELD = 'SET_FOCUSED_FILTER_FIELD';
export function setFocusedFilterField(chartId, column) {
return { type: SET_FOCUSED_FILTER_FIELD, chartId, column };
}
export const UNSET_FOCUSED_FILTER_FIELD = 'UNSET_FOCUSED_FILTER_FIELD';
export function unsetFocusedFilterField(chartId, column) {
return { type: UNSET_FOCUSED_FILTER_FIELD, chartId, column };
}
// Undo history ---------------------------------------------------------------
export const SET_MAX_UNDO_HISTORY_EXCEEDED = 'SET_MAX_UNDO_HISTORY_EXCEEDED';
export function setMaxUndoHistoryExceeded(maxUndoHistoryExceeded = true) {
return {
type: SET_MAX_UNDO_HISTORY_EXCEEDED,
payload: { maxUndoHistoryExceeded },
};
}
export function maxUndoHistoryToast() {
return (dispatch, getState) => {
const { dashboardLayout } = getState();
const historyLength = dashboardLayout.past.length;
return dispatch(
addWarningToast(
`You have used all ${historyLength} undo slots and will not be able to fully undo subsequent actions. You may save your current state to reset the history.`,
),
);
};
}