748 lines
24 KiB
JavaScript
748 lines
24 KiB
JavaScript
/**
|
|
* 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 moment from 'moment';
|
|
import { PureComponent } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import {
|
|
styled,
|
|
css,
|
|
isFeatureEnabled,
|
|
FeatureFlag,
|
|
t,
|
|
getExtensionsRegistry,
|
|
} from '@superset-ui/core';
|
|
import { Global } from '@emotion/react';
|
|
import {
|
|
LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD,
|
|
LOG_ACTIONS_FORCE_REFRESH_DASHBOARD,
|
|
LOG_ACTIONS_TOGGLE_EDIT_DASHBOARD,
|
|
} from 'src/logger/LogUtils';
|
|
import Icons from 'src/components/Icons';
|
|
import { Button } from 'src/components/';
|
|
import { findPermission } from 'src/utils/findPermission';
|
|
import { Tooltip } from 'src/components/Tooltip';
|
|
import { safeStringify } from 'src/utils/safeStringify';
|
|
import ConnectedHeaderActionsDropdown from 'src/dashboard/components/Header/HeaderActionsDropdown';
|
|
import PublishedStatus from 'src/dashboard/components/PublishedStatus';
|
|
import UndoRedoKeyListeners from 'src/dashboard/components/UndoRedoKeyListeners';
|
|
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
|
|
import { chartPropShape } from 'src/dashboard/util/propShapes';
|
|
import getOwnerName from 'src/utils/getOwnerName';
|
|
import {
|
|
UNDO_LIMIT,
|
|
SAVE_TYPE_OVERWRITE,
|
|
DASHBOARD_POSITION_DATA_LIMIT,
|
|
} from 'src/dashboard/util/constants';
|
|
import setPeriodicRunner, {
|
|
stopPeriodicRender,
|
|
} from 'src/dashboard/util/setPeriodicRunner';
|
|
import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
|
|
import MetadataBar, { MetadataType } from 'src/components/MetadataBar';
|
|
import DashboardEmbedModal from '../EmbeddedModal';
|
|
import OverwriteConfirm from '../OverwriteConfirm';
|
|
|
|
const extensionsRegistry = getExtensionsRegistry();
|
|
|
|
const propTypes = {
|
|
addSuccessToast: PropTypes.func.isRequired,
|
|
addDangerToast: PropTypes.func.isRequired,
|
|
addWarningToast: PropTypes.func.isRequired,
|
|
user: PropTypes.object, // UserWithPermissionsAndRoles,
|
|
dashboardInfo: PropTypes.object.isRequired,
|
|
dashboardTitle: PropTypes.string,
|
|
dataMask: PropTypes.object.isRequired,
|
|
charts: PropTypes.objectOf(chartPropShape).isRequired,
|
|
layout: PropTypes.object.isRequired,
|
|
expandedSlices: PropTypes.object,
|
|
customCss: PropTypes.string,
|
|
colorNamespace: PropTypes.string,
|
|
colorScheme: PropTypes.string,
|
|
setColorScheme: PropTypes.func.isRequired,
|
|
setUnsavedChanges: PropTypes.func.isRequired,
|
|
isStarred: PropTypes.bool.isRequired,
|
|
isPublished: PropTypes.bool.isRequired,
|
|
isLoading: PropTypes.bool.isRequired,
|
|
onSave: PropTypes.func.isRequired,
|
|
onChange: PropTypes.func.isRequired,
|
|
fetchFaveStar: PropTypes.func.isRequired,
|
|
fetchCharts: PropTypes.func.isRequired,
|
|
saveFaveStar: PropTypes.func.isRequired,
|
|
savePublished: PropTypes.func.isRequired,
|
|
updateDashboardTitle: PropTypes.func.isRequired,
|
|
editMode: PropTypes.bool.isRequired,
|
|
setEditMode: PropTypes.func.isRequired,
|
|
showBuilderPane: PropTypes.func.isRequired,
|
|
updateCss: PropTypes.func.isRequired,
|
|
logEvent: PropTypes.func.isRequired,
|
|
hasUnsavedChanges: PropTypes.bool.isRequired,
|
|
maxUndoHistoryExceeded: PropTypes.bool.isRequired,
|
|
lastModifiedTime: PropTypes.number.isRequired,
|
|
|
|
// redux
|
|
onRefresh: PropTypes.func.isRequired,
|
|
onUndo: PropTypes.func.isRequired,
|
|
onRedo: PropTypes.func.isRequired,
|
|
undoLength: PropTypes.number.isRequired,
|
|
redoLength: PropTypes.number.isRequired,
|
|
setMaxUndoHistoryExceeded: PropTypes.func.isRequired,
|
|
maxUndoHistoryToast: PropTypes.func.isRequired,
|
|
refreshFrequency: PropTypes.number,
|
|
shouldPersistRefreshFrequency: PropTypes.bool.isRequired,
|
|
setRefreshFrequency: PropTypes.func.isRequired,
|
|
dashboardInfoChanged: PropTypes.func.isRequired,
|
|
dashboardTitleChanged: PropTypes.func.isRequired,
|
|
};
|
|
|
|
const defaultProps = {
|
|
colorNamespace: undefined,
|
|
colorScheme: undefined,
|
|
};
|
|
|
|
const headerContainerStyle = theme => css`
|
|
border-bottom: 1px solid ${theme.colors.grayscale.light2};
|
|
`;
|
|
|
|
const editButtonStyle = theme => css`
|
|
color: ${theme.colors.primary.dark2};
|
|
`;
|
|
|
|
const actionButtonsStyle = theme => css`
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.action-schedule-report {
|
|
margin-left: ${theme.gridUnit * 2}px;
|
|
}
|
|
|
|
.undoRedo {
|
|
display: flex;
|
|
margin-right: ${theme.gridUnit * 2}px;
|
|
}
|
|
`;
|
|
|
|
const StyledUndoRedoButton = styled(Button)`
|
|
// TODO: check if we need this.
|
|
padding: 0;
|
|
&:hover {
|
|
background: transparent;
|
|
}
|
|
`;
|
|
|
|
const undoRedoStyle = theme => css`
|
|
color: ${theme.colors.grayscale.light1};
|
|
&:hover {
|
|
color: ${theme.colors.grayscale.base};
|
|
}
|
|
`;
|
|
|
|
const undoRedoEmphasized = theme => css`
|
|
color: ${theme.colors.grayscale.base};
|
|
`;
|
|
|
|
const undoRedoDisabled = theme => css`
|
|
color: ${theme.colors.grayscale.light2};
|
|
`;
|
|
|
|
const saveBtnStyle = theme => css`
|
|
min-width: ${theme.gridUnit * 17}px;
|
|
height: ${theme.gridUnit * 8}px;
|
|
`;
|
|
|
|
const discardBtnStyle = theme => css`
|
|
min-width: ${theme.gridUnit * 22}px;
|
|
height: ${theme.gridUnit * 8}px;
|
|
`;
|
|
|
|
class Header extends PureComponent {
|
|
static discardChanges() {
|
|
const url = new URL(window.location.href);
|
|
|
|
url.searchParams.delete('edit');
|
|
window.location.assign(url);
|
|
}
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
didNotifyMaxUndoHistoryToast: false,
|
|
emphasizeUndo: false,
|
|
emphasizeRedo: false,
|
|
showingPropertiesModal: false,
|
|
isDropdownVisible: false,
|
|
};
|
|
|
|
this.handleChangeText = this.handleChangeText.bind(this);
|
|
this.handleCtrlZ = this.handleCtrlZ.bind(this);
|
|
this.handleCtrlY = this.handleCtrlY.bind(this);
|
|
this.toggleEditMode = this.toggleEditMode.bind(this);
|
|
this.forceRefresh = this.forceRefresh.bind(this);
|
|
this.startPeriodicRender = this.startPeriodicRender.bind(this);
|
|
this.overwriteDashboard = this.overwriteDashboard.bind(this);
|
|
this.showPropertiesModal = this.showPropertiesModal.bind(this);
|
|
this.hidePropertiesModal = this.hidePropertiesModal.bind(this);
|
|
this.setIsDropdownVisible = this.setIsDropdownVisible.bind(this);
|
|
}
|
|
|
|
componentDidMount() {
|
|
const { refreshFrequency } = this.props;
|
|
this.startPeriodicRender(refreshFrequency * 1000);
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
if (this.props.refreshFrequency !== prevProps.refreshFrequency) {
|
|
const { refreshFrequency } = this.props;
|
|
this.startPeriodicRender(refreshFrequency * 1000);
|
|
}
|
|
}
|
|
|
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
if (
|
|
UNDO_LIMIT - nextProps.undoLength <= 0 &&
|
|
!this.state.didNotifyMaxUndoHistoryToast
|
|
) {
|
|
this.setState(() => ({ didNotifyMaxUndoHistoryToast: true }));
|
|
this.props.maxUndoHistoryToast();
|
|
}
|
|
if (
|
|
nextProps.undoLength > UNDO_LIMIT &&
|
|
!this.props.maxUndoHistoryExceeded
|
|
) {
|
|
this.props.setMaxUndoHistoryExceeded();
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
stopPeriodicRender(this.refreshTimer);
|
|
this.props.setRefreshFrequency(0);
|
|
clearTimeout(this.ctrlYTimeout);
|
|
clearTimeout(this.ctrlZTimeout);
|
|
}
|
|
|
|
handleChangeText(nextText) {
|
|
const { updateDashboardTitle, onChange } = this.props;
|
|
if (nextText && this.props.dashboardTitle !== nextText) {
|
|
updateDashboardTitle(nextText);
|
|
onChange();
|
|
}
|
|
}
|
|
|
|
setIsDropdownVisible(visible) {
|
|
this.setState({
|
|
isDropdownVisible: visible,
|
|
});
|
|
}
|
|
|
|
handleCtrlY() {
|
|
this.props.onRedo();
|
|
this.setState({ emphasizeRedo: true }, () => {
|
|
if (this.ctrlYTimeout) clearTimeout(this.ctrlYTimeout);
|
|
this.ctrlYTimeout = setTimeout(() => {
|
|
this.setState({ emphasizeRedo: false });
|
|
}, 100);
|
|
});
|
|
}
|
|
|
|
handleCtrlZ() {
|
|
this.props.onUndo();
|
|
this.setState({ emphasizeUndo: true }, () => {
|
|
if (this.ctrlZTimeout) clearTimeout(this.ctrlZTimeout);
|
|
this.ctrlZTimeout = setTimeout(() => {
|
|
this.setState({ emphasizeUndo: false });
|
|
}, 100);
|
|
});
|
|
}
|
|
|
|
forceRefresh() {
|
|
if (!this.props.isLoading) {
|
|
const chartList = Object.keys(this.props.charts);
|
|
this.props.logEvent(LOG_ACTIONS_FORCE_REFRESH_DASHBOARD, {
|
|
force: true,
|
|
interval: 0,
|
|
chartCount: chartList.length,
|
|
});
|
|
return this.props.onRefresh(
|
|
chartList,
|
|
true,
|
|
0,
|
|
this.props.dashboardInfo.id,
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
startPeriodicRender(interval) {
|
|
let intervalMessage;
|
|
|
|
if (interval) {
|
|
const { dashboardInfo } = this.props;
|
|
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 = moment.duration(interval, 'millisecond').humanize();
|
|
}
|
|
}
|
|
|
|
const periodicRender = () => {
|
|
const { fetchCharts, logEvent, charts, dashboardInfo } = this.props;
|
|
const { metadata } = dashboardInfo;
|
|
const immune = metadata.timed_refresh_immune_slices || [];
|
|
const affectedCharts = Object.values(charts)
|
|
.filter(chart => immune.indexOf(chart.id) === -1)
|
|
.map(chart => chart.id);
|
|
|
|
logEvent(LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD, {
|
|
interval,
|
|
chartCount: affectedCharts.length,
|
|
});
|
|
this.props.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,
|
|
false,
|
|
interval * 0.2,
|
|
dashboardInfo.id,
|
|
);
|
|
}
|
|
return fetchCharts(
|
|
affectedCharts,
|
|
true,
|
|
interval * 0.2,
|
|
dashboardInfo.id,
|
|
);
|
|
};
|
|
|
|
this.refreshTimer = setPeriodicRunner({
|
|
interval,
|
|
periodicRender,
|
|
refreshTimer: this.refreshTimer,
|
|
});
|
|
}
|
|
|
|
toggleEditMode() {
|
|
this.props.logEvent(LOG_ACTIONS_TOGGLE_EDIT_DASHBOARD, {
|
|
edit_mode: !this.props.editMode,
|
|
});
|
|
this.props.setEditMode(!this.props.editMode);
|
|
}
|
|
|
|
overwriteDashboard() {
|
|
const {
|
|
dashboardTitle,
|
|
layout: positions,
|
|
colorScheme,
|
|
colorNamespace,
|
|
customCss,
|
|
dashboardInfo,
|
|
refreshFrequency: currentRefreshFrequency,
|
|
shouldPersistRefreshFrequency,
|
|
lastModifiedTime,
|
|
slug,
|
|
} = this.props;
|
|
|
|
// check refresh frequency is for current session or persist
|
|
const refreshFrequency = shouldPersistRefreshFrequency
|
|
? currentRefreshFrequency
|
|
: dashboardInfo.metadata?.refresh_frequency;
|
|
|
|
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: lastModifiedTime,
|
|
owners: dashboardInfo.owners,
|
|
roles: dashboardInfo.roles,
|
|
slug,
|
|
metadata: {
|
|
...dashboardInfo?.metadata,
|
|
color_namespace: currentColorNamespace,
|
|
color_scheme: currentColorScheme,
|
|
positions,
|
|
refresh_frequency: refreshFrequency,
|
|
},
|
|
};
|
|
|
|
// make sure positions data less than DB storage limitation:
|
|
const positionJSONLength = safeStringify(positions).length;
|
|
const limit =
|
|
dashboardInfo.common.conf.SUPERSET_DASHBOARD_POSITION_DATA_LIMIT ||
|
|
DASHBOARD_POSITION_DATA_LIMIT;
|
|
if (positionJSONLength >= limit) {
|
|
this.props.addDangerToast(
|
|
t(
|
|
'Your dashboard is too large. Please reduce its size before saving it.',
|
|
),
|
|
);
|
|
} else {
|
|
if (positionJSONLength >= limit * 0.9) {
|
|
this.props.addWarningToast('Your dashboard is near the size limit.');
|
|
}
|
|
|
|
this.props.onSave(data, dashboardInfo.id, SAVE_TYPE_OVERWRITE);
|
|
}
|
|
}
|
|
|
|
showPropertiesModal() {
|
|
this.setState({ showingPropertiesModal: true });
|
|
}
|
|
|
|
hidePropertiesModal() {
|
|
this.setState({ showingPropertiesModal: false });
|
|
}
|
|
|
|
showEmbedModal = () => {
|
|
this.setState({ showingEmbedModal: true });
|
|
};
|
|
|
|
hideEmbedModal = () => {
|
|
this.setState({ showingEmbedModal: false });
|
|
};
|
|
|
|
getMetadataItems = () => {
|
|
const { dashboardInfo } = this.props;
|
|
return [
|
|
{
|
|
type: MetadataType.LastModified,
|
|
value: dashboardInfo.changed_on_delta_humanized,
|
|
modifiedBy:
|
|
getOwnerName(dashboardInfo.changed_by) || t('Not available'),
|
|
},
|
|
{
|
|
type: MetadataType.Owner,
|
|
createdBy: getOwnerName(dashboardInfo.created_by) || t('Not available'),
|
|
owners:
|
|
dashboardInfo.owners.length > 0
|
|
? dashboardInfo.owners.map(getOwnerName)
|
|
: t('None'),
|
|
createdOn: dashboardInfo.created_on_delta_humanized,
|
|
},
|
|
];
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
dashboardTitle,
|
|
layout,
|
|
expandedSlices,
|
|
customCss,
|
|
colorNamespace,
|
|
dataMask,
|
|
setColorScheme,
|
|
setUnsavedChanges,
|
|
colorScheme,
|
|
onUndo,
|
|
onRedo,
|
|
undoLength,
|
|
redoLength,
|
|
onChange,
|
|
onSave,
|
|
updateCss,
|
|
editMode,
|
|
isPublished,
|
|
user,
|
|
dashboardInfo,
|
|
hasUnsavedChanges,
|
|
isLoading,
|
|
refreshFrequency,
|
|
shouldPersistRefreshFrequency,
|
|
setRefreshFrequency,
|
|
lastModifiedTime,
|
|
logEvent,
|
|
} = this.props;
|
|
|
|
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 refreshLimit =
|
|
dashboardInfo.common?.conf?.SUPERSET_DASHBOARD_PERIODICAL_REFRESH_LIMIT;
|
|
const refreshWarning =
|
|
dashboardInfo.common?.conf
|
|
?.SUPERSET_DASHBOARD_PERIODICAL_REFRESH_WARNING_MESSAGE;
|
|
|
|
const handleOnPropertiesChange = updates => {
|
|
const { dashboardInfoChanged, dashboardTitleChanged } = this.props;
|
|
|
|
setColorScheme(updates.colorScheme);
|
|
dashboardInfoChanged({
|
|
slug: updates.slug,
|
|
metadata: JSON.parse(updates.jsonMetadata || '{}'),
|
|
certified_by: updates.certifiedBy,
|
|
certification_details: updates.certificationDetails,
|
|
owners: updates.owners,
|
|
roles: updates.roles,
|
|
});
|
|
setUnsavedChanges(true);
|
|
dashboardTitleChanged(updates.title);
|
|
};
|
|
|
|
const NavExtension = extensionsRegistry.get('dashboard.nav.right');
|
|
|
|
return (
|
|
<div
|
|
css={headerContainerStyle}
|
|
data-test="dashboard-header-container"
|
|
data-test-id={dashboardInfo.id}
|
|
className="dashboard-header-container"
|
|
>
|
|
<PageHeaderWithActions
|
|
editableTitleProps={{
|
|
title: dashboardTitle,
|
|
canEdit: userCanEdit && editMode,
|
|
onSave: this.handleChangeText,
|
|
placeholder: t('Add the name of the dashboard'),
|
|
label: t('Dashboard title'),
|
|
showTooltip: false,
|
|
}}
|
|
certificatiedBadgeProps={{
|
|
certifiedBy: dashboardInfo.certified_by,
|
|
details: dashboardInfo.certification_details,
|
|
}}
|
|
faveStarProps={{
|
|
itemId: dashboardInfo.id,
|
|
fetchFaveStar: this.props.fetchFaveStar,
|
|
saveFaveStar: this.props.saveFaveStar,
|
|
isStarred: this.props.isStarred,
|
|
showTooltip: true,
|
|
}}
|
|
titlePanelAdditionalItems={[
|
|
!editMode && (
|
|
<PublishedStatus
|
|
dashboardId={dashboardInfo.id}
|
|
isPublished={isPublished}
|
|
savePublished={this.props.savePublished}
|
|
userCanEdit={userCanEdit}
|
|
userCanSave={userCanSaveAs}
|
|
visible={!editMode}
|
|
/>
|
|
),
|
|
!editMode && (
|
|
<MetadataBar
|
|
items={this.getMetadataItems()}
|
|
tooltipPlacement="bottom"
|
|
/>
|
|
),
|
|
]}
|
|
rightPanelAdditionalItems={
|
|
<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 && onUndo}
|
|
>
|
|
<Icons.Undo
|
|
css={[
|
|
undoRedoStyle,
|
|
this.state.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 && onRedo}
|
|
>
|
|
<Icons.Redo
|
|
css={[
|
|
undoRedoStyle,
|
|
this.state.emphasizeRedo && undoRedoEmphasized,
|
|
redoLength < 1 && undoRedoDisabled,
|
|
]}
|
|
data-test="redo-action"
|
|
iconSize="xl"
|
|
/>
|
|
</StyledUndoRedoButton>
|
|
</Tooltip>
|
|
</div>
|
|
<Button
|
|
css={discardBtnStyle}
|
|
buttonSize="small"
|
|
onClick={this.constructor.discardChanges}
|
|
buttonStyle="default"
|
|
data-test="discard-changes-button"
|
|
aria-label={t('Discard')}
|
|
>
|
|
{t('Discard')}
|
|
</Button>
|
|
<Button
|
|
css={saveBtnStyle}
|
|
buttonSize="small"
|
|
disabled={!hasUnsavedChanges}
|
|
buttonStyle="primary"
|
|
onClick={this.overwriteDashboard}
|
|
data-test="header-save-button"
|
|
aria-label={t('Save')}
|
|
>
|
|
{t('Save')}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
{editMode ? (
|
|
<UndoRedoKeyListeners
|
|
onUndo={this.handleCtrlZ}
|
|
onRedo={this.handleCtrlY}
|
|
/>
|
|
) : (
|
|
<div css={actionButtonsStyle}>
|
|
{NavExtension && <NavExtension />}
|
|
{userCanEdit && (
|
|
<Button
|
|
buttonStyle="secondary"
|
|
onClick={this.toggleEditMode}
|
|
data-test="edit-dashboard-button"
|
|
className="action-button"
|
|
css={editButtonStyle}
|
|
aria-label={t('Edit dashboard')}
|
|
>
|
|
{t('Edit dashboard')}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
}
|
|
menuDropdownProps={{
|
|
getPopupContainer: triggerNode =>
|
|
triggerNode.closest('.header-with-actions'),
|
|
visible: this.state.isDropdownVisible,
|
|
onVisibleChange: this.setIsDropdownVisible,
|
|
}}
|
|
additionalActionsMenu={
|
|
<ConnectedHeaderActionsDropdown
|
|
addSuccessToast={this.props.addSuccessToast}
|
|
addDangerToast={this.props.addDangerToast}
|
|
dashboardId={dashboardInfo.id}
|
|
dashboardTitle={dashboardTitle}
|
|
dashboardInfo={dashboardInfo}
|
|
dataMask={dataMask}
|
|
layout={layout}
|
|
expandedSlices={expandedSlices}
|
|
customCss={customCss}
|
|
colorNamespace={colorNamespace}
|
|
colorScheme={colorScheme}
|
|
onSave={onSave}
|
|
onChange={onChange}
|
|
forceRefreshAllCharts={this.forceRefresh}
|
|
startPeriodicRender={this.startPeriodicRender}
|
|
refreshFrequency={refreshFrequency}
|
|
shouldPersistRefreshFrequency={shouldPersistRefreshFrequency}
|
|
setRefreshFrequency={setRefreshFrequency}
|
|
updateCss={updateCss}
|
|
editMode={editMode}
|
|
hasUnsavedChanges={hasUnsavedChanges}
|
|
userCanEdit={userCanEdit}
|
|
userCanShare={userCanShare}
|
|
userCanSave={userCanSaveAs}
|
|
userCanCurate={userCanCurate}
|
|
isLoading={isLoading}
|
|
showPropertiesModal={this.showPropertiesModal}
|
|
manageEmbedded={this.showEmbedModal}
|
|
refreshLimit={refreshLimit}
|
|
refreshWarning={refreshWarning}
|
|
lastModifiedTime={lastModifiedTime}
|
|
isDropdownVisible={this.state.isDropdownVisible}
|
|
setIsDropdownVisible={this.setIsDropdownVisible}
|
|
logEvent={logEvent}
|
|
/>
|
|
}
|
|
showFaveStar={user?.userId && dashboardInfo?.id}
|
|
showTitlePanelItems
|
|
/>
|
|
{this.state.showingPropertiesModal && (
|
|
<PropertiesModal
|
|
dashboardId={dashboardInfo.id}
|
|
dashboardInfo={dashboardInfo}
|
|
dashboardTitle={dashboardTitle}
|
|
show={this.state.showingPropertiesModal}
|
|
onHide={this.hidePropertiesModal}
|
|
colorScheme={this.props.colorScheme}
|
|
onSubmit={handleOnPropertiesChange}
|
|
onlyApply
|
|
/>
|
|
)}
|
|
|
|
<OverwriteConfirm />
|
|
|
|
{userCanCurate && (
|
|
<DashboardEmbedModal
|
|
show={this.state.showingEmbedModal}
|
|
onHide={this.hideEmbedModal}
|
|
dashboardId={dashboardInfo.id}
|
|
/>
|
|
)}
|
|
<Global
|
|
styles={css`
|
|
.ant-menu-vertical {
|
|
border-right: none;
|
|
}
|
|
`}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
Header.propTypes = propTypes;
|
|
Header.defaultProps = defaultProps;
|
|
|
|
export default Header;
|