Browse Source

tech: url and query mobx store so now react components and containers can read and modify url path and query via mobx store

Torkel Ödegaard 8 years ago
parent
commit
8f50795a94

+ 7 - 3
public/app/containers/AlertRuleList/AlertRuleList.tsx

@@ -25,12 +25,16 @@ export class AlertRuleList extends React.Component<AlertRuleListProps, any> {
   constructor(props) {
     super(props);
 
-    this.props.store.nav.load('alerting', 'alert-list');
-    this.props.store.alertList.loadRules();
+    const store = this.props.store;
+
+    store.nav.load('alerting', 'alert-list');
+    store.alertList.setStateFilter(store.view.query.get('state') || 'all');
+    store.alertList.loadRules();
   }
 
   onStateFilterChanged = evt => {
     this.props.store.alertList.setStateFilter(evt.target.value);
+    this.props.store.view.updateQuery({ state: evt.target.value });
     this.props.store.alertList.loadRules();
   };
 
@@ -54,7 +58,7 @@ export class AlertRuleList extends React.Component<AlertRuleListProps, any> {
               <label className="gf-form-label">Filter by state</label>
 
               <div className="gf-form-select-wrapper width-13">
-                <select className="gf-form-input" onChange={this.onStateFilterChanged}>
+                <select className="gf-form-input" onChange={this.onStateFilterChanged} value={alertList.stateFilter}>
                   {this.stateFilters.map(AlertStateFilterOption)}
                 </select>
               </div>

+ 4 - 5
public/app/core/components/grafana_app.ts

@@ -10,19 +10,18 @@ import { createStore } from 'app/stores/store';
 
 export class GrafanaCtrl {
   /** @ngInject */
-  constructor($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv, globalEventSrv, backendSrv) {
+  constructor($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv, bridgeSrv, backendSrv) {
     createStore(backendSrv);
 
     $scope.init = function() {
       $scope.contextSrv = contextSrv;
-
-      $rootScope.appSubUrl = config.appSubUrl;
+      $scope.appSubUrl = config.appSubUrl;
       $scope._ = _;
 
       profiler.init(config, $rootScope);
       alertSrv.init();
       utilSrv.init();
-      globalEventSrv.init();
+      bridgeSrv.init();
 
       $scope.dashAlerts = alertSrv;
     };
@@ -54,7 +53,7 @@ export class GrafanaCtrl {
 }
 
 /** @ngInject */
-export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScope) {
+export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScope, $location) {
   return {
     restrict: 'E',
     controller: GrafanaCtrl,

+ 1 - 1
public/app/core/services/all.js

@@ -8,6 +8,6 @@ define([
   './segment_srv',
   './backend_srv',
   './dynamic_directive_srv',
-  './global_event_srv'
+  './bridge_srv'
 ],
 function () {});

+ 32 - 6
public/app/core/services/global_event_srv.ts → public/app/core/services/bridge_srv.ts

@@ -1,15 +1,16 @@
-import coreModule from 'app/core/core_module';
+import coreModule from 'app/core/core_module';
 import config from 'app/core/config';
 import appEvents from 'app/core/app_events';
+import { store } from 'app/stores/store';
+import { reaction } from 'mobx';
 
-// This service is for registering global events.
-// Good for communication react > angular and vice verse
-export class GlobalEventSrv {
+// Services that handles angular -> mobx store sync & other react <-> angular sync
+export class BridgeSrv {
   private appSubUrl;
   private fullPageReloadRoutes;
 
   /** @ngInject */
-  constructor(private $location, private $timeout, private $window) {
+  constructor(private $location, private $timeout, private $window, private $rootScope) {
     this.appSubUrl = config.appSubUrl;
     this.fullPageReloadRoutes = ['/logout'];
   }
@@ -25,6 +26,31 @@ export class GlobalEventSrv {
   }
 
   init() {
+    this.$rootScope.$on('$routeUpdate', (evt, data) => {
+      let angularUrl = this.$location.url();
+      if (store.view.currentUrl !== angularUrl) {
+        store.view.updatePathAndQuery(this.$location.path(), this.$location.search());
+      }
+    });
+
+    this.$rootScope.$on('$routeChangeSuccess', (evt, data) => {
+      let angularUrl = this.$location.url();
+      if (store.view.currentUrl !== angularUrl) {
+        store.view.updatePathAndQuery(this.$location.path(), this.$location.search());
+      }
+    });
+
+    reaction(
+      () => store.view.currentUrl,
+      currentUrl => {
+        let angularUrl = this.$location.url();
+        if (angularUrl !== currentUrl) {
+          this.$location.url(currentUrl);
+          console.log('store updating angular $location.url', currentUrl);
+        }
+      }
+    );
+
     appEvents.on('location-change', payload => {
       const urlWithoutBase = this.stripBaseFromUrl(payload.href);
       if (this.fullPageReloadRoutes.indexOf(urlWithoutBase) > -1) {
@@ -40,4 +66,4 @@ export class GlobalEventSrv {
   }
 }
 
-coreModule.service('globalEventSrv', GlobalEventSrv);
+coreModule.service('bridgeSrv', BridgeSrv);

+ 2 - 3
public/app/core/specs/global_event_srv.jest.ts → public/app/core/specs/bridge_srv.jest.ts

@@ -1,5 +1,4 @@
-import { GlobalEventSrv } from 'app/core/services/global_event_srv';
-import { beforeEach } from 'test/lib/common';
+import { GlobalEventSrv } from 'app/core/services/bridge_srv';
 
 jest.mock('app/core/config', () => {
   return {
@@ -7,7 +6,7 @@ jest.mock('app/core/config', () => {
   };
 });
 
-describe('GlobalEventSrv', () => {
+describe('BridgeSrv', () => {
   let searchSrv;
 
   beforeEach(() => {

+ 0 - 74
public/app/features/alerting/alert_list_ctrl.ts

@@ -1,74 +0,0 @@
-///<reference path="../../headers/common.d.ts" />
-
-import _ from 'lodash';
-import moment from 'moment';
-
-import { coreModule, appEvents } from 'app/core/core';
-import alertDef from './alert_def';
-
-export class AlertListCtrl {
-  alerts: any;
-  stateFilters = [
-    { text: 'All', value: null },
-    { text: 'OK', value: 'ok' },
-    { text: 'Not OK', value: 'not_ok' },
-    { text: 'Alerting', value: 'alerting' },
-    { text: 'No Data', value: 'no_data' },
-    { text: 'Paused', value: 'paused' },
-  ];
-  filters = {
-    state: 'ALL',
-  };
-  navModel: any;
-
-  /** @ngInject */
-  constructor(private backendSrv, private $location, navModelSrv) {
-    this.navModel = navModelSrv.getNav('alerting', 'alert-list', 0);
-
-    var params = $location.search();
-    this.filters.state = params.state || null;
-    this.loadAlerts();
-  }
-
-  filtersChanged() {
-    this.$location.search(this.filters);
-  }
-
-  loadAlerts() {
-    this.backendSrv.get('/api/alerts', this.filters).then(result => {
-      this.alerts = _.map(result, alert => {
-        alert.stateModel = alertDef.getStateDisplayModel(alert.state);
-        alert.newStateDateAgo = moment(alert.newStateDate)
-          .fromNow()
-          .replace(' ago', '');
-        if (alert.evalData && alert.evalData.no_data) {
-          alert.no_data = true;
-        }
-        return alert;
-      });
-    });
-  }
-
-  pauseAlertRule(alertId: any) {
-    var alert = _.find(this.alerts, { id: alertId });
-
-    var payload = {
-      paused: alert.state !== 'paused',
-    };
-
-    this.backendSrv.post(`/api/alerts/${alert.id}/pause`, payload).then(result => {
-      alert.state = result.state;
-      alert.stateModel = alertDef.getStateDisplayModel(result.state);
-    });
-  }
-
-  openHowTo() {
-    appEvents.emit('show-modal', {
-      src: 'public/app/features/alerting/partials/alert_howto.html',
-      modalClass: 'confirm-modal',
-      model: {},
-    });
-  }
-}
-
-coreModule.controller('AlertListCtrl', AlertListCtrl);

+ 0 - 1
public/app/features/alerting/all.ts

@@ -1,3 +1,2 @@
-import './alert_list_ctrl';
 import './notifications_list_ctrl';
 import './notification_edit_ctrl';

+ 0 - 61
public/app/features/alerting/partials/alert_list.html

@@ -1,61 +0,0 @@
-<page-header model="ctrl.navModel"></page-header>
-
-<div class="page-container page-body">
-
-  <div class="page-action-bar">
-    <div class="gf-form">
-      <label class="gf-form-label">Filter by state</label>
-      <div class="gf-form-select-wrapper width-13">
-        <select class="gf-form-input" ng-model="ctrl.filters.state" ng-options="f.value as f.text for f in ctrl.stateFilters" ng-change="ctrl.filtersChanged()">
-        </select>
-      </div>
-    </div>
-
-    <div class="page-action-bar__spacer">
-    </div>
-
-    <a class="btn btn-secondary" ng-click="ctrl.openHowTo()">
-      <i class="fa fa-info-circle"></i>
-      How to add an alert
-    </a>
-  </div>
-
-  <section class="card-section card-list-layout-list">
-
-    <ol class="card-list" >
-      <li class="card-item-wrapper" ng-repeat="alert in ctrl.alerts">
-        <div class="card-item card-item--alert">
-          <div class="card-item-header">
-            <div class="card-item-type">
-              <a class="card-item-cog" bs-tooltip="'Pausing an alert rule prevents it from executing'" ng-click="ctrl.pauseAlertRule(alert.id)">
-                <i ng-show="alert.state !== 'paused'" class="fa fa-pause"></i>
-                <i ng-show="alert.state === 'paused'" class="fa fa-play"></i>
-              </a>
-              <a class="card-item-cog" href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert" bs-tooltip="'Edit alert rule'">
-                <i class="icon-gf icon-gf-settings"></i>
-              </a>
-            </div>
-          </div>
-          <div class="card-item-body">
-            <div class="card-item-details">
-              <div class="card-item-name">
-                <a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert">
-                  {{alert.name}}
-                </a>
-              </div>
-              <div class="card-item-sub-name">
-                <span class="alert-list-item-state {{alert.stateModel.stateClass}}">
-                  <i class="{{alert.stateModel.iconClass}}"></i>
-                  {{alert.stateModel.text}} <span class="small muted" ng-show="alert.no_data">(due to no data)</span>
-                </span> for {{alert.newStateDateAgo}}
-              </div>
-              <div class="small muted" ng-show="alert.executionError !== ''">
-                Error: "{{alert.executionError}}"
-              </div>
-            </div>
-          </div>
-        </div>
-      </li>
-    </ol>
-  </section>
-</div>

+ 1 - 1
public/app/routes/ReactContainer.tsx

@@ -13,7 +13,7 @@ function WrapInProvider(store, Component, props) {
 }
 
 /** @ngInject */
-export function reactContainer($route) {
+export function reactContainer($route, $location) {
   return {
     restrict: 'E',
     template: '',

+ 2 - 1
public/app/routes/routes.ts

@@ -226,8 +226,9 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
       controller: 'AlertListCtrl',
       controllerAs: 'ctrl',
     })
-    .when('/alerting/list2', {
+    .when('/alerting/list', {
       template: '<react-container />',
+      reloadOnSearch: false,
       resolve: {
         component: () => AlertRuleList,
       },

+ 2 - 4
public/app/stores/AlertListStore.ts

@@ -61,14 +61,12 @@ export const AlertListStore = types
 
     loadRules: flow(function* load() {
       let backendSrv = getEnv(self).backendSrv;
-
       let filters = { state: self.stateFilter };
-
-      let rules = yield backendSrv.get('/api/alerts', filters);
+      let apiRules = yield backendSrv.get('/api/alerts', filters);
 
       self.rules.clear();
 
-      for (let rule of rules) {
+      for (let rule of apiRules) {
         setStateFields(rule, rule.state);
 
         if (rule.executionError) {

+ 5 - 0
public/app/stores/RootStore.ts

@@ -3,6 +3,7 @@ import { SearchStore } from './SearchStore';
 import { ServerStatsStore } from './ServerStatsStore';
 import { NavStore } from './NavStore';
 import { AlertListStore } from './AlertListStore';
+import { ViewStore } from './ViewStore';
 
 export const RootStore = types.model({
   search: types.optional(SearchStore, {
@@ -15,6 +16,10 @@ export const RootStore = types.model({
   alertList: types.optional(AlertListStore, {
     rules: [],
   }),
+  view: types.optional(ViewStore, {
+    path: '',
+    query: {},
+  }),
 });
 
 type IRootStoreType = typeof RootStore.Type;

+ 35 - 0
public/app/stores/ViewStore.ts

@@ -0,0 +1,35 @@
+import { types } from 'mobx-state-tree';
+import _ from 'lodash';
+import $ from 'jquery';
+
+export const ViewStore = types
+  .model({
+    path: types.string,
+    query: types.map(types.string),
+  })
+  .views(self => ({
+    get currentUrl() {
+      let path = self.path;
+      if (self.query.size) {
+        path += '?' + $.param(self.query.toJS());
+      }
+      return path;
+    },
+  }))
+  .actions(self => ({
+    updatePathAndQuery(path: string, query: any) {
+      self.path = path;
+      self.query.clear();
+
+      for (let key of _.keys(query)) {
+        self.query.set(key, query[key]);
+      }
+    },
+
+    updateQuery(query: any) {
+      self.query.clear();
+      for (let key of _.keys(query)) {
+        self.query.set(key, query[key]);
+      }
+    },
+  }));

+ 2 - 0
public/app/stores/store.ts

@@ -11,4 +11,6 @@ export function createStore(backendSrv) {
       navTree: config.bootData.navTree,
     }
   );
+
+  return store;
 }