Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Public Dashboard Page to React #4203

Closed
wants to merge 12 commits into from
2 changes: 1 addition & 1 deletion client/app/components/dashboards/DashboardGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const WidgetType = PropTypes.shape({
const SINGLE = 'single-column';
const MULTI = 'multi-column';

class DashboardGrid extends React.Component {
export class DashboardGrid extends React.Component {
static propTypes = {
isEditing: PropTypes.bool.isRequired,
isPublic: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ function VisualizationWidgetFooter({ widget, isPublic, onRefresh, onExpand }) {
}
};

return (
return widgetQueryResult ? (
<>
<span>
{(!isPublic && !!widgetQueryResult) && (
{!isPublic && (
<a
className="refresh-button hidden-print btn btn-sm btn-default btn-transparent"
onClick={() => refreshWidget(1)}
Expand Down Expand Up @@ -162,7 +162,7 @@ function VisualizationWidgetFooter({ widget, isPublic, onRefresh, onExpand }) {
</a>
</span>
</>
);
) : null;
}

VisualizationWidgetFooter.propTypes = {
Expand Down
115 changes: 115 additions & 0 deletions client/app/pages/dashboards/PublicDashboardPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React from 'react';
import { isEmpty } from 'lodash';
import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';
import { BigMessage } from '@/components/BigMessage';
import { PageHeader } from '@/components/PageHeader';
import { Parameters } from '@/components/Parameters';
import { DashboardGrid } from '@/components/dashboards/DashboardGrid';
import { Filters } from '@/components/Filters';
import { Dashboard } from '@/services/dashboard';
import { $route as ngRoute } from '@/services/ng';
import PromiseRejectionError from '@/lib/promise-rejection-error';
import logoUrl from '@/assets/images/redash_icon_small.png';
import useDashboard from './useDashboard';

import './PublicDashboardPage.less';


function PublicDashboard({ dashboard }) {
const { globalParameters, filters, setFilters, refreshDashboard,
widgets, loadWidget, refreshWidget } = useDashboard(dashboard);

return (
<div className="container p-t-10 p-b-20">
<PageHeader title={dashboard.name} />
{!isEmpty(globalParameters) && (
<div className="m-b-10 p-15 bg-white tiled">
<Parameters parameters={globalParameters} onValuesChange={refreshDashboard} />
</div>
)}
{!isEmpty(filters) && (
<div className="m-b-10 p-15 bg-white tiled">
<Filters filters={filters} onChange={setFilters} />
</div>
)}
<div id="dashboard-container">
<DashboardGrid
dashboard={dashboard}
widgets={widgets}
filters={filters}
isEditing={false}
isPublic
onLoadWidget={loadWidget}
onRefreshWidget={refreshWidget}
/>
</div>
</div>
);
}

PublicDashboard.propTypes = {
dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};

class PublicDashboardPage extends React.Component {
state = {
loading: true,
dashboard: null,
};

componentDidMount() {
Dashboard.getByToken({ token: ngRoute.current.params.token }).$promise
.then(dashboard => this.setState({ dashboard, loading: false }))
.catch((error) => { throw new PromiseRejectionError(error); });
}

render() {
const { loading, dashboard } = this.state;
return (
<div className="public-dashboard-page">
{loading ? (
<div className="container loading-message">
<BigMessage
className=""
icon="fa-spinner fa-2x fa-pulse"
message="Loading..."
/>
</div>
) : (
<PublicDashboard dashboard={dashboard} />
)}
<div id="footer">
<div className="text-center">
<a href="https://redash.io"><img alt="Redash Logo" src={logoUrl} width="38" /></a>
</div>
Powered by <a href="https://redash.io/?ref=public-dashboard">Redash</a>
</div>
</div>
);
}
}

export default function init(ngModule) {
ngModule.component('publicDashboardPage', react2angular(PublicDashboardPage));

function session($route, Auth) {
const token = $route.current.params.token;
Auth.setApiKey(token);
return Auth.loadConfig();
}

ngModule.config(($routeProvider) => {
$routeProvider.when('/public/dashboards/:token', {
template: '<public-dashboard-page></public-dashboard-page>',
reloadOnSearch: false,
resolve: {
session,
},
});
});

return [];
}

init.init = true;
16 changes: 16 additions & 0 deletions client/app/pages/dashboards/PublicDashboardPage.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.public-dashboard-page {
> .container {
min-height: calc(100vh - 95px);
}

.loading-message {
display: flex;
align-items: center;
justify-content: center;
}

#footer {
height: 95px;
text-align: center;
}
}
11 changes: 0 additions & 11 deletions client/app/pages/dashboards/dashboard.less
Original file line number Diff line number Diff line change
Expand Up @@ -206,17 +206,6 @@
}
}

public-dashboard-page {
> .container {
min-height: calc(100vh - 95px);
}

#footer {
height: 95px;
text-align: center;
}
}

/****
grid bg - based on 6 cols, 35px rows and 15px spacing
****/
Expand Down
33 changes: 0 additions & 33 deletions client/app/pages/dashboards/public-dashboard-page.html

This file was deleted.

98 changes: 0 additions & 98 deletions client/app/pages/dashboards/public-dashboard-page.js

This file was deleted.

75 changes: 75 additions & 0 deletions client/app/pages/dashboards/useDashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useState, useEffect, useMemo, useCallback } from 'react';
import { isEmpty, isNaN, includes, compact, map } from 'lodash';
import { $location } from '@/services/ng';
import { collectDashboardFilters } from '@/services/dashboard';

function getAffectedWidgets(widgets, updatedParameters = []) {
return !isEmpty(updatedParameters) ? widgets.filter(
widget => Object.values(widget.getParameterMappings()).filter(
({ type }) => type === 'dashboard-level',
).some(
({ mapTo }) => includes(updatedParameters.map(p => p.name), mapTo),
),
) : widgets;
}

function getRefreshRateFromUrl() {
const refreshRate = parseFloat($location.search().refresh);
return isNaN(refreshRate) ? null : Math.max(30, refreshRate);
}

function useDashboard(dashboard) {
const [filters, setFilters] = useState([]);
const [widgets, setWidgets] = useState(dashboard.widgets);
const globalParameters = useMemo(() => dashboard.getParametersDefs(), [dashboard]);
const refreshRate = useMemo(getRefreshRateFromUrl, []);

const loadWidget = useCallback((widget, forceRefresh = false) => {
widget.getParametersDefs(); // Force widget to read parameters values from URL
setWidgets([...dashboard.widgets]);
return widget.load(forceRefresh).finally(() => setWidgets([...dashboard.widgets]));
}, [dashboard]);

const refreshWidget = useCallback(widget => loadWidget(widget, true), [loadWidget]);

const loadDashboard = useCallback((forceRefresh = false, updatedParameters = []) => {
const affectedWidgets = getAffectedWidgets(widgets, updatedParameters);
const loadWidgetPromises = compact(
affectedWidgets.map(widget => loadWidget(widget, forceRefresh).catch(error => error)),
);

return Promise.all(loadWidgetPromises).then(() => {
const queryResults = compact(map(widgets, widget => widget.getQueryResult()));
const updatedFilters = collectDashboardFilters(dashboard, queryResults, $location.search());
setFilters(updatedFilters);
});
}, [dashboard, widgets, loadWidget]);

const refreshDashboard = useCallback(
updatedParameters => loadDashboard(true, updatedParameters),
[loadDashboard],
);

useEffect(() => {
loadDashboard();
}, [dashboard]);

useEffect(() => {
if (refreshRate) {
const refreshTimer = setInterval(refreshDashboard, refreshRate * 1000);
return () => clearInterval(refreshTimer);
}
}, [refreshRate]);

return {
widgets,
globalParameters,
filters,
setFilters,
refreshDashboard,
loadWidget,
refreshWidget,
};
}

export default useDashboard;
Loading