Browse Source

wip: minor progress

Torkel Ödegaard 7 years ago
parent
commit
d86e773c75

+ 50 - 80
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<Props, State> {
   };
 
   async componentDidMount() {
-    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);
-        }
-      });
+    this.props.initDashboard({
+      injector: this.props.$injector,
+      scope: this.props.$scope,
+      urlSlug: this.props.urlSlug,
+      urlUid: this.props.urlUid,
+      urlType: this.props.urlType,
+    })
+
+    // 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);
+    // }
   }
 
-  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 <div className="alert alert-error">Dashboard not found</div>;
-    }
+    const { loadingState, dashboard } = this.props;
 
     if (!dashboard) {
-      return <LoadingPlaceholder text="Loading dashboard" />;
+      return <LoadingPlaceholder text={loadingState.toString()} />;
     }
 
     return <div>title: {dashboard.title}</div>;
@@ -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));

+ 18 - 23
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 interface LoadDashboardPermissionsAction {
-  type: ActionTypes.LoadDashboardPermissions;
-  payload: DashboardAcl[];
-}
-
-export interface LoadStarredDashboardsAction {
-  type: ActionTypes.LoadStarredDashboards;
-  payload: DashboardAcl[];
-}
+export const loadDashboardPermissions = actionCreatorFactory<DashboardAclDTO[]>('LOAD_DASHBOARD_PERMISSIONS').create();
+export const setDashboardLoadingState = actionCreatorFactory<DashboardLoadingState>('SET_DASHBOARD_LOADING_STATE').create();
 
-export type Action = LoadDashboardPermissionsAction | LoadStarredDashboardsAction;
+export type Action = ActionOf<DashboardAclDTO[]>;
 
-type ThunkResult<R> = ThunkAction<R, StoreState, undefined, any>;
-
-export const loadDashboardPermissions = (items: DashboardAclDTO[]): LoadDashboardPermissionsAction => ({
-  type: ActionTypes.LoadDashboardPermissions,
-  payload: items,
-});
+export type ThunkResult<R> = ThunkAction<R, StoreState, undefined, any>;
 
 export function getDashboardPermissions(id: number): ThunkResult<void> {
   return async dispatch => {
@@ -124,7 +118,7 @@ export function addDashboardPermission(dashboardId: number, newItem: NewDashboar
 export function importDashboard(data, dashboardTitle: string): ThunkResult<void> {
   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<void> {
     dispatch(loadPluginDashboards());
   };
 }
+

+ 66 - 1
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<void> {
+  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));
+    }
+  };
 }

+ 2 - 2
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 },

+ 21 - 12
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,

+ 14 - 1
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;
 }