Commit 8f50795a by Torkel Ödegaard

tech: url and query mobx store so now react components and containers can read…

tech: url and query mobx store so now react components and containers can read and modify url path and query via mobx store
parent 029317ed
......@@ -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>
......
......@@ -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,
......
......@@ -8,6 +8,6 @@ define([
'./segment_srv',
'./backend_srv',
'./dynamic_directive_srv',
'./global_event_srv'
'./bridge_srv'
],
function () {});
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);
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(() => {
......
///<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);
import './alert_list_ctrl';
import './notifications_list_ctrl';
import './notification_edit_ctrl';
<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>
......@@ -13,7 +13,7 @@ function WrapInProvider(store, Component, props) {
}
/** @ngInject */
export function reactContainer($route) {
export function reactContainer($route, $location) {
return {
restrict: 'E',
template: '',
......
......@@ -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,
},
......
......@@ -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) {
......
......@@ -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;
......
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]);
}
},
}));
......@@ -11,4 +11,6 @@ export function createStore(backendSrv) {
navTree: config.bootData.navTree,
}
);
return store;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment