client/app/pages/dashboards/components/DashboardHeader.jsx (273 lines of code) (raw):
import React from "react";
import cx from "classnames";
import PropTypes from "prop-types";
import { map, includes } from "lodash";
import Button from "antd/lib/button";
import Dropdown from "antd/lib/dropdown";
import Menu from "antd/lib/menu";
import EllipsisOutlinedIcon from "@ant-design/icons/EllipsisOutlined";
import Modal from "antd/lib/modal";
import Tooltip from "@/components/Tooltip";
import FavoritesControl from "@/components/FavoritesControl";
import EditInPlace from "@/components/EditInPlace";
import PlainButton from "@/components/PlainButton";
import { DashboardTagsControl } from "@/components/tags-control/TagsControl";
import getTags from "@/services/getTags";
import { clientConfig } from "@/services/auth";
import { policy } from "@/services/policy";
import { durationHumanize } from "@/lib/utils";
import { DashboardStatusEnum } from "../hooks/useDashboard";
import "./DashboardHeader.less";
function getDashboardTags() {
return getTags("api/dashboards/tags").then(tags => map(tags, t => t.name));
}
function buttonType(value) {
return value ? "primary" : "default";
}
function DashboardPageTitle({ dashboardConfiguration }) {
const { dashboard, canEditDashboard, updateDashboard, editingLayout } = dashboardConfiguration;
return (
<div className="title-with-tags">
<div className="page-title">
<FavoritesControl item={dashboard} />
<h3>
<EditInPlace
isEditable={editingLayout}
onDone={name => updateDashboard({ name })}
value={dashboard.name}
ignoreBlanks
/>
</h3>
<Tooltip title={dashboard.user.name} placement="bottom">
<img src={dashboard.user.profile_image_url} className="profile-image" alt={dashboard.user.name} />
</Tooltip>
</div>
<DashboardTagsControl
tags={dashboard.tags}
isDraft={dashboard.is_draft}
isArchived={dashboard.is_archived}
canEdit={canEditDashboard}
getAvailableTags={getDashboardTags}
onEdit={tags => updateDashboard({ tags })}
/>
</div>
);
}
DashboardPageTitle.propTypes = {
dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
function RefreshButton({ dashboardConfiguration }) {
const { refreshRate, setRefreshRate, disableRefreshRate, refreshing, refreshDashboard } = dashboardConfiguration;
const allowedIntervals = policy.getDashboardRefreshIntervals();
const refreshRateOptions = clientConfig.dashboardRefreshIntervals;
const onRefreshRateSelected = ({ key }) => {
const parsedRefreshRate = parseFloat(key);
if (parsedRefreshRate) {
setRefreshRate(parsedRefreshRate);
refreshDashboard();
} else {
disableRefreshRate();
}
};
return (
<Button.Group>
<Tooltip title={refreshRate ? `Auto Refreshing every ${durationHumanize(refreshRate)}` : null}>
<Button type={buttonType(refreshRate)} onClick={() => refreshDashboard()}>
<i className={cx("zmdi zmdi-refresh m-r-5", { "zmdi-hc-spin": refreshing })} aria-hidden="true" />
{refreshRate ? durationHumanize(refreshRate) : "Refresh"}
</Button>
</Tooltip>
<Dropdown
trigger={["click"]}
placement="bottomRight"
overlay={
<Menu onClick={onRefreshRateSelected} selectedKeys={[`${refreshRate}`]}>
{refreshRateOptions.map(option => (
<Menu.Item key={`${option}`} disabled={!includes(allowedIntervals, option)}>
{durationHumanize(option)}
</Menu.Item>
))}
{refreshRate && <Menu.Item key={null}>Disable auto refresh</Menu.Item>}
</Menu>
}>
<Button className="icon-button hidden-xs" type={buttonType(refreshRate)}>
<i className="fa fa-angle-down" aria-hidden="true" />
<span className="sr-only">Split button!</span>
</Button>
</Dropdown>
</Button.Group>
);
}
RefreshButton.propTypes = {
dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
function DashboardMoreOptionsButton({ dashboardConfiguration }) {
const {
dashboard,
setEditingLayout,
togglePublished,
archiveDashboard,
managePermissions,
gridDisabled,
isDashboardOwnerOrAdmin,
} = dashboardConfiguration;
const archive = () => {
Modal.confirm({
title: "Archive Dashboard",
content: `Are you sure you want to archive the "${dashboard.name}" dashboard?`,
okText: "Archive",
okType: "danger",
onOk: archiveDashboard,
maskClosable: true,
autoFocusButton: null,
});
};
return (
<Dropdown
trigger={["click"]}
placement="bottomRight"
overlay={
<Menu data-test="DashboardMoreButtonMenu">
<Menu.Item className={cx({ hidden: gridDisabled })}>
<PlainButton onClick={() => setEditingLayout(true)}>Edit</PlainButton>
</Menu.Item>
{clientConfig.showPermissionsControl && isDashboardOwnerOrAdmin && (
<Menu.Item>
<PlainButton onClick={managePermissions}>Manage Permissions</PlainButton>
</Menu.Item>
)}
{!clientConfig.disablePublish && !dashboard.is_draft && (
<Menu.Item>
<PlainButton onClick={togglePublished}>Unpublish</PlainButton>
</Menu.Item>
)}
<Menu.Item>
<PlainButton onClick={archive}>Archive</PlainButton>
</Menu.Item>
</Menu>
}>
<Button className="icon-button m-l-5" data-test="DashboardMoreButton" aria-label="More actions">
<EllipsisOutlinedIcon rotate={90} aria-hidden="true" />
</Button>
</Dropdown>
);
}
DashboardMoreOptionsButton.propTypes = {
dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
function DashboardControl({ dashboardConfiguration, headerExtra }) {
const {
dashboard,
togglePublished,
canEditDashboard,
fullscreen,
toggleFullscreen,
showShareDashboardDialog,
} = dashboardConfiguration;
const showPublishButton = dashboard.is_draft;
const showRefreshButton = true;
const showFullscreenButton = !dashboard.is_draft;
const canShareDashboard = canEditDashboard && !dashboard.is_draft;
const showShareButton = !clientConfig.disablePublicUrls && (dashboard.publicAccessEnabled || canShareDashboard);
const showMoreOptionsButton = canEditDashboard;
return (
<div className="dashboard-control">
{!dashboard.is_archived && (
<span className="hidden-print">
{showPublishButton && (
<Button className="m-r-5 hidden-xs" onClick={togglePublished}>
<span className="fa fa-paper-plane m-r-5" /> Publish
</Button>
)}
{showRefreshButton && <RefreshButton dashboardConfiguration={dashboardConfiguration} />}
{showFullscreenButton && (
<Tooltip className="hidden-xs" title="Enable/Disable Fullscreen display">
<Button
type={buttonType(fullscreen)}
className="icon-button m-l-5"
onClick={toggleFullscreen}
aria-label="Toggle fullscreen display">
<i className="zmdi zmdi-fullscreen" aria-hidden="true" />
</Button>
</Tooltip>
)}
{headerExtra}
{showShareButton && (
<Tooltip title="Dashboard Sharing Options">
<Button
className="icon-button m-l-5"
type={buttonType(dashboard.publicAccessEnabled)}
onClick={showShareDashboardDialog}
data-test="OpenShareForm"
aria-label="Share">
<i className="zmdi zmdi-share" aria-hidden="true" />
</Button>
</Tooltip>
)}
{showMoreOptionsButton && <DashboardMoreOptionsButton dashboardConfiguration={dashboardConfiguration} />}
</span>
)}
</div>
);
}
DashboardControl.propTypes = {
dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
headerExtra: PropTypes.node,
};
function DashboardEditControl({ dashboardConfiguration, headerExtra }) {
const {
setEditingLayout,
doneBtnClickedWhileSaving,
dashboardStatus,
retrySaveDashboardLayout,
} = dashboardConfiguration;
let status;
if (dashboardStatus === DashboardStatusEnum.SAVED) {
status = <span className="save-status">Saved</span>;
} else if (dashboardStatus === DashboardStatusEnum.SAVING) {
status = (
<span className="save-status" data-saving>
Saving
</span>
);
} else {
status = (
<span className="save-status" data-error>
Saving Failed
</span>
);
}
return (
<div className="dashboard-control">
{status}
{dashboardStatus === DashboardStatusEnum.SAVING_FAILED ? (
<Button type="primary" onClick={retrySaveDashboardLayout}>
Retry
</Button>
) : (
<Button loading={doneBtnClickedWhileSaving} type="primary" onClick={() => setEditingLayout(false)}>
{!doneBtnClickedWhileSaving && <i className="fa fa-check m-r-5" aria-hidden="true" />} Done Editing
</Button>
)}
{headerExtra}
</div>
);
}
DashboardEditControl.propTypes = {
dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
headerExtra: PropTypes.node,
};
export default function DashboardHeader({ dashboardConfiguration, headerExtra }) {
const { editingLayout } = dashboardConfiguration;
const DashboardControlComponent = editingLayout ? DashboardEditControl : DashboardControl;
return (
<div className="dashboard-header">
<DashboardPageTitle dashboardConfiguration={dashboardConfiguration} />
<DashboardControlComponent dashboardConfiguration={dashboardConfiguration} headerExtra={headerExtra} />
</div>
);
}
DashboardHeader.propTypes = {
dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
headerExtra: PropTypes.node,
};