mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Big refactoring for dashboard init redux actions
This commit is contained in:
41
public/app/core/components/AlertBox/AlertBox.tsx
Normal file
41
public/app/core/components/AlertBox/AlertBox.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { AppNotificationSeverity } from 'app/types';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
icon?: string;
|
||||
text?: string;
|
||||
severity: AppNotificationSeverity;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
function getIconFromSeverity(severity: AppNotificationSeverity): string {
|
||||
switch (severity) {
|
||||
case AppNotificationSeverity.Error: {
|
||||
return 'fa fa-exclamation-triangle';
|
||||
}
|
||||
case AppNotificationSeverity.Success: {
|
||||
return 'fa fa-check';
|
||||
}
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const AlertBox: FunctionComponent<Props> = ({ title, icon, text, severity, onClose }) => {
|
||||
return (
|
||||
<div className={`alert alert-${severity}`}>
|
||||
<div className="alert-icon">
|
||||
<i className={icon || getIconFromSeverity(severity)} />
|
||||
</div>
|
||||
<div className="alert-body">
|
||||
<div className="alert-title">{title}</div>
|
||||
{text && <div className="alert-text">{text}</div>}
|
||||
</div>
|
||||
{onClose && (
|
||||
<button type="button" className="alert-close" onClick={onClose}>
|
||||
<i className="fa fa fa-remove" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { AppNotification } from 'app/types';
|
||||
import { AlertBox } from '../AlertBox/AlertBox';
|
||||
|
||||
interface Props {
|
||||
appNotification: AppNotification;
|
||||
@@ -22,18 +23,13 @@ export default class AppNotificationItem extends Component<Props> {
|
||||
const { appNotification, onClearNotification } = this.props;
|
||||
|
||||
return (
|
||||
<div className={`alert-${appNotification.severity} alert`}>
|
||||
<div className="alert-icon">
|
||||
<i className={appNotification.icon} />
|
||||
</div>
|
||||
<div className="alert-body">
|
||||
<div className="alert-title">{appNotification.title}</div>
|
||||
<div className="alert-text">{appNotification.text}</div>
|
||||
</div>
|
||||
<button type="button" className="alert-close" onClick={() => onClearNotification(appNotification.id)}>
|
||||
<i className="fa fa fa-remove" />
|
||||
</button>
|
||||
</div>
|
||||
<AlertBox
|
||||
severity={appNotification.severity}
|
||||
title={appNotification.title}
|
||||
text={appNotification.text}
|
||||
icon={appNotification.icon}
|
||||
onClose={() => onClearNotification(appNotification.id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { AppNotification, AppNotificationSeverity, AppNotificationTimeout } from 'app/types';
|
||||
import { getMessageFromError } from 'app/core/utils/errors';
|
||||
|
||||
const defaultSuccessNotification: AppNotification = {
|
||||
title: '',
|
||||
@@ -33,21 +33,10 @@ export const createSuccessNotification = (title: string, text?: string): AppNoti
|
||||
});
|
||||
|
||||
export const createErrorNotification = (title: string, text?: any): AppNotification => {
|
||||
// Handling if text is an error object
|
||||
if (text && !_.isString(text)) {
|
||||
if (text.message) {
|
||||
text = text.message;
|
||||
} else if (text.data && text.data.message) {
|
||||
text = text.data.message;
|
||||
} else {
|
||||
text = text.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...defaultErrorNotification,
|
||||
title: title,
|
||||
text: text,
|
||||
text: getMessageFromError(text),
|
||||
id: Date.now(),
|
||||
};
|
||||
};
|
||||
|
||||
15
public/app/core/utils/errors.ts
Normal file
15
public/app/core/utils/errors.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export function getMessageFromError(err: any): string | null {
|
||||
if (err && !_.isString(err)) {
|
||||
if (err.message) {
|
||||
return err.message;
|
||||
} else if (err.data && err.data.message) {
|
||||
return err.data.message;
|
||||
} else {
|
||||
return JSON.stringify(err);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import moment from 'moment';
|
||||
import angular from 'angular';
|
||||
import { appEvents, NavModel } from 'app/core/core';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
|
||||
export class DashNavCtrl {
|
||||
dashboard: DashboardModel;
|
||||
navModel: NavModel;
|
||||
titleTooltip: string;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $scope, private dashboardSrv, private $location, public playlistSrv) {
|
||||
if (this.dashboard.meta.isSnapshot) {
|
||||
const meta = this.dashboard.meta;
|
||||
this.titleTooltip = 'Created: ' + moment(meta.created).calendar();
|
||||
if (meta.expires) {
|
||||
this.titleTooltip += '<br>Expires: ' + moment(meta.expires).fromNow() + '<br>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleSettings() {
|
||||
const search = this.$location.search();
|
||||
if (search.editview) {
|
||||
delete search.editview;
|
||||
} else {
|
||||
search.editview = 'settings';
|
||||
}
|
||||
this.$location.search(search);
|
||||
}
|
||||
|
||||
toggleViewMode() {
|
||||
appEvents.emit('toggle-kiosk-mode');
|
||||
}
|
||||
|
||||
close() {
|
||||
const search = this.$location.search();
|
||||
if (search.editview) {
|
||||
delete search.editview;
|
||||
} else if (search.fullscreen) {
|
||||
delete search.fullscreen;
|
||||
delete search.edit;
|
||||
delete search.tab;
|
||||
delete search.panelId;
|
||||
}
|
||||
this.$location.search(search);
|
||||
}
|
||||
|
||||
starDashboard() {
|
||||
this.dashboardSrv.starDashboard(this.dashboard.id, this.dashboard.meta.isStarred).then(newState => {
|
||||
this.dashboard.meta.isStarred = newState;
|
||||
});
|
||||
}
|
||||
|
||||
shareDashboard(tabIndex) {
|
||||
const modalScope = this.$scope.$new();
|
||||
modalScope.tabIndex = tabIndex;
|
||||
modalScope.dashboard = this.dashboard;
|
||||
|
||||
appEvents.emit('show-modal', {
|
||||
src: 'public/app/features/dashboard/components/ShareModal/template.html',
|
||||
scope: modalScope,
|
||||
});
|
||||
}
|
||||
|
||||
hideTooltip(evt) {
|
||||
angular.element(evt.currentTarget).tooltip('hide');
|
||||
}
|
||||
|
||||
saveDashboard() {
|
||||
return this.dashboardSrv.saveDashboard();
|
||||
}
|
||||
|
||||
showSearch() {
|
||||
if (this.dashboard.meta.fullscreen) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
appEvents.emit('show-dash-search');
|
||||
}
|
||||
|
||||
addPanel() {
|
||||
appEvents.emit('dash-scroll', { animate: true, evt: 0 });
|
||||
|
||||
if (this.dashboard.panels.length > 0 && this.dashboard.panels[0].type === 'add-panel') {
|
||||
return; // Return if the "Add panel" exists already
|
||||
}
|
||||
|
||||
this.dashboard.addPanel({
|
||||
type: 'add-panel',
|
||||
gridPos: { x: 0, y: 0, w: 12, h: 8 },
|
||||
title: 'Panel Title',
|
||||
});
|
||||
}
|
||||
|
||||
navItemClicked(navItem, evt) {
|
||||
if (navItem.clickHandler) {
|
||||
navItem.clickHandler();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function dashNavDirective() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'public/app/features/dashboard/components/DashNav/template.html',
|
||||
controller: DashNavCtrl,
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
transclude: true,
|
||||
scope: { dashboard: '=' },
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('grafana.directives').directive('dashnav', dashNavDirective);
|
||||
@@ -1,3 +1,2 @@
|
||||
export { DashNavCtrl } from './DashNavCtrl';
|
||||
import DashNav from './DashNav';
|
||||
export { DashNav };
|
||||
|
||||
@@ -2,8 +2,8 @@ import React from 'react';
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { DashboardPage, Props, State } from './DashboardPage';
|
||||
import { DashboardModel } from '../state';
|
||||
import { setDashboardModel } from '../state/actions';
|
||||
import { DashboardRouteInfo, DashboardLoadingState } from 'app/types';
|
||||
import { cleanUpDashboard } from '../state/actions';
|
||||
import { DashboardRouteInfo, DashboardInitPhase } from 'app/types';
|
||||
|
||||
jest.mock('sass/_variables.scss', () => ({
|
||||
panelhorizontalpadding: 10,
|
||||
@@ -22,13 +22,13 @@ function setup(propOverrides?: Partial<Props>): ShallowWrapper<Props, State, Das
|
||||
routeInfo: DashboardRouteInfo.Normal,
|
||||
urlEdit: false,
|
||||
urlFullscreen: false,
|
||||
loadingState: DashboardLoadingState.Done,
|
||||
isLoadingSlow: false,
|
||||
initPhase: DashboardInitPhase.Completed,
|
||||
isInitSlow: false,
|
||||
initDashboard: jest.fn(),
|
||||
updateLocation: jest.fn(),
|
||||
notifyApp: jest.fn(),
|
||||
dashboard: null,
|
||||
setDashboardModel: setDashboardModel,
|
||||
cleanUpDashboard: cleanUpDashboard,
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
@@ -66,7 +66,7 @@ describe('DashboardPage', () => {
|
||||
canEdit: true,
|
||||
canSave: true,
|
||||
});
|
||||
wrapper.setProps({ dashboard, loadingState: DashboardLoadingState.Done });
|
||||
wrapper.setProps({ dashboard, initPhase: DashboardInitPhase.Completed });
|
||||
});
|
||||
|
||||
it('Should update title', () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import classNames from 'classnames';
|
||||
|
||||
// Services & Utils
|
||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||
import { getMessageFromError } from 'app/core/utils/errors';
|
||||
|
||||
// Components
|
||||
import { DashboardGrid } from '../dashgrid/DashboardGrid';
|
||||
@@ -14,15 +15,22 @@ import { DashNav } from '../components/DashNav';
|
||||
import { SubMenu } from '../components/SubMenu';
|
||||
import { DashboardSettings } from '../components/DashboardSettings';
|
||||
import { CustomScrollbar } from '@grafana/ui';
|
||||
import { AlertBox } from 'app/core/components/AlertBox/AlertBox';
|
||||
|
||||
// Redux
|
||||
import { initDashboard } from '../state/initDashboard';
|
||||
import { setDashboardModel } from '../state/actions';
|
||||
import { cleanUpDashboard } from '../state/actions';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
|
||||
// Types
|
||||
import { StoreState, DashboardLoadingState, DashboardRouteInfo } from 'app/types';
|
||||
import {
|
||||
StoreState,
|
||||
DashboardInitPhase,
|
||||
DashboardRouteInfo,
|
||||
DashboardInitError,
|
||||
AppNotificationSeverity,
|
||||
} from 'app/types';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
|
||||
export interface Props {
|
||||
@@ -37,11 +45,12 @@ export interface Props {
|
||||
routeInfo: DashboardRouteInfo;
|
||||
urlEdit: boolean;
|
||||
urlFullscreen: boolean;
|
||||
loadingState: DashboardLoadingState;
|
||||
isLoadingSlow: boolean;
|
||||
initPhase: DashboardInitPhase;
|
||||
isInitSlow: boolean;
|
||||
dashboard: DashboardModel | null;
|
||||
initError?: DashboardInitError;
|
||||
initDashboard: typeof initDashboard;
|
||||
setDashboardModel: typeof setDashboardModel;
|
||||
cleanUpDashboard: typeof cleanUpDashboard;
|
||||
notifyApp: typeof notifyApp;
|
||||
updateLocation: typeof updateLocation;
|
||||
}
|
||||
@@ -83,7 +92,7 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
componentWillUnmount() {
|
||||
if (this.props.dashboard) {
|
||||
this.props.dashboard.destroy();
|
||||
this.props.setDashboardModel(null);
|
||||
this.props.cleanUpDashboard();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,23 +213,37 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
this.setState({ scrollTop: 0 });
|
||||
};
|
||||
|
||||
renderLoadingState() {
|
||||
renderSlowInitState() {
|
||||
return (
|
||||
<div className="dashboard-loading">
|
||||
<div className="dashboard-loading__text">
|
||||
<i className="fa fa-spinner fa-spin" /> Dashboard {this.props.loadingState}
|
||||
<i className="fa fa-spinner fa-spin" /> {this.props.initPhase}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderInitFailedState() {
|
||||
const { initError } = this.props;
|
||||
|
||||
return (
|
||||
<div className="dashboard-loading">
|
||||
<AlertBox
|
||||
severity={AppNotificationSeverity.Error}
|
||||
title={initError.message}
|
||||
text={getMessageFromError(initError.error)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dashboard, editview, $injector, isLoadingSlow } = this.props;
|
||||
const { dashboard, editview, $injector, isInitSlow, initError } = this.props;
|
||||
const { isSettingsOpening, isEditing, isFullscreen, scrollTop } = this.state;
|
||||
|
||||
if (!dashboard) {
|
||||
if (isLoadingSlow) {
|
||||
return this.renderLoadingState();
|
||||
if (isInitSlow) {
|
||||
return this.renderSlowInitState();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -249,6 +272,8 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
<CustomScrollbar autoHeightMin={'100%'} setScrollTop={this.setScrollTop} scrollTop={scrollTop}>
|
||||
{editview && <DashboardSettings dashboard={dashboard} />}
|
||||
|
||||
{initError && this.renderInitFailedState()}
|
||||
|
||||
<div className={gridWrapperClasses}>
|
||||
{dashboard.meta.submenuEnabled && <SubMenu dashboard={dashboard} />}
|
||||
<DashboardGrid dashboard={dashboard} isEditing={isEditing} isFullscreen={isFullscreen} />
|
||||
@@ -269,14 +294,15 @@ const mapStateToProps = (state: StoreState) => ({
|
||||
urlFolderId: state.location.query.folderId,
|
||||
urlFullscreen: state.location.query.fullscreen === true,
|
||||
urlEdit: state.location.query.edit === true,
|
||||
loadingState: state.dashboard.loadingState,
|
||||
isLoadingSlow: state.dashboard.isLoadingSlow,
|
||||
initPhase: state.dashboard.initPhase,
|
||||
isInitSlow: state.dashboard.isInitSlow,
|
||||
initError: state.dashboard.initError,
|
||||
dashboard: state.dashboard.model as DashboardModel,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
initDashboard,
|
||||
setDashboardModel,
|
||||
cleanUpDashboard,
|
||||
notifyApp,
|
||||
updateLocation,
|
||||
};
|
||||
|
||||
@@ -100,7 +100,6 @@ const mapStateToProps = (state: StoreState) => ({
|
||||
urlSlug: state.location.routeParams.slug,
|
||||
urlType: state.location.routeParams.type,
|
||||
urlPanelId: state.location.query.panelId,
|
||||
loadingState: state.dashboard.loadingState,
|
||||
dashboard: state.dashboard.model as DashboardModel,
|
||||
});
|
||||
|
||||
|
||||
@@ -8,20 +8,36 @@ import { loadPluginDashboards } from '../../plugins/state/actions';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
|
||||
// Types
|
||||
import { ThunkResult } from 'app/types';
|
||||
import {
|
||||
ThunkResult,
|
||||
DashboardAcl,
|
||||
DashboardAclDTO,
|
||||
PermissionLevel,
|
||||
DashboardAclUpdateDTO,
|
||||
NewDashboardAclItem,
|
||||
} from 'app/types/acl';
|
||||
import { DashboardLoadingState, MutableDashboard } from 'app/types/dashboard';
|
||||
MutableDashboard,
|
||||
DashboardInitError,
|
||||
} from 'app/types';
|
||||
|
||||
export const loadDashboardPermissions = actionCreatorFactory<DashboardAclDTO[]>('LOAD_DASHBOARD_PERMISSIONS').create();
|
||||
export const setDashboardLoadingState = actionCreatorFactory<DashboardLoadingState>('SET_DASHBOARD_LOADING_STATE').create();
|
||||
export const setDashboardModel = actionCreatorFactory<MutableDashboard>('SET_DASHBOARD_MODEL').create();
|
||||
export const setDashboardLoadingSlow = noPayloadActionCreatorFactory('SET_DASHBOARD_LOADING_SLOW').create();
|
||||
|
||||
export const dashboardInitFetching = noPayloadActionCreatorFactory('DASHBOARD_INIT_FETCHING').create();
|
||||
|
||||
export const dashboardInitServices = noPayloadActionCreatorFactory('DASHBOARD_INIT_SERVICES').create();
|
||||
|
||||
export const dashboardInitSlow = noPayloadActionCreatorFactory('SET_DASHBOARD_INIT_SLOW').create();
|
||||
|
||||
export const dashboardInitCompleted = actionCreatorFactory<MutableDashboard>('DASHBOARD_INIT_COMLETED').create();
|
||||
|
||||
/*
|
||||
* Unrecoverable init failure (fetch or model creation failed)
|
||||
*/
|
||||
export const dashboardInitFailed = actionCreatorFactory<DashboardInitError>('DASHBOARD_INIT_FAILED').create();
|
||||
|
||||
/*
|
||||
* When leaving dashboard, resets state
|
||||
* */
|
||||
export const cleanUpDashboard = noPayloadActionCreatorFactory('DASHBOARD_CLEAN_UP').create();
|
||||
|
||||
export function getDashboardPermissions(id: number): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { initDashboard, InitDashboardArgs } from './initDashboard';
|
||||
import { DashboardRouteInfo, DashboardLoadingState } from 'app/types';
|
||||
import { DashboardRouteInfo } from 'app/types';
|
||||
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
@@ -98,13 +98,11 @@ describeInitScenario('Initializing new dashboard', ctx => {
|
||||
});
|
||||
|
||||
it('Should send action to set loading state to fetching', () => {
|
||||
expect(ctx.actions[0].type).toBe('SET_DASHBOARD_LOADING_STATE');
|
||||
expect(ctx.actions[0].payload).toBe(DashboardLoadingState.Fetching);
|
||||
expect(ctx.actions[0].type).toBe('DASHBOARD_INIT_FETCHING');
|
||||
});
|
||||
|
||||
it('Should send action to set loading state to Initializing', () => {
|
||||
expect(ctx.actions[1].type).toBe('SET_DASHBOARD_LOADING_STATE');
|
||||
expect(ctx.actions[1].payload).toBe(DashboardLoadingState.Initializing);
|
||||
expect(ctx.actions[1].type).toBe('DASHBOARD_INIT_SERVICES');
|
||||
});
|
||||
|
||||
it('Should update location with orgId query param', () => {
|
||||
@@ -113,7 +111,7 @@ describeInitScenario('Initializing new dashboard', ctx => {
|
||||
});
|
||||
|
||||
it('Should send action to set dashboard model', () => {
|
||||
expect(ctx.actions[3].type).toBe('SET_DASHBOARD_MODEL');
|
||||
expect(ctx.actions[3].type).toBe('DASHBOARD_INIT_COMLETED');
|
||||
expect(ctx.actions[3].payload.title).toBe('New dashboard');
|
||||
});
|
||||
|
||||
|
||||
@@ -12,17 +12,16 @@ import { KeybindingSrv } from 'app/core/services/keybindingSrv';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { setDashboardLoadingState, setDashboardModel, setDashboardLoadingSlow } from './actions';
|
||||
import {
|
||||
dashboardInitFetching,
|
||||
dashboardInitCompleted,
|
||||
dashboardInitFailed,
|
||||
dashboardInitSlow,
|
||||
dashboardInitServices,
|
||||
} from './actions';
|
||||
|
||||
// Types
|
||||
import {
|
||||
DashboardLoadingState,
|
||||
DashboardRouteInfo,
|
||||
StoreState,
|
||||
ThunkDispatch,
|
||||
ThunkResult,
|
||||
DashboardDTO,
|
||||
} from 'app/types';
|
||||
import { DashboardRouteInfo, StoreState, ThunkDispatch, ThunkResult, DashboardDTO } from 'app/types';
|
||||
import { DashboardModel } from './DashboardModel';
|
||||
|
||||
export interface InitDashboardArgs {
|
||||
@@ -106,8 +105,7 @@ async function fetchDashboard(
|
||||
throw { message: 'Unknown route ' + args.routeInfo };
|
||||
}
|
||||
} catch (err) {
|
||||
dispatch(setDashboardLoadingState(DashboardLoadingState.Error));
|
||||
dispatch(notifyApp(createErrorNotification('Dashboard fetch failed', err)));
|
||||
dispatch(dashboardInitFailed({ message: 'Failed to fetch dashboard', error: err }));
|
||||
console.log(err);
|
||||
return null;
|
||||
}
|
||||
@@ -125,13 +123,13 @@ async function fetchDashboard(
|
||||
export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
|
||||
return async (dispatch, getState) => {
|
||||
// set fetching state
|
||||
dispatch(setDashboardLoadingState(DashboardLoadingState.Fetching));
|
||||
dispatch(dashboardInitFetching());
|
||||
|
||||
// Detect slow loading / initializing and set state flag
|
||||
// This is in order to not show loading indication for fast loading dashboards as it creates blinking/flashing
|
||||
setTimeout(() => {
|
||||
if (getState().dashboard.model === null) {
|
||||
dispatch(setDashboardLoadingSlow());
|
||||
dispatch(dashboardInitSlow());
|
||||
}
|
||||
}, 500);
|
||||
|
||||
@@ -144,15 +142,14 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
|
||||
}
|
||||
|
||||
// set initializing state
|
||||
dispatch(setDashboardLoadingState(DashboardLoadingState.Initializing));
|
||||
dispatch(dashboardInitServices());
|
||||
|
||||
// create model
|
||||
let dashboard: DashboardModel;
|
||||
try {
|
||||
dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta);
|
||||
} catch (err) {
|
||||
dispatch(setDashboardLoadingState(DashboardLoadingState.Error));
|
||||
dispatch(notifyApp(createErrorNotification('Dashboard model initializing failure', err)));
|
||||
dispatch(dashboardInitFailed({ message: 'Failed create dashboard model', error: err }));
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
@@ -203,8 +200,8 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
|
||||
|
||||
// legacy srv state
|
||||
dashboardSrv.setCurrent(dashboard);
|
||||
// set model in redux (even though it's mutable)
|
||||
dispatch(setDashboardModel(dashboard));
|
||||
// yay we are done
|
||||
dispatch(dashboardInitCompleted(dashboard));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import { DashboardState, DashboardLoadingState } from 'app/types/dashboard';
|
||||
import { loadDashboardPermissions, setDashboardLoadingState, setDashboardModel, setDashboardLoadingSlow } from './actions';
|
||||
import { DashboardState, DashboardInitPhase } from 'app/types';
|
||||
import {
|
||||
loadDashboardPermissions,
|
||||
dashboardInitFetching,
|
||||
dashboardInitSlow,
|
||||
dashboardInitServices,
|
||||
dashboardInitFailed,
|
||||
dashboardInitCompleted,
|
||||
cleanUpDashboard,
|
||||
} from './actions';
|
||||
import { reducerFactory } from 'app/core/redux';
|
||||
import { processAclItems } from 'app/core/utils/acl';
|
||||
import { DashboardModel } from './DashboardModel';
|
||||
|
||||
export const initialState: DashboardState = {
|
||||
loadingState: DashboardLoadingState.NotStarted,
|
||||
isLoadingSlow: false,
|
||||
initPhase: DashboardInitPhase.NotStarted,
|
||||
isInitSlow: false,
|
||||
model: null,
|
||||
permissions: [],
|
||||
};
|
||||
@@ -19,26 +28,59 @@ export const dashboardReducer = reducerFactory(initialState)
|
||||
}),
|
||||
})
|
||||
.addMapper({
|
||||
filter: setDashboardLoadingState,
|
||||
mapper: (state, action) => ({
|
||||
filter: dashboardInitFetching,
|
||||
mapper: state => ({
|
||||
...state,
|
||||
loadingState: action.payload
|
||||
initPhase: DashboardInitPhase.Fetching,
|
||||
}),
|
||||
})
|
||||
.addMapper({
|
||||
filter: setDashboardModel,
|
||||
filter: dashboardInitServices,
|
||||
mapper: state => ({
|
||||
...state,
|
||||
initPhase: DashboardInitPhase.Services,
|
||||
}),
|
||||
})
|
||||
.addMapper({
|
||||
filter: dashboardInitSlow,
|
||||
mapper: state => ({
|
||||
...state,
|
||||
isInitSlow: true,
|
||||
}),
|
||||
})
|
||||
.addMapper({
|
||||
filter: dashboardInitFailed,
|
||||
mapper: (state, action) => ({
|
||||
...state,
|
||||
initPhase: DashboardInitPhase.Failed,
|
||||
isInitSlow: false,
|
||||
initError: action.payload,
|
||||
model: new DashboardModel({ title: 'Dashboard init failed' }, { canSave: false, canEdit: false }),
|
||||
}),
|
||||
})
|
||||
.addMapper({
|
||||
filter: dashboardInitCompleted,
|
||||
mapper: (state, action) => ({
|
||||
...state,
|
||||
initPhase: DashboardInitPhase.Completed,
|
||||
model: action.payload,
|
||||
isLoadingSlow: false,
|
||||
isInitSlow: false,
|
||||
}),
|
||||
})
|
||||
.addMapper({
|
||||
filter: setDashboardLoadingSlow,
|
||||
mapper: (state, action) => ({
|
||||
...state,
|
||||
isLoadingSlow: true,
|
||||
}),
|
||||
filter: cleanUpDashboard,
|
||||
mapper: (state, action) => {
|
||||
// tear down current dashboard
|
||||
state.model.destroy();
|
||||
|
||||
return {
|
||||
...state,
|
||||
initPhase: DashboardInitPhase.NotStarted,
|
||||
model: null,
|
||||
isInitSlow: false,
|
||||
initError: null,
|
||||
};
|
||||
},
|
||||
})
|
||||
.create();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DashboardAcl } from './acl';
|
||||
|
||||
export interface MutableDashboard {
|
||||
meta: DashboardMeta;
|
||||
destroy: () => void;
|
||||
}
|
||||
|
||||
export interface DashboardDTO {
|
||||
@@ -44,12 +45,17 @@ export enum DashboardRouteInfo {
|
||||
Scripted = 'scripted-dashboard',
|
||||
}
|
||||
|
||||
export enum DashboardLoadingState {
|
||||
export enum DashboardInitPhase {
|
||||
NotStarted = 'Not started',
|
||||
Fetching = 'Fetching',
|
||||
Initializing = 'Initializing',
|
||||
Error = 'Error',
|
||||
Done = 'Done',
|
||||
Services = 'Services',
|
||||
Failed = 'Failed',
|
||||
Completed = 'Completed',
|
||||
}
|
||||
|
||||
export interface DashboardInitError {
|
||||
message: string;
|
||||
error: any;
|
||||
}
|
||||
|
||||
export const KIOSK_MODE_TV = 'tv';
|
||||
@@ -57,7 +63,8 @@ export type KioskUrlValue = 'tv' | '1' | true;
|
||||
|
||||
export interface DashboardState {
|
||||
model: MutableDashboard | null;
|
||||
loadingState: DashboardLoadingState;
|
||||
isLoadingSlow: boolean;
|
||||
initPhase: DashboardInitPhase;
|
||||
isInitSlow: boolean;
|
||||
initError?: DashboardInitError;
|
||||
permissions: DashboardAcl[] | null;
|
||||
}
|
||||
|
||||
@@ -282,6 +282,11 @@ div.flot-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.alert {
|
||||
max-width: 600px;
|
||||
min-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-loading__text {
|
||||
|
||||
Reference in New Issue
Block a user