| /** |
| * 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-env browser */ |
| import { extendedDayjs } from '@superset-ui/core/utils/dates'; |
| import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; |
| import { |
| styled, |
| css, |
| isFeatureEnabled, |
| FeatureFlag, |
| t, |
| getExtensionsRegistry, |
| } from '@superset-ui/core'; |
| import { Global } from '@emotion/react'; |
| import { shallowEqual, useDispatch, useSelector } from 'react-redux'; |
| import { bindActionCreators } from 'redux'; |
| import { |
| LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD, |
| LOG_ACTIONS_FORCE_REFRESH_DASHBOARD, |
| LOG_ACTIONS_TOGGLE_EDIT_DASHBOARD, |
| } from 'src/logger/LogUtils'; |
| import { Icons } from '@superset-ui/core/components/Icons'; |
| import { |
| Button, |
| Tooltip, |
| DeleteModal, |
| UnsavedChangesModal, |
| } from '@superset-ui/core/components'; |
| import { findPermission } from 'src/utils/findPermission'; |
| import { safeStringify } from 'src/utils/safeStringify'; |
| import PublishedStatus from 'src/dashboard/components/PublishedStatus'; |
| import UndoRedoKeyListeners from 'src/dashboard/components/UndoRedoKeyListeners'; |
| import PropertiesModal from 'src/dashboard/components/PropertiesModal'; |
| import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal'; |
| import { |
| UNDO_LIMIT, |
| SAVE_TYPE_OVERWRITE, |
| DASHBOARD_POSITION_DATA_LIMIT, |
| DASHBOARD_HEADER_ID, |
| } from 'src/dashboard/util/constants'; |
| import { TagTypeEnum } from 'src/components/Tag/TagType'; |
| import setPeriodicRunner, { |
| stopPeriodicRender, |
| } from 'src/dashboard/util/setPeriodicRunner'; |
| import ReportModal from 'src/features/reports/ReportModal'; |
| import { deleteActiveReport } from 'src/features/reports/ReportModal/actions'; |
| import { PageHeaderWithActions } from '@superset-ui/core/components/PageHeaderWithActions'; |
| import { useUnsavedChangesPrompt } from 'src/hooks/useUnsavedChangesPrompt'; |
| import DashboardEmbedModal from '../EmbeddedModal'; |
| import OverwriteConfirm from '../OverwriteConfirm'; |
| import { |
| addDangerToast, |
| addSuccessToast, |
| addWarningToast, |
| } from '../../../components/MessageToasts/actions'; |
| import { |
| dashboardTitleChanged, |
| redoLayoutAction, |
| undoLayoutAction, |
| updateDashboardTitle, |
| clearDashboardHistory, |
| } from '../../actions/dashboardLayout'; |
| import { |
| fetchCharts, |
| fetchFaveStar, |
| maxUndoHistoryToast, |
| onChange, |
| onRefresh, |
| saveDashboardRequest, |
| saveFaveStar, |
| savePublished, |
| setEditMode, |
| setMaxUndoHistoryExceeded, |
| setRefreshFrequency, |
| setUnsavedChanges, |
| } from '../../actions/dashboardState'; |
| import { logEvent } from '../../../logger/actions'; |
| import { dashboardInfoChanged } from '../../actions/dashboardInfo'; |
| import isDashboardLoading from '../../util/isDashboardLoading'; |
| import { useChartIds } from '../../util/charts/useChartIds'; |
| import { useDashboardMetadataBar } from './useDashboardMetadataBar'; |
| import { useHeaderActionsMenu } from './useHeaderActionsDropdownMenu'; |
| |
| const extensionsRegistry = getExtensionsRegistry(); |
| |
| const headerContainerStyle = theme => css` |
| border-bottom: 1px solid ${theme.colorSplit}; |
| `; |
| |
| const editButtonStyle = theme => css` |
| color: ${theme.colorPrimary}; |
| `; |
| |
| const actionButtonsStyle = theme => css` |
| display: flex; |
| align-items: center; |
| |
| .action-schedule-report { |
| margin-left: ${theme.sizeUnit * 2}px; |
| } |
| |
| .undoRedo { |
| display: flex; |
| margin-right: ${theme.sizeUnit * 2}px; |
| } |
| `; |
| |
| const StyledUndoRedoButton = styled(Button)` |
| // TODO: check if we need this |
| padding: 0; |
| &:hover { |
| background: transparent; |
| } |
| `; |
| |
| const undoRedoStyle = theme => css` |
| color: ${theme.colorIcon}; |
| &:hover { |
| color: ${theme.colorIconHover}; |
| } |
| `; |
| |
| const undoRedoEmphasized = theme => css` |
| color: ${theme.colorIcon}; |
| `; |
| |
| const undoRedoDisabled = theme => css` |
| color: ${theme.colorTextDisabled}; |
| `; |
| |
| const saveBtnStyle = theme => css` |
| min-width: ${theme.sizeUnit * 17}px; |
| height: ${theme.sizeUnit * 8}px; |
| span > :first-of-type { |
| margin-right: 0; |
| } |
| `; |
| |
| const discardBtnStyle = theme => css` |
| min-width: ${theme.sizeUnit * 22}px; |
| height: ${theme.sizeUnit * 8}px; |
| `; |
| |
| const discardChanges = () => { |
| const url = new URL(window.location.href); |
| |
| url.searchParams.delete('edit'); |
| window.location.assign(url); |
| }; |
| |
| const Header = () => { |
| const dispatch = useDispatch(); |
| const [didNotifyMaxUndoHistoryToast, setDidNotifyMaxUndoHistoryToast] = |
| useState(false); |
| const [emphasizeUndo, setEmphasizeUndo] = useState(false); |
| const [emphasizeRedo, setEmphasizeRedo] = useState(false); |
| const [showingPropertiesModal, setShowingPropertiesModal] = useState(false); |
| const [showingRefreshModal, setShowingRefreshModal] = useState(false); |
| const [showingEmbedModal, setShowingEmbedModal] = useState(false); |
| const [showingReportModal, setShowingReportModal] = useState(false); |
| const [currentReportDeleting, setCurrentReportDeleting] = useState(null); |
| const dashboardInfo = useSelector(state => state.dashboardInfo); |
| const layout = useSelector(state => state.dashboardLayout.present); |
| const undoLength = useSelector(state => state.dashboardLayout.past.length); |
| const redoLength = useSelector(state => state.dashboardLayout.future.length); |
| const dataMask = useSelector(state => state.dataMask); |
| const user = useSelector(state => state.user); |
| const chartIds = useChartIds(); |
| |
| const { |
| expandedSlices, |
| refreshFrequency, |
| shouldPersistRefreshFrequency, |
| customCss, |
| colorNamespace, |
| colorScheme, |
| isStarred, |
| isPublished, |
| hasUnsavedChanges, |
| maxUndoHistoryExceeded, |
| editMode, |
| lastModifiedTime, |
| } = useSelector( |
| state => ({ |
| expandedSlices: state.dashboardState.expandedSlices, |
| refreshFrequency: state.dashboardState.refreshFrequency, |
| shouldPersistRefreshFrequency: |
| !!state.dashboardState.shouldPersistRefreshFrequency, |
| customCss: state.dashboardInfo.css, |
| colorNamespace: state.dashboardState.colorNamespace, |
| colorScheme: state.dashboardState.colorScheme, |
| isStarred: !!state.dashboardState.isStarred, |
| isPublished: !!state.dashboardState.isPublished, |
| hasUnsavedChanges: !!state.dashboardState.hasUnsavedChanges, |
| maxUndoHistoryExceeded: !!state.dashboardState.maxUndoHistoryExceeded, |
| editMode: !!state.dashboardState.editMode, |
| lastModifiedTime: state.lastModifiedTime, |
| }), |
| shallowEqual, |
| ); |
| const isLoading = useSelector(state => isDashboardLoading(state.charts)); |
| |
| const refreshTimer = useRef(0); |
| const ctrlYTimeout = useRef(0); |
| const ctrlZTimeout = useRef(0); |
| |
| const dashboardTitle = layout[DASHBOARD_HEADER_ID]?.meta?.text; |
| const { slug } = dashboardInfo; |
| const actualLastModifiedTime = Math.max( |
| lastModifiedTime, |
| dashboardInfo.last_modified_time, |
| ); |
| const boundActionCreators = useMemo( |
| () => |
| bindActionCreators( |
| { |
| addSuccessToast, |
| addDangerToast, |
| addWarningToast, |
| onUndo: undoLayoutAction, |
| onRedo: redoLayoutAction, |
| clearDashboardHistory, |
| setEditMode, |
| setUnsavedChanges, |
| fetchFaveStar, |
| saveFaveStar, |
| savePublished, |
| fetchCharts, |
| updateDashboardTitle, |
| onChange, |
| onSave: saveDashboardRequest, |
| setMaxUndoHistoryExceeded, |
| maxUndoHistoryToast, |
| logEvent, |
| setRefreshFrequency, |
| onRefresh, |
| dashboardInfoChanged, |
| dashboardTitleChanged, |
| }, |
| dispatch, |
| ), |
| [dispatch], |
| ); |
| |
| const startPeriodicRender = useCallback( |
| interval => { |
| let intervalMessage; |
| |
| if (interval) { |
| const periodicRefreshOptions = |
| dashboardInfo.common?.conf?.DASHBOARD_AUTO_REFRESH_INTERVALS; |
| const predefinedValue = periodicRefreshOptions.find( |
| option => Number(option[0]) === interval / 1000, |
| ); |
| |
| if (predefinedValue) { |
| intervalMessage = t(predefinedValue[1]); |
| } else { |
| intervalMessage = extendedDayjs |
| .duration(interval, 'millisecond') |
| .humanize(); |
| } |
| } |
| |
| const fetchCharts = (charts, force = false) => |
| boundActionCreators.fetchCharts( |
| charts, |
| force, |
| interval * 0.2, |
| dashboardInfo.id, |
| ); |
| |
| const periodicRender = () => { |
| const { metadata } = dashboardInfo; |
| const immune = metadata.timed_refresh_immune_slices || []; |
| const affectedCharts = chartIds.filter( |
| chartId => immune.indexOf(chartId) === -1, |
| ); |
| |
| boundActionCreators.logEvent(LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD, { |
| interval, |
| chartCount: affectedCharts.length, |
| }); |
| boundActionCreators.addWarningToast( |
| t( |
| `This dashboard is currently auto refreshing; the next auto refresh will be in %s.`, |
| intervalMessage, |
| ), |
| ); |
| if ( |
| dashboardInfo.common?.conf?.DASHBOARD_AUTO_REFRESH_MODE === 'fetch' |
| ) { |
| // force-refresh while auto-refresh in dashboard |
| return fetchCharts(affectedCharts); |
| } |
| return fetchCharts(affectedCharts, true); |
| }; |
| |
| refreshTimer.current = setPeriodicRunner({ |
| interval, |
| periodicRender, |
| refreshTimer: refreshTimer.current, |
| }); |
| }, |
| [boundActionCreators, chartIds, dashboardInfo], |
| ); |
| |
| useEffect(() => { |
| startPeriodicRender(refreshFrequency * 1000); |
| }, [refreshFrequency, startPeriodicRender]); |
| |
| // Ensure theme changes are tracked as unsaved changes |
| useEffect(() => { |
| if (editMode && dashboardInfo.theme !== undefined) { |
| boundActionCreators.setUnsavedChanges(true); |
| } |
| }, [dashboardInfo.theme, editMode, boundActionCreators]); |
| |
| useEffect(() => { |
| if (UNDO_LIMIT - undoLength <= 0 && !didNotifyMaxUndoHistoryToast) { |
| setDidNotifyMaxUndoHistoryToast(true); |
| boundActionCreators.maxUndoHistoryToast(); |
| } |
| if (undoLength > UNDO_LIMIT && !maxUndoHistoryExceeded) { |
| boundActionCreators.setMaxUndoHistoryExceeded(); |
| } |
| }, [ |
| boundActionCreators, |
| didNotifyMaxUndoHistoryToast, |
| maxUndoHistoryExceeded, |
| undoLength, |
| ]); |
| |
| useEffect( |
| () => () => { |
| stopPeriodicRender(refreshTimer.current); |
| boundActionCreators.setRefreshFrequency(0); |
| clearTimeout(ctrlYTimeout.current); |
| clearTimeout(ctrlZTimeout.current); |
| }, |
| [boundActionCreators], |
| ); |
| |
| const handleChangeText = useCallback( |
| nextText => { |
| if (nextText && dashboardTitle !== nextText) { |
| boundActionCreators.updateDashboardTitle(nextText); |
| boundActionCreators.onChange(); |
| } |
| }, |
| [boundActionCreators, dashboardTitle], |
| ); |
| |
| const handleCtrlY = useCallback(() => { |
| boundActionCreators.onRedo(); |
| setEmphasizeRedo(true); |
| if (ctrlYTimeout.current) { |
| clearTimeout(ctrlYTimeout.current); |
| } |
| ctrlYTimeout.current = setTimeout(() => { |
| setEmphasizeRedo(false); |
| }, 100); |
| }, [boundActionCreators]); |
| |
| const handleCtrlZ = useCallback(() => { |
| boundActionCreators.onUndo(); |
| setEmphasizeUndo(true); |
| if (ctrlZTimeout.current) { |
| clearTimeout(ctrlZTimeout.current); |
| } |
| ctrlZTimeout.current = setTimeout(() => { |
| setEmphasizeUndo(false); |
| }, 100); |
| }, [boundActionCreators]); |
| |
| const forceRefresh = useCallback(() => { |
| if (!isLoading) { |
| boundActionCreators.logEvent(LOG_ACTIONS_FORCE_REFRESH_DASHBOARD, { |
| force: true, |
| interval: 0, |
| chartCount: chartIds.length, |
| }); |
| return boundActionCreators.onRefresh(chartIds, true, 0, dashboardInfo.id); |
| } |
| return false; |
| }, [boundActionCreators, chartIds, dashboardInfo.id, isLoading]); |
| |
| const toggleEditMode = useCallback(() => { |
| boundActionCreators.logEvent(LOG_ACTIONS_TOGGLE_EDIT_DASHBOARD, { |
| edit_mode: !editMode, |
| }); |
| boundActionCreators.setEditMode(!editMode); |
| }, [boundActionCreators, editMode]); |
| |
| const overwriteDashboard = useCallback(() => { |
| const currentColorNamespace = |
| dashboardInfo?.metadata?.color_namespace || colorNamespace; |
| const currentColorScheme = |
| dashboardInfo?.metadata?.color_scheme || colorScheme; |
| |
| const data = { |
| certified_by: dashboardInfo.certified_by, |
| certification_details: dashboardInfo.certification_details, |
| css: customCss, |
| dashboard_title: dashboardTitle, |
| last_modified_time: actualLastModifiedTime, |
| owners: dashboardInfo.owners, |
| roles: dashboardInfo.roles, |
| slug, |
| tags: (dashboardInfo.tags || []).filter( |
| item => item.type === TagTypeEnum.Custom || !item.type, |
| ), |
| theme_id: dashboardInfo.theme ? dashboardInfo.theme.id : null, |
| metadata: { |
| ...dashboardInfo?.metadata, |
| color_namespace: currentColorNamespace, |
| color_scheme: currentColorScheme, |
| positions: layout, |
| refresh_frequency: shouldPersistRefreshFrequency |
| ? refreshFrequency |
| : dashboardInfo.metadata?.refresh_frequency, |
| }, |
| }; |
| |
| // make sure positions data less than DB storage limitation: |
| const positionJSONLength = safeStringify(layout).length; |
| const limit = |
| dashboardInfo.common?.conf?.SUPERSET_DASHBOARD_POSITION_DATA_LIMIT || |
| DASHBOARD_POSITION_DATA_LIMIT; |
| if (positionJSONLength >= limit) { |
| boundActionCreators.addDangerToast( |
| t( |
| 'Your dashboard is too large. Please reduce its size before saving it.', |
| ), |
| ); |
| } else { |
| if (positionJSONLength >= limit * 0.9) { |
| boundActionCreators.addWarningToast( |
| t('Your dashboard is near the size limit.'), |
| ); |
| } |
| |
| boundActionCreators.onSave(data, dashboardInfo.id, SAVE_TYPE_OVERWRITE); |
| } |
| }, [ |
| actualLastModifiedTime, |
| boundActionCreators, |
| colorNamespace, |
| colorScheme, |
| customCss, |
| dashboardInfo.certification_details, |
| dashboardInfo.certified_by, |
| dashboardInfo.common?.conf?.SUPERSET_DASHBOARD_POSITION_DATA_LIMIT, |
| dashboardInfo.id, |
| dashboardInfo.metadata, |
| dashboardInfo.owners, |
| dashboardInfo.roles, |
| dashboardInfo.tags, |
| dashboardTitle, |
| layout, |
| refreshFrequency, |
| shouldPersistRefreshFrequency, |
| slug, |
| ]); |
| |
| const { |
| showModal: showUnsavedChangesModal, |
| setShowModal: setShowUnsavedChangesModal, |
| handleConfirmNavigation, |
| handleSaveAndCloseModal, |
| } = useUnsavedChangesPrompt({ |
| hasUnsavedChanges, |
| onSave: overwriteDashboard, |
| }); |
| |
| const showPropertiesModal = useCallback(() => { |
| setShowingPropertiesModal(true); |
| }, []); |
| |
| const hidePropertiesModal = useCallback(() => { |
| setShowingPropertiesModal(false); |
| }, []); |
| const showRefreshModal = useCallback(() => { |
| setShowingRefreshModal(true); |
| }, []); |
| const hideRefreshModal = useCallback(() => { |
| setShowingRefreshModal(false); |
| }, []); |
| |
| const showEmbedModal = useCallback(() => { |
| setShowingEmbedModal(true); |
| }, []); |
| |
| const hideEmbedModal = useCallback(() => { |
| setShowingEmbedModal(false); |
| }, []); |
| |
| const showReportModal = useCallback(() => { |
| setShowingReportModal(true); |
| }, []); |
| |
| const hideReportModal = useCallback(() => { |
| setShowingReportModal(false); |
| }, []); |
| |
| const metadataBar = useDashboardMetadataBar(dashboardInfo); |
| |
| const userCanEdit = |
| dashboardInfo.dash_edit_perm && !dashboardInfo.is_managed_externally; |
| const userCanShare = dashboardInfo.dash_share_perm; |
| const userCanSaveAs = dashboardInfo.dash_save_perm; |
| const userCanCurate = |
| isFeatureEnabled(FeatureFlag.EmbeddedSuperset) && |
| findPermission('can_set_embedded', 'Dashboard', user.roles); |
| const isEmbedded = !dashboardInfo?.userId; |
| |
| const handleOnPropertiesChange = useCallback( |
| updates => { |
| boundActionCreators.dashboardInfoChanged({ |
| slug: updates.slug, |
| metadata: JSON.parse(updates.jsonMetadata || '{}'), |
| certified_by: updates.certifiedBy, |
| certification_details: updates.certificationDetails, |
| owners: updates.owners, |
| roles: updates.roles, |
| tags: updates.tags, |
| theme_id: updates.themeId, |
| css: updates.css, |
| }); |
| boundActionCreators.setUnsavedChanges(true); |
| |
| if (updates.title && dashboardTitle !== updates.title) { |
| boundActionCreators.updateDashboardTitle(updates.title); |
| boundActionCreators.onChange(); |
| } |
| }, |
| [boundActionCreators, dashboardTitle], |
| ); |
| |
| const handleRefreshChange = useCallback( |
| (refreshFrequency, editMode) => { |
| boundActionCreators.setRefreshFrequency(refreshFrequency, !!editMode); |
| }, |
| [boundActionCreators], |
| ); |
| |
| const NavExtension = extensionsRegistry.get('dashboard.nav.right'); |
| |
| const editableTitleProps = useMemo( |
| () => ({ |
| title: dashboardTitle, |
| canEdit: userCanEdit && editMode, |
| onSave: handleChangeText, |
| placeholder: t('Add the name of the dashboard'), |
| label: t('Dashboard title'), |
| showTooltip: false, |
| }), |
| [dashboardTitle, editMode, handleChangeText, userCanEdit], |
| ); |
| |
| const certifiedBadgeProps = useMemo( |
| () => ({ |
| certifiedBy: dashboardInfo.certified_by, |
| details: dashboardInfo.certification_details, |
| }), |
| [dashboardInfo.certification_details, dashboardInfo.certified_by], |
| ); |
| |
| const faveStarProps = useMemo( |
| () => ({ |
| itemId: dashboardInfo.id, |
| fetchFaveStar: boundActionCreators.fetchFaveStar, |
| saveFaveStar: boundActionCreators.saveFaveStar, |
| isStarred, |
| showTooltip: true, |
| }), |
| [ |
| boundActionCreators.fetchFaveStar, |
| boundActionCreators.saveFaveStar, |
| dashboardInfo.id, |
| isStarred, |
| ], |
| ); |
| |
| const titlePanelAdditionalItems = useMemo( |
| () => [ |
| !editMode && ( |
| <PublishedStatus |
| dashboardId={dashboardInfo.id} |
| isPublished={isPublished} |
| savePublished={boundActionCreators.savePublished} |
| userCanEdit={userCanEdit} |
| userCanSave={userCanSaveAs} |
| visible={!editMode} |
| /> |
| ), |
| !editMode && !isEmbedded && metadataBar, |
| ], |
| [ |
| boundActionCreators.savePublished, |
| dashboardInfo.id, |
| editMode, |
| metadataBar, |
| isEmbedded, |
| isPublished, |
| userCanEdit, |
| userCanSaveAs, |
| ], |
| ); |
| |
| const rightPanelAdditionalItems = useMemo( |
| () => ( |
| <div className="button-container"> |
| {userCanSaveAs && ( |
| <div className="button-container" data-test="dashboard-edit-actions"> |
| {editMode && ( |
| <div css={actionButtonsStyle}> |
| <div className="undoRedo"> |
| <Tooltip |
| id="dashboard-undo-tooltip" |
| title={t('Undo the action')} |
| > |
| <StyledUndoRedoButton |
| buttonStyle="link" |
| disabled={undoLength < 1} |
| onClick={ |
| undoLength > 0 ? boundActionCreators.onUndo : undefined |
| } |
| > |
| <Icons.Undo |
| css={[ |
| undoRedoStyle, |
| emphasizeUndo && undoRedoEmphasized, |
| undoLength < 1 && undoRedoDisabled, |
| ]} |
| data-test="undo-action" |
| iconSize="xl" |
| /> |
| </StyledUndoRedoButton> |
| </Tooltip> |
| <Tooltip |
| id="dashboard-redo-tooltip" |
| title={t('Redo the action')} |
| > |
| <StyledUndoRedoButton |
| buttonStyle="link" |
| disabled={redoLength < 1} |
| onClick={ |
| redoLength > 0 ? boundActionCreators.onRedo : undefined |
| } |
| > |
| <Icons.Redo |
| css={[ |
| undoRedoStyle, |
| emphasizeRedo && undoRedoEmphasized, |
| redoLength < 1 && undoRedoDisabled, |
| ]} |
| data-test="redo-action" |
| iconSize="xl" |
| /> |
| </StyledUndoRedoButton> |
| </Tooltip> |
| </div> |
| <Button |
| css={discardBtnStyle} |
| buttonSize="small" |
| onClick={discardChanges} |
| buttonStyle="secondary" |
| data-test="discard-changes-button" |
| aria-label={t('Discard')} |
| > |
| {t('Discard')} |
| </Button> |
| <Button |
| css={saveBtnStyle} |
| buttonSize="small" |
| disabled={!hasUnsavedChanges} |
| buttonStyle="primary" |
| onClick={overwriteDashboard} |
| data-test="header-save-button" |
| aria-label={t('Save')} |
| > |
| <Icons.SaveOutlined iconSize="m" /> |
| {t('Save')} |
| </Button> |
| </div> |
| )} |
| </div> |
| )} |
| {editMode ? ( |
| <UndoRedoKeyListeners onUndo={handleCtrlZ} onRedo={handleCtrlY} /> |
| ) : ( |
| <div css={actionButtonsStyle}> |
| {NavExtension && <NavExtension />} |
| {userCanEdit && ( |
| <Button |
| buttonStyle="secondary" |
| onClick={() => { |
| toggleEditMode(); |
| boundActionCreators.clearDashboardHistory?.(); // Resets the `past` as an empty array |
| }} |
| data-test="edit-dashboard-button" |
| className="action-button" |
| css={editButtonStyle} |
| aria-label={t('Edit dashboard')} |
| > |
| {t('Edit dashboard')} |
| </Button> |
| )} |
| </div> |
| )} |
| </div> |
| ), |
| [ |
| NavExtension, |
| boundActionCreators.onRedo, |
| boundActionCreators.onUndo, |
| boundActionCreators.clearDashboardHistory, |
| editMode, |
| emphasizeRedo, |
| emphasizeUndo, |
| handleCtrlY, |
| handleCtrlZ, |
| hasUnsavedChanges, |
| overwriteDashboard, |
| redoLength, |
| toggleEditMode, |
| undoLength, |
| userCanEdit, |
| userCanSaveAs, |
| ], |
| ); |
| |
| const handleReportDelete = async report => { |
| await dispatch(deleteActiveReport(report)); |
| setCurrentReportDeleting(null); |
| }; |
| |
| const [menu, isDropdownVisible, setIsDropdownVisible] = useHeaderActionsMenu({ |
| addSuccessToast: boundActionCreators.addSuccessToast, |
| addDangerToast: boundActionCreators.addDangerToast, |
| dashboardInfo, |
| dashboardId: dashboardInfo.id, |
| dashboardTitle, |
| dataMask, |
| layout, |
| expandedSlices, |
| customCss, |
| colorNamespace, |
| colorScheme, |
| onSave: boundActionCreators.onSave, |
| forceRefreshAllCharts: forceRefresh, |
| refreshFrequency, |
| shouldPersistRefreshFrequency, |
| editMode, |
| hasUnsavedChanges, |
| userCanEdit, |
| userCanShare, |
| userCanSave: userCanSaveAs, |
| userCanCurate, |
| isLoading, |
| showReportModal, |
| showPropertiesModal, |
| showRefreshModal, |
| setCurrentReportDeleting, |
| manageEmbedded: showEmbedModal, |
| lastModifiedTime: actualLastModifiedTime, |
| logEvent: boundActionCreators.logEvent, |
| }); |
| return ( |
| <div |
| css={headerContainerStyle} |
| data-test="dashboard-header-container" |
| data-test-id={dashboardInfo.id} |
| className="dashboard-header-container" |
| > |
| <PageHeaderWithActions |
| editableTitleProps={editableTitleProps} |
| certificatiedBadgeProps={certifiedBadgeProps} |
| faveStarProps={faveStarProps} |
| titlePanelAdditionalItems={titlePanelAdditionalItems} |
| rightPanelAdditionalItems={rightPanelAdditionalItems} |
| menuDropdownProps={{ |
| open: isDropdownVisible, |
| onOpenChange: setIsDropdownVisible, |
| }} |
| additionalActionsMenu={menu} |
| showFaveStar={user?.userId && dashboardInfo?.id} |
| showTitlePanelItems |
| /> |
| {showingPropertiesModal && ( |
| <PropertiesModal |
| dashboardId={dashboardInfo.id} |
| dashboardInfo={dashboardInfo} |
| dashboardTitle={dashboardTitle} |
| show={showingPropertiesModal} |
| onHide={hidePropertiesModal} |
| colorScheme={colorScheme} |
| onSubmit={handleOnPropertiesChange} |
| onlyApply |
| /> |
| )} |
| {showingRefreshModal && ( |
| <RefreshIntervalModal |
| show={showingRefreshModal} |
| onHide={hideRefreshModal} |
| refreshFrequency={refreshFrequency} |
| onChange={handleRefreshChange} |
| editMode={editMode} |
| refreshLimit={ |
| dashboardInfo.common?.conf |
| ?.SUPERSET_DASHBOARD_PERIODICAL_REFRESH_LIMIT |
| } |
| refreshWarning={ |
| dashboardInfo.common?.conf?.DASHBOARD_AUTO_REFRESH_WARNING_MESSAGE |
| } |
| addSuccessToast={boundActionCreators.addSuccessToast} |
| /> |
| )} |
| |
| <ReportModal |
| userId={user.userId} |
| show={showingReportModal} |
| onHide={hideReportModal} |
| userEmail={user.email} |
| dashboardId={dashboardInfo.id} |
| creationMethod="dashboards" |
| /> |
| |
| {currentReportDeleting && ( |
| <DeleteModal |
| description={t( |
| 'This action will permanently delete %s.', |
| currentReportDeleting?.name, |
| )} |
| onConfirm={() => { |
| if (currentReportDeleting) { |
| handleReportDelete(currentReportDeleting); |
| } |
| }} |
| onHide={() => setCurrentReportDeleting(null)} |
| open |
| title={t('Delete Report?')} |
| /> |
| )} |
| |
| <OverwriteConfirm /> |
| |
| {userCanCurate && ( |
| <DashboardEmbedModal |
| show={showingEmbedModal} |
| onHide={hideEmbedModal} |
| dashboardId={dashboardInfo.id} |
| /> |
| )} |
| <Global |
| styles={css` |
| .ant-menu-vertical { |
| border-right: none; |
| } |
| `} |
| /> |
| |
| <UnsavedChangesModal |
| title={t('Save changes to your dashboard?')} |
| body={t("If you don't save, changes will be lost.")} |
| showModal={showUnsavedChangesModal} |
| onHide={() => setShowUnsavedChangesModal(false)} |
| onConfirmNavigation={handleConfirmNavigation} |
| handleSave={handleSaveAndCloseModal} |
| /> |
| </div> |
| ); |
| }; |
| |
| export default Header; |