client/app/pages/dashboards/DashboardPage.jsx (159 lines of code) (raw):

import { isEmpty } from "lodash"; import React, { useState, useEffect } from "react"; import PropTypes from "prop-types"; import cx from "classnames"; import Button from "antd/lib/button"; import Checkbox from "antd/lib/checkbox"; import routeWithUserSession from "@/components/ApplicationArea/routeWithUserSession"; import DashboardGrid from "@/components/dashboards/DashboardGrid"; import Parameters from "@/components/Parameters"; import Filters from "@/components/Filters"; import { Dashboard } from "@/services/dashboard"; import recordEvent from "@/services/recordEvent"; import resizeObserver from "@/services/resizeObserver"; import routes from "@/services/routes"; import useImmutableCallback from "@/lib/hooks/useImmutableCallback"; import useDashboard from "./hooks/useDashboard"; import DashboardHeader from "./components/DashboardHeader"; import "./DashboardPage.less"; function DashboardSettings({ dashboardOptions }) { const { dashboard, updateDashboard } = dashboardOptions; return ( <div className="m-b-10 p-15 bg-white tiled"> <Checkbox checked={!!dashboard.dashboard_filters_enabled} onChange={({ target }) => updateDashboard({ dashboard_filters_enabled: target.checked })} data-test="DashboardFiltersCheckbox"> Use Dashboard Level Filters </Checkbox> </div> ); } DashboardSettings.propTypes = { dashboardOptions: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types }; function AddWidgetContainer({ dashboardOptions, className, ...props }) { const { showAddTextboxDialog, showAddWidgetDialog } = dashboardOptions; return ( <div className={cx("add-widget-container", className)} {...props}> <h2> <i className="zmdi zmdi-widgets" /> <span className="hidden-xs hidden-sm"> Widgets are individual query visualizations or text boxes you can place on your dashboard in various arrangements. </span> </h2> <div> <Button className="m-r-15" onClick={showAddTextboxDialog} data-test="AddTextboxButton"> Add Textbox </Button> <Button type="primary" onClick={showAddWidgetDialog} data-test="AddWidgetButton"> Add Widget </Button> </div> </div> ); } AddWidgetContainer.propTypes = { dashboardOptions: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types className: PropTypes.string, }; function DashboardComponent(props) { const dashboardOptions = useDashboard(props.dashboard); const { dashboard, filters, setFilters, loadDashboard, loadWidget, removeWidget, saveDashboardLayout, globalParameters, refreshDashboard, refreshWidget, editingLayout, setGridDisabled, } = dashboardOptions; const [pageContainer, setPageContainer] = useState(null); const [bottomPanelStyles, setBottomPanelStyles] = useState({}); useEffect(() => { if (pageContainer) { const unobserve = resizeObserver(pageContainer, () => { if (editingLayout) { const style = window.getComputedStyle(pageContainer, null); const bounds = pageContainer.getBoundingClientRect(); const paddingLeft = parseFloat(style.paddingLeft) || 0; const paddingRight = parseFloat(style.paddingRight) || 0; setBottomPanelStyles({ left: Math.round(bounds.left) + paddingRight, width: pageContainer.clientWidth - paddingLeft - paddingRight, }); } // reflow grid when container changes its size window.dispatchEvent(new Event("resize")); }); return unobserve; } }, [pageContainer, editingLayout]); return ( <div className="container" ref={setPageContainer}> <DashboardHeader dashboardOptions={dashboardOptions} /> {!isEmpty(globalParameters) && ( <div className="dashboard-parameters m-b-10 p-15 bg-white tiled" data-test="DashboardParameters"> <Parameters parameters={globalParameters} onValuesChange={refreshDashboard} /> </div> )} {!isEmpty(filters) && ( <div className="m-b-10 p-15 bg-white tiled" data-test="DashboardFilters"> <Filters filters={filters} onChange={setFilters} /> </div> )} {editingLayout && <DashboardSettings dashboardOptions={dashboardOptions} />} <div id="dashboard-container"> <DashboardGrid dashboard={dashboard} widgets={dashboard.widgets} filters={filters} isEditing={editingLayout} onLayoutChange={editingLayout ? saveDashboardLayout : () => {}} onBreakpointChange={setGridDisabled} onLoadWidget={loadWidget} onRefreshWidget={refreshWidget} onRemoveWidget={removeWidget} onParameterMappingsChange={loadDashboard} /> </div> {editingLayout && <AddWidgetContainer dashboardOptions={dashboardOptions} style={bottomPanelStyles} />} </div> ); } DashboardComponent.propTypes = { dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types }; function DashboardPage({ dashboardSlug, onError }) { const [dashboard, setDashboard] = useState(null); const handleError = useImmutableCallback(onError); useEffect(() => { Dashboard.get({ slug: dashboardSlug }) .then(dashboardData => { recordEvent("view", "dashboard", dashboardData.id); setDashboard(dashboardData); }) .catch(handleError); }, [dashboardSlug, handleError]); return <div className="dashboard-page">{dashboard && <DashboardComponent dashboard={dashboard} />}</div>; } DashboardPage.propTypes = { dashboardSlug: PropTypes.string.isRequired, onError: PropTypes.func, }; DashboardPage.defaultProps = { onError: PropTypes.func, }; routes.register( "Dashboards.ViewOrEdit", routeWithUserSession({ path: "/dashboard/:dashboardSlug", render: pageProps => <DashboardPage {...pageProps} />, }) );