rusackas commented on code in PR #37625: URL: https://github.com/apache/superset/pull/37625#discussion_r2759082973
########## superset-frontend/src/dashboard/actions/dashboardState.ts: ########## @@ -0,0 +1,1517 @@ +/** + * 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 rison from 'rison'; +import { + ensureIsArray, + isFeatureEnabled, + FeatureFlag, + getLabelsColorMap, + SupersetClient, + getClientErrorObject, + getCategoricalSchemeRegistry, + promiseTimeout, + JsonObject, +} from '@superset-ui/core'; +import { + addChart, + removeChart, + refreshChart, +} from 'src/components/Chart/chartAction'; +import { logging } from '@apache-superset/core'; +import { t } from '@apache-superset/core/ui'; +import { chart as initChart } from 'src/components/Chart/chartReducer'; +import { applyDefaultFormData } from 'src/explore/store'; +import { + SAVE_TYPE_OVERWRITE, + SAVE_TYPE_OVERWRITE_CONFIRMED, +} from 'src/dashboard/util/constants'; +import { + getCrossFiltersConfiguration, + isCrossFiltersEnabled, +} from 'src/dashboard/util/crossFilters'; +import { + addSuccessToast, + addWarningToast, + addDangerToast, +} from 'src/components/MessageToasts/actions'; +import serializeActiveFilterValues from 'src/dashboard/util/serializeActiveFilterValues'; +import serializeFilterScopes from 'src/dashboard/util/serializeFilterScopes'; +import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters'; +import { safeStringify } from 'src/utils/safeStringify'; +import { logEvent } from 'src/logger/actions'; +import { LOG_ACTIONS_CONFIRM_OVERWRITE_DASHBOARD_METADATA } from 'src/logger/LogUtils'; +import { isEqual } from 'lodash'; +import { navigateWithState, navigateTo } from 'src/utils/navigationUtils'; +import type { AnyAction } from 'redux'; +import type { ThunkDispatch } from 'redux-thunk'; +import { ResourceStatus } from 'src/hooks/apiResources/apiResources'; +import type { AgGridChartState } from '@superset-ui/core'; +import type { DashboardChartStates } from 'src/dashboard/types/chartState'; +import { UPDATE_COMPONENTS_PARENTS_LIST } from './dashboardLayout'; +import { + saveChartConfiguration, + dashboardInfoChanged, + SAVE_CHART_CONFIG_COMPLETE, +} from './dashboardInfo'; +import { fetchDatasourceMetadata, setDatasources } from './datasources'; +import { updateDirectPathToFilter } from './dashboardFilters'; +import { SET_IN_SCOPE_STATUS_OF_FILTERS } from './nativeFilters'; +import getOverwriteItems from '../util/getOverwriteItems'; +import { + applyColors, + enforceSharedLabelsColorsArray, + isLabelsColorMapSynced, + getColorSchemeDomain, + getColorNamespace, + getFreshLabelsColorMapEntries, + getFreshSharedLabels, + getDynamicLabelsColors, +} from '../../utils/colorScheme'; +import type { DashboardState, RootState, Slice } from '../types'; + +type GetState = () => RootState; + +// Dashboard dispatch type. The base ThunkDispatch handles dashboard-specific +// thunks. The intersection with a generic function-accepting overload allows +// dispatching thunks from other modules (e.g. chart actions) whose RootState +// type differs from the dashboard RootState. At runtime the Redux store +// satisfies all module state shapes. +interface AppDispatch extends ThunkDispatch<RootState, undefined, AnyAction> { + <R>(asyncAction: (...args: never[]) => R): R; +} + +// --------------------------------------------------------------------------- +// Simple action creators +// --------------------------------------------------------------------------- + +export const TOGGLE_NATIVE_FILTERS_BAR = 'TOGGLE_NATIVE_FILTERS_BAR'; + +interface ToggleNativeFiltersBarAction { + type: typeof TOGGLE_NATIVE_FILTERS_BAR; + isOpen: boolean; +} + +export function toggleNativeFiltersBar( + isOpen: boolean, +): ToggleNativeFiltersBarAction { + return { type: TOGGLE_NATIVE_FILTERS_BAR, isOpen }; +} + +export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES'; + +interface SetUnsavedChangesAction { + type: typeof SET_UNSAVED_CHANGES; + payload: { hasUnsavedChanges: boolean }; +} + +export function setUnsavedChanges( + hasUnsavedChanges: boolean, +): SetUnsavedChangesAction { + return { type: SET_UNSAVED_CHANGES, payload: { hasUnsavedChanges } }; +} + +export const ADD_SLICE = 'ADD_SLICE'; + +interface AddSliceAction { + type: typeof ADD_SLICE; + slice: Slice; +} + +export function addSlice(slice: Slice): AddSliceAction { + return { type: ADD_SLICE, slice }; +} + +export const REMOVE_SLICE = 'REMOVE_SLICE'; + +interface RemoveSliceAction { + type: typeof REMOVE_SLICE; + sliceId: number; +} + +export function removeSlice(sliceId: number): RemoveSliceAction { + return { type: REMOVE_SLICE, sliceId }; +} + +export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR'; + +interface ToggleFaveStarAction { + type: typeof TOGGLE_FAVE_STAR; + isStarred: boolean; +} + +export function toggleFaveStar(isStarred: boolean): ToggleFaveStarAction { + return { type: TOGGLE_FAVE_STAR, isStarred }; +} + +export function fetchFaveStar(id: number) { + return function fetchFaveStarThunk(dispatch: AppDispatch) { + return SupersetClient.get({ + endpoint: `/api/v1/dashboard/favorite_status/?q=${rison.encode([id])}`, + }) + .then(({ json }: { json: JsonObject }) => { + dispatch(toggleFaveStar(!!(json?.result as JsonObject[])?.[0]?.value)); + }) + .catch(() => + dispatch( + addDangerToast( + t( + 'There was an issue fetching the favorite status of this dashboard.', + ), + ), + ), + ); + }; +} + +export function saveFaveStar(id: number, isStarred: boolean) { + return function saveFaveStarThunk(dispatch: AppDispatch) { + const endpoint = `/api/v1/dashboard/${id}/favorites/`; + const apiCall = isStarred + ? SupersetClient.delete({ + endpoint, + }) + : SupersetClient.post({ endpoint }); + + return apiCall + .then(() => { + dispatch(toggleFaveStar(!isStarred)); + }) + .catch(() => + dispatch( + addDangerToast(t('There was an issue favoriting this dashboard.')), + ), + ); + }; +} + +export const TOGGLE_PUBLISHED = 'TOGGLE_PUBLISHED'; + +interface TogglePublishedAction { + type: typeof TOGGLE_PUBLISHED; + isPublished: boolean; +} + +export function togglePublished(isPublished: boolean): TogglePublishedAction { + return { type: TOGGLE_PUBLISHED, isPublished }; +} + +export function savePublished( + id: number, + isPublished: boolean, +): (dispatch: AppDispatch) => Promise<void> { + return function savePublishedThunk(dispatch: AppDispatch): Promise<void> { + return SupersetClient.put({ + endpoint: `/api/v1/dashboard/${id}`, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + published: isPublished, + }), + }) + .then(() => { + dispatch( + addSuccessToast( + isPublished + ? t('This dashboard is now published') + : t('This dashboard is now hidden'), + ), + ); + dispatch(togglePublished(isPublished)); + }) + .catch(() => { + dispatch( + addDangerToast( + t('You do not have permissions to edit this dashboard.'), + ), + ); + }); + }; +} + +export const TOGGLE_EXPAND_SLICE = 'TOGGLE_EXPAND_SLICE'; + +interface ToggleExpandSliceAction { + type: typeof TOGGLE_EXPAND_SLICE; + sliceId: number; +} + +export function toggleExpandSlice(sliceId: number): ToggleExpandSliceAction { + return { type: TOGGLE_EXPAND_SLICE, sliceId }; +} + +export const SET_EDIT_MODE = 'SET_EDIT_MODE'; + +interface SetEditModeAction { + type: typeof SET_EDIT_MODE; + editMode: boolean; +} + +export function setEditMode(editMode: boolean): SetEditModeAction { + return { type: SET_EDIT_MODE, editMode }; +} + +export const ON_CHANGE = 'ON_CHANGE'; + +interface OnChangeAction { + type: typeof ON_CHANGE; +} + +export function onChange(): OnChangeAction { + return { type: ON_CHANGE }; +} + +export const ON_SAVE = 'ON_SAVE'; + +interface OnSaveAction { + type: typeof ON_SAVE; + lastModifiedTime: number; +} + +export function onSave(lastModifiedTime: number): OnSaveAction { + return { type: ON_SAVE, lastModifiedTime }; +} + +export const SET_REFRESH_FREQUENCY = 'SET_REFRESH_FREQUENCY'; + +interface SetRefreshFrequencyAction { + type: typeof SET_REFRESH_FREQUENCY; + refreshFrequency: number; + isPersistent: boolean; +} + +export function setRefreshFrequency( + refreshFrequency: number, + isPersistent = false, +): SetRefreshFrequencyAction { + return { type: SET_REFRESH_FREQUENCY, refreshFrequency, isPersistent }; +} + +export function saveDashboardRequestSuccess( + lastModifiedTime: number, +): (dispatch: AppDispatch) => void { + return (dispatch: AppDispatch) => { + dispatch(onSave(lastModifiedTime)); + // clear layout undo history + dispatch(UndoActionCreators.clearHistory()); + }; +} + +export const SET_OVERRIDE_CONFIRM = 'SET_OVERRIDE_CONFIRM'; + +interface SetOverrideConfirmAction { + type: typeof SET_OVERRIDE_CONFIRM; + overwriteConfirmMetadata: DashboardState['overwriteConfirmMetadata']; +} + +export function setOverrideConfirm( + overwriteConfirmMetadata: DashboardState['overwriteConfirmMetadata'], +): SetOverrideConfirmAction { + return { + type: SET_OVERRIDE_CONFIRM, + overwriteConfirmMetadata, + }; +} + +export const SAVE_DASHBOARD_STARTED = 'SAVE_DASHBOARD_STARTED'; + +interface SaveDashboardStartedAction { + type: typeof SAVE_DASHBOARD_STARTED; +} + +export function saveDashboardStarted(): SaveDashboardStartedAction { + return { type: SAVE_DASHBOARD_STARTED }; +} + +export const SAVE_DASHBOARD_FINISHED = 'SAVE_DASHBOARD_FINISHED'; + +interface SaveDashboardFinishedAction { + type: typeof SAVE_DASHBOARD_FINISHED; +} + +export function saveDashboardFinished(): SaveDashboardFinishedAction { + return { type: SAVE_DASHBOARD_FINISHED }; +} + +export const SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE = + 'SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE'; +export const SET_DASHBOARD_LABELS_COLORMAP_SYNCED = + 'SET_DASHBOARD_LABELS_COLORMAP_SYNCED'; +export const SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE = + 'SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE'; +export const SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED = + 'SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED'; + +interface SetDashboardLabelsColorMapSyncAction { + type: typeof SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE; +} + +interface SetDashboardLabelsColorMapSyncedAction { + type: typeof SET_DASHBOARD_LABELS_COLORMAP_SYNCED; +} + +interface SetDashboardSharedLabelsColorsSyncAction { + type: typeof SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE; +} + +interface SetDashboardSharedLabelsColorsSyncedAction { + type: typeof SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED; +} + +export function setDashboardLabelsColorMapSync(): SetDashboardLabelsColorMapSyncAction { + return { type: SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE }; +} + +export function setDashboardLabelsColorMapSynced(): SetDashboardLabelsColorMapSyncedAction { + return { type: SET_DASHBOARD_LABELS_COLORMAP_SYNCED }; +} + +export function setDashboardSharedLabelsColorsSync(): SetDashboardSharedLabelsColorsSyncAction { + return { type: SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE }; +} + +export function setDashboardSharedLabelsColorsSynced(): SetDashboardSharedLabelsColorsSyncedAction { + return { type: SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED }; +} + +export const setDashboardMetadata = + (updatedMetadata: JsonObject) => + async (dispatch: AppDispatch, getState: GetState): Promise<void> => { + const { dashboardInfo } = getState(); + dispatch( + dashboardInfoChanged({ + metadata: { + ...dashboardInfo?.metadata, + ...updatedMetadata, + }, + }), + ); + }; + +// --------------------------------------------------------------------------- +// saveDashboardRequest +// --------------------------------------------------------------------------- + +interface DashboardSaveData extends JsonObject { + certified_by?: string; + certification_details?: string; + css?: string; + dashboard_title?: string; + owners?: { id: number }[] | number[]; + roles?: { id: number }[] | number[]; + slug?: string | null; + tags?: { id: number }[] | number[]; + metadata?: JsonObject; + positions?: JsonObject; + duplicate_slices?: boolean; + theme_id?: number | null; +} + +export function saveDashboardRequest( + data: DashboardSaveData, + id: number, + saveType: string, +): (dispatch: AppDispatch, getState: GetState) => Promise<JsonObject | void> { + return (dispatch: AppDispatch, getState: GetState) => { + dispatch({ type: UPDATE_COMPONENTS_PARENTS_LIST }); + dispatch(saveDashboardStarted()); + + const { dashboardFilters, dashboardLayout } = getState(); + const layout = dashboardLayout.present; + Object.values(dashboardFilters).forEach((filter: JsonObject) => { + const { chartId } = filter; + const componentId = (filter.directPathToFilter as string[]) + .slice() + .pop() as string; + const directPathToFilter = (layout[componentId]?.parents || []).slice(); + directPathToFilter.push(componentId); + dispatch(updateDirectPathToFilter(chartId as number, 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); + const { + certified_by, + certification_details, + css, + dashboard_title, + owners, + roles, + slug, + tags, + } = data; + + const hasId = (item: JsonObject): boolean => item.id !== undefined; + const metadataCrossFiltersEnabled = data.metadata?.cross_filters_enabled; + const colorScheme = data.metadata?.color_scheme as string | undefined; + const customLabelsColor = (data.metadata?.label_colors || {}) as Record< + string, + string + >; + const sharedLabelsColor = enforceSharedLabelsColorsArray( + data.metadata?.shared_label_colors, + ); + const cleanedData: JsonObject = { + ...data, + certified_by: certified_by || '', + certification_details: + certified_by && certification_details ? certification_details : '', + css: css || '', + dashboard_title: dashboard_title || t('[ untitled dashboard ]'), + owners: ensureIsArray(owners as JsonObject[]).map((o: JsonObject) => + hasId(o) ? o.id : o, + ), + roles: !isFeatureEnabled(FeatureFlag.DashboardRbac) + ? undefined + : ensureIsArray(roles as JsonObject[]).map((r: JsonObject) => + hasId(r) ? r.id : r, + ), + slug: slug || null, + tags: !isFeatureEnabled(FeatureFlag.TaggingSystem) + ? undefined + : ensureIsArray((tags || []) as JsonObject[]).map((r: JsonObject) => + hasId(r) ? r.id : r, + ), + metadata: { + ...data.metadata, + color_namespace: getColorNamespace( + data.metadata?.color_namespace as string | undefined, + ), + color_scheme: colorScheme || '', + color_scheme_domain: colorScheme + ? getColorSchemeDomain(colorScheme) + : [], + expanded_slices: data.metadata?.expanded_slices || {}, + label_colors: customLabelsColor, + shared_label_colors: getFreshSharedLabels(sharedLabelsColor), + map_label_colors: getFreshLabelsColorMapEntries(customLabelsColor), + refresh_frequency: data.metadata?.refresh_frequency || 0, + timed_refresh_immune_slices: + data.metadata?.timed_refresh_immune_slices || [], + // cross-filters should be enabled by default + cross_filters_enabled: isCrossFiltersEnabled( + metadataCrossFiltersEnabled as boolean | undefined, + ), + }, + }; + + const handleChartConfiguration = () => { + const { + dashboardLayout: currentDashboardLayout, + charts, + dashboardInfo: { metadata }, + } = getState(); + return getCrossFiltersConfiguration( + currentDashboardLayout.present, + metadata, + charts, + ); + }; + + const onCopySuccess = (response: JsonObject): JsonObject => { + const lastModifiedTime = (response.json as JsonObject).result + ?.last_modified_time as number; + if (lastModifiedTime) { + dispatch(saveDashboardRequestSuccess(lastModifiedTime)); + } + const { chartConfiguration, globalChartConfiguration } = + handleChartConfiguration(); + dispatch( + saveChartConfiguration({ + chartConfiguration, + globalChartConfiguration, + }), + ); + dispatch(saveDashboardFinished()); + navigateTo( + `/superset/dashboard/${(response.json as JsonObject).result?.id}/`, + ); + dispatch(addSuccessToast(t('This dashboard was saved successfully.'))); + return response; + }; + + const onUpdateSuccess = (response: JsonObject): JsonObject => { + const updatedDashboard = (response.json as JsonObject) + .result as JsonObject; + const lastModifiedTime = (response.json as JsonObject) + .last_modified_time as number; + // syncing with the backend transformations of the metadata + if (updatedDashboard.json_metadata) { + const parsedMetadata: JsonObject = JSON.parse( + updatedDashboard.json_metadata as string, + ); + dispatch(setDashboardMetadata(parsedMetadata)); + if (parsedMetadata.chart_configuration) { + dispatch({ + type: SAVE_CHART_CONFIG_COMPLETE, + chartConfiguration: parsedMetadata.chart_configuration, + }); + } + if (parsedMetadata.native_filter_configuration) { + dispatch({ + type: SET_IN_SCOPE_STATUS_OF_FILTERS, + filterConfig: parsedMetadata.native_filter_configuration, + }); + } + + // fetch datasets to make sure they are up to date + SupersetClient.get({ + endpoint: `/api/v1/dashboard/${id}/datasets`, + headers: { 'Content-Type': 'application/json' }, + }) + .then(({ json }: { json: JsonObject }) => { + const datasources = json?.result ?? []; + if ((datasources as JsonObject[]).length) { + dispatch( + setDatasources( + datasources as Parameters<typeof setDatasources>[0], + ), + ); + } + }) + .catch((error: Error) => { + logging.error('Error fetching dashboard datasets:', error); + }); + } + if (lastModifiedTime) { + dispatch(saveDashboardRequestSuccess(lastModifiedTime)); + } + dispatch(saveDashboardFinished()); + // redirect to the new slug or id + navigateWithState(`/superset/dashboard/${slug || id}/`, { + event: 'dashboard_properties_changed', + }); + + dispatch(addSuccessToast(t('This dashboard was saved successfully.'))); + dispatch(setOverrideConfirm(undefined)); + return response; + }; + + const onError = async (response: Response): Promise<void> => { + const { error, message } = await getClientErrorObject(response); + let errorText = t('Sorry, an unknown error occurred'); + + if (error) { + errorText = t( + 'Sorry, there was an error saving this dashboard: %s', + error, + ); + } + if (typeof message === 'string' && message === 'Forbidden') { + errorText = t('You do not have permission to edit this dashboard'); + } + dispatch(saveDashboardFinished()); + dispatch(addDangerToast(errorText)); + }; + + if ( + [SAVE_TYPE_OVERWRITE, SAVE_TYPE_OVERWRITE_CONFIRMED].includes(saveType) + ) { + const { chartConfiguration, globalChartConfiguration } = + handleChartConfiguration(); + const updatedDashboard: JsonObject = + saveType === SAVE_TYPE_OVERWRITE_CONFIRMED + ? data + : { + certified_by: cleanedData.certified_by, + certification_details: cleanedData.certification_details, + css: cleanedData.css, + dashboard_title: cleanedData.dashboard_title, + slug: cleanedData.slug, + owners: cleanedData.owners, + roles: cleanedData.roles, + tags: cleanedData.tags || [], + theme_id: cleanedData.theme_id, + json_metadata: safeStringify({ + ...(cleanedData?.metadata as JsonObject), + default_filters: safeStringify(serializedFilters), + filter_scopes: serializedFilterScopes, + chart_configuration: chartConfiguration, + global_chart_configuration: globalChartConfiguration, + }), + }; + + const updateDashboard = (): Promise<JsonObject | void> => + SupersetClient.put({ + endpoint: `/api/v1/dashboard/${id}`, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(updatedDashboard), + }) + .then(response => onUpdateSuccess(response)) + .catch(response => onError(response)); + return new Promise<void>((resolve, reject) => { + if ( + !isFeatureEnabled(FeatureFlag.ConfirmDashboardDiff) || + saveType === SAVE_TYPE_OVERWRITE_CONFIRMED + ) { + // skip overwrite precheck + resolve(); + return; + } + + // precheck for overwrite items + SupersetClient.get({ + endpoint: `/api/v1/dashboard/${id}`, + }).then((response: JsonObject) => { + const dashboard = (response.json as JsonObject).result as JsonObject; + const overwriteConfirmItems = getOverwriteItems( + dashboard, + updatedDashboard, + ); + if (overwriteConfirmItems.length > 0) { + dispatch( + setOverrideConfirm({ + updatedAt: dashboard.changed_on as string, + updatedBy: dashboard.changed_by_name as string, + overwriteConfirmItems: + overwriteConfirmItems as DashboardState['overwriteConfirmMetadata'] extends + | { overwriteConfirmItems: infer I } + | undefined + ? I + : never, + dashboardId: id, + data: updatedDashboard, + }), + ); + return reject(overwriteConfirmItems); + } + return resolve(); + }); + }) + .then(updateDashboard) + .catch((overwriteConfirmItems: JsonObject[]) => { + const errorText = t('Please confirm the overwrite values.'); + dispatch( + logEvent(LOG_ACTIONS_CONFIRM_OVERWRITE_DASHBOARD_METADATA, { + dashboard_id: id, + items: overwriteConfirmItems, + }), + ); + dispatch(addDangerToast(errorText)); + }); + } + // changing the data as the endpoint requires + if ( + 'positions' in cleanedData && + !('positions' in (cleanedData.metadata as JsonObject)) + ) { + (cleanedData.metadata as JsonObject).positions = cleanedData.positions; + } + (cleanedData.metadata as JsonObject).default_filters = + safeStringify(serializedFilters); + (cleanedData.metadata as JsonObject).filter_scopes = serializedFilterScopes; + const copyPayload = { + dashboard_title: cleanedData.dashboard_title, + css: cleanedData.css, + duplicate_slices: cleanedData.duplicate_slices, + json_metadata: JSON.stringify(cleanedData.metadata), + }; + + return SupersetClient.post({ + endpoint: `/api/v1/dashboard/${id}/copy/`, + jsonPayload: copyPayload, + }) + .then(response => onCopySuccess(response)) + .catch(response => onError(response)); + }; +} + +// --------------------------------------------------------------------------- +// Chart refresh +// --------------------------------------------------------------------------- + +export function fetchCharts( + chartList: number[] = [], + force = false, + interval = 0, + dashboardId?: number, +): (dispatch: AppDispatch, getState: GetState) => void { + return (dispatch: AppDispatch, getState: GetState) => { + if (!interval) { + chartList.forEach(chartKey => + dispatch(refreshChart(chartKey, force, dashboardId)), + ); + return; + } + + const { metadata } = getState().dashboardInfo; + const meta = metadata as JsonObject; + const refreshTime = Math.max( + interval, + (meta.stagger_time as number) || 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; Review Comment: Fixed in 6ead55bc0a. Added `chartList.length > 1` check to the condition so staggering only applies when there are multiple charts, avoiding `refreshTime / 0 = Infinity`. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
