From d86e773c756806bb826af50c347709bce265a65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 2 Feb 2019 22:43:19 +0100 Subject: [PATCH] wip: minor progress --- .../dashboard/containers/DashboardPage.tsx | 128 +++++++----------- .../app/features/dashboard/state/actions.ts | 41 +++--- .../features/dashboard/state/initDashboard.ts | 67 ++++++++- .../features/dashboard/state/reducers.test.ts | 4 +- .../app/features/dashboard/state/reducers.ts | 33 +++-- public/app/types/dashboard.ts | 15 +- 6 files changed, 170 insertions(+), 118 deletions(-) diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 54eed34fc29..0e3e1058660 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -3,21 +3,16 @@ import React, { Component } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; -// Utils & Services -import locationUtil from 'app/core/utils/location_util'; -import { getBackendSrv } from 'app/core/services/backend_srv'; -import { createErrorNotification } from 'app/core/copy/appNotification'; - // Components import { LoadingPlaceholder } from '@grafana/ui'; // Redux -import { updateLocation } from 'app/core/actions'; -import { notifyApp } from 'app/core/actions'; +import { initDashboard } from '../state/initDashboard'; // Types import { StoreState } from 'app/types'; import { DashboardModel } from 'app/features/dashboard/state'; +import { DashboardLoadingState } from 'app/types/dashboard'; interface Props { panelId: string; @@ -26,8 +21,9 @@ interface Props { urlType?: string; $scope: any; $injector: any; - updateLocation: typeof updateLocation; - notifyApp: typeof notifyApp; + initDashboard: typeof initDashboard; + loadingState: DashboardLoadingState; + dashboard: DashboardModel; } interface State { @@ -42,81 +38,54 @@ export class DashboardPage extends Component { }; async componentDidMount() { - const { $injector, urlUid, urlType, urlSlug } = this.props; + this.props.initDashboard({ + injector: this.props.$injector, + scope: this.props.$scope, + urlSlug: this.props.urlSlug, + urlUid: this.props.urlUid, + urlType: this.props.urlType, + }) - // handle old urls with no uid - if (!urlUid && !(urlType === 'script' || urlType === 'snapshot')) { - this.redirectToNewUrl(); - return; - } - - const loaderSrv = $injector.get('dashboardLoaderSrv'); - const dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid); - - try { - this.initDashboard(dashDTO); - } catch (err) { - this.props.notifyApp(createErrorNotification('Failed to init dashboard', err.toString())); - console.log(err); - } + // const { $injector, urlUid, urlType, urlSlug } = this.props; + // + // // handle old urls with no uid + // if (!urlUid && !(urlType === 'script' || urlType === 'snapshot')) { + // this.redirectToNewUrl(); + // return; + // } + // + // const loaderSrv = $injector.get('dashboardLoaderSrv'); + // const dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid); + // + // try { + // this.initDashboard(dashDTO); + // } catch (err) { + // this.props.notifyApp(createErrorNotification('Failed to init dashboard', err.toString())); + // console.log(err); + // } } - redirectToNewUrl() { - getBackendSrv() - .getDashboardBySlug(this.props.urlSlug) - .then(res => { - if (res) { - const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/')); - this.props.updateLocation(url); - } - }); - } - - initDashboard(dashDTO: any) { - const dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta); - - // init services - this.timeSrv.init(dashboard); - this.annotationsSrv.init(dashboard); - - // template values service needs to initialize completely before - // the rest of the dashboard can load - this.variableSrv - .init(dashboard) - // template values failes are non fatal - .catch(this.onInitFailed.bind(this, 'Templating init failed', false)) - // continue - .finally(() => { - this.dashboard = dashboard; - this.dashboard.processRepeats(); - this.dashboard.updateSubmenuVisibility(); - this.dashboard.autoFitPanels(window.innerHeight); - - this.unsavedChangesSrv.init(dashboard, this.$scope); - - // TODO refactor ViewStateSrv - this.$scope.dashboard = dashboard; - this.dashboardViewState = this.dashboardViewStateSrv.create(this.$scope); - - this.keybindingSrv.setupDashboardBindings(this.$scope, dashboard); - this.setWindowTitleAndTheme(); - - appEvents.emit('dashboard-initialized', dashboard); - }) - .catch(this.onInitFailed.bind(this, 'Dashboard init failed', true)); - - this.setState({ dashboard }); - } + // redirectToNewUrl() { + // getBackendSrv() + // .getDashboardBySlug(this.props.urlSlug) + // .then(res => { + // if (res) { + // const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/')); + // this.props.updateLocation(url); + // } + // }); + // } + // + // initDashboard(dashDTO: any) { + // const dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta); + // this.setState({ dashboard }); + // } render() { - const { notFound, dashboard } = this.state; - - if (notFound) { - return
Dashboard not found
; - } + const { loadingState, dashboard } = this.props; if (!dashboard) { - return ; + return ; } return
title: {dashboard.title}
; @@ -128,11 +97,12 @@ const mapStateToProps = (state: StoreState) => ({ urlSlug: state.location.routeParams.slug, urlType: state.location.routeParams.type, panelId: state.location.query.panelId, + loadingState: state.dashboard.loadingState, + dashboard: state.dashboard as DashboardModel, }); const mapDispatchToProps = { - updateLocation, - notifyApp, + initDashboard }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DashboardPage)); diff --git a/public/app/features/dashboard/state/actions.ts b/public/app/features/dashboard/state/actions.ts index 4dcf0a925b7..1bb29dc3ad5 100644 --- a/public/app/features/dashboard/state/actions.ts +++ b/public/app/features/dashboard/state/actions.ts @@ -1,8 +1,18 @@ +// Libaries import { StoreState } from 'app/types'; import { ThunkAction } from 'redux-thunk'; + +// Services & Utils import { getBackendSrv } from 'app/core/services/backend_srv'; -import appEvents from 'app/core/app_events'; +import { actionCreatorFactory } from 'app/core/redux'; +import { ActionOf } from 'app/core/redux/actionCreatorFactory'; +import { createSuccessNotification } from 'app/core/copy/appNotification'; + +// Actions import { loadPluginDashboards } from '../../plugins/state/actions'; +import { notifyApp } from 'app/core/actions'; + +// Types import { DashboardAcl, DashboardAclDTO, @@ -10,30 +20,14 @@ import { DashboardAclUpdateDTO, NewDashboardAclItem, } from 'app/types/acl'; +import { DashboardLoadingState } from 'app/types/dashboard'; -export enum ActionTypes { - LoadDashboardPermissions = 'LOAD_DASHBOARD_PERMISSIONS', - LoadStarredDashboards = 'LOAD_STARRED_DASHBOARDS', -} +export const loadDashboardPermissions = actionCreatorFactory('LOAD_DASHBOARD_PERMISSIONS').create(); +export const setDashboardLoadingState = actionCreatorFactory('SET_DASHBOARD_LOADING_STATE').create(); -export interface LoadDashboardPermissionsAction { - type: ActionTypes.LoadDashboardPermissions; - payload: DashboardAcl[]; -} +export type Action = ActionOf; -export interface LoadStarredDashboardsAction { - type: ActionTypes.LoadStarredDashboards; - payload: DashboardAcl[]; -} - -export type Action = LoadDashboardPermissionsAction | LoadStarredDashboardsAction; - -type ThunkResult = ThunkAction; - -export const loadDashboardPermissions = (items: DashboardAclDTO[]): LoadDashboardPermissionsAction => ({ - type: ActionTypes.LoadDashboardPermissions, - payload: items, -}); +export type ThunkResult = ThunkAction; export function getDashboardPermissions(id: number): ThunkResult { return async dispatch => { @@ -124,7 +118,7 @@ export function addDashboardPermission(dashboardId: number, newItem: NewDashboar export function importDashboard(data, dashboardTitle: string): ThunkResult { return async dispatch => { await getBackendSrv().post('/api/dashboards/import', data); - appEvents.emit('alert-success', ['Dashboard Imported', dashboardTitle]); + dispatch(notifyApp(createSuccessNotification('Dashboard Imported', dashboardTitle))); dispatch(loadPluginDashboards()); }; } @@ -135,3 +129,4 @@ export function removeDashboard(uri: string): ThunkResult { dispatch(loadPluginDashboards()); }; } + diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index 3b2307b3ccc..124d03eee4a 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -1,5 +1,70 @@ +// Libaries +import { StoreState } from 'app/types'; +import { ThunkAction } from 'redux-thunk'; +// Services & Utils +import { getBackendSrv } from 'app/core/services/backend_srv'; +import { createErrorNotification } from 'app/core/copy/appNotification'; -export function initDashboard(dashboard: DashboardModel, $injector: any, $scope: any) { +// Actions +import { updateLocation } from 'app/core/actions'; +import { notifyApp } from 'app/core/actions'; +import locationUtil from 'app/core/utils/location_util'; +import { setDashboardLoadingState, ThunkResult } from './actions'; +// Types +import { DashboardLoadingState } from 'app/types/dashboard'; +import { DashboardModel } from './DashboardModel'; + +export interface InitDashboardArgs { + injector: any; + scope: any; + urlUid?: string; + urlSlug?: string; + urlType?: string; +} + +export function initDashboard({ injector, scope, urlUid, urlSlug, urlType }: InitDashboardArgs): ThunkResult { + return async dispatch => { + const loaderSrv = injector.get('dashboardLoaderSrv'); + + dispatch(setDashboardLoadingState(DashboardLoadingState.Fetching)); + + try { + // fetch dashboard from api + const dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid); + // set initializing state + dispatch(setDashboardLoadingState(DashboardLoadingState.Initializing)); + // create model + const dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta); + // init services + + injector.get('timeSrv').init(dashboard); + injector.get('annotationsSrv').init(dashboard); + + // template values service needs to initialize completely before + // the rest of the dashboard can load + injector.get('variableSrv').init(dashboard) + .catch(err => { + dispatch(notifyApp(createErrorNotification('Templating init failed'))); + }) + .finally(() => { + + dashboard.processRepeats(); + dashboard.updateSubmenuVisibility(); + dashboard.autoFitPanels(window.innerHeight); + + injector.get('unsavedChangesSrv').init(dashboard, scope); + + scope.dashboard = dashboard; + injector.get('dashboardViewStateSrv').create(scope); + injector.get('keybindingSrv').setupDashboardBindings(scope, dashboard); + }) + .catch(err => { + dispatch(setDashboardLoadingState(DashboardLoadingState.Error)); + }); + } catch (err) { + dispatch(setDashboardLoadingState(DashboardLoadingState.Error)); + } + }; } diff --git a/public/app/features/dashboard/state/reducers.test.ts b/public/app/features/dashboard/state/reducers.test.ts index ced8866aad8..ea3353ce741 100644 --- a/public/app/features/dashboard/state/reducers.test.ts +++ b/public/app/features/dashboard/state/reducers.test.ts @@ -1,4 +1,4 @@ -import { Action, ActionTypes } from './actions'; +import { Action } from './actions'; import { OrgRole, PermissionLevel, DashboardState } from 'app/types'; import { initialState, dashboardReducer } from './reducers'; @@ -8,7 +8,7 @@ describe('dashboard reducer', () => { beforeEach(() => { const action: Action = { - type: ActionTypes.LoadDashboardPermissions, + type: 'LOAD_DASHBOARD_PERMISSIONS', payload: [ { id: 2, dashboardId: 1, role: OrgRole.Viewer, permission: PermissionLevel.View }, { id: 3, dashboardId: 1, role: OrgRole.Editor, permission: PermissionLevel.Edit }, diff --git a/public/app/features/dashboard/state/reducers.ts b/public/app/features/dashboard/state/reducers.ts index 8a79a6c9f77..bd13446b090 100644 --- a/public/app/features/dashboard/state/reducers.ts +++ b/public/app/features/dashboard/state/reducers.ts @@ -1,21 +1,30 @@ -import { DashboardState } from 'app/types'; -import { Action, ActionTypes } from './actions'; +import { DashboardState, DashboardLoadingState } from 'app/types/dashboard'; +import { loadDashboardPermissions, setDashboardLoadingState } from './actions'; +import { reducerFactory } from 'app/core/redux'; import { processAclItems } from 'app/core/utils/acl'; export const initialState: DashboardState = { + loadingState: DashboardLoadingState.NotStarted, + dashboard: null, permissions: [], }; -export const dashboardReducer = (state = initialState, action: Action): DashboardState => { - switch (action.type) { - case ActionTypes.LoadDashboardPermissions: - return { - ...state, - permissions: processAclItems(action.payload), - }; - } - return state; -}; +export const dashboardReducer = reducerFactory(initialState) + .addMapper({ + filter: loadDashboardPermissions, + mapper: (state, action) => ({ + ...state, + permissions: processAclItems(action.payload), + }), + }) + .addMapper({ + filter: setDashboardLoadingState, + mapper: (state, action) => ({ + ...state, + loadingState: action.payload + }), + }) + .create() export default { dashboard: dashboardReducer, diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts index d33405c985e..df9a2e53548 100644 --- a/public/app/types/dashboard.ts +++ b/public/app/types/dashboard.ts @@ -1,5 +1,18 @@ import { DashboardAcl } from './acl'; +export interface Dashboard { +} + +export enum DashboardLoadingState { + NotStarted = 'Not started', + Fetching = 'Fetching', + Initializing = 'Initializing', + Error = 'Error', + Done = 'Done', +} + export interface DashboardState { - permissions: DashboardAcl[]; + dashboard: Dashboard | null; + loadingState: DashboardLoadingState; + permissions: DashboardAcl[] | null; }