| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- ///<reference path="../../headers/common.d.ts" />
- import _ from 'lodash';
- import {ThresholdMapper} from './threshold_mapper';
- import {QueryPart} from 'app/core/components/query_part/query_part';
- import alertDef from './alert_def';
- import config from 'app/core/config';
- import moment from 'moment';
- import appEvents from 'app/core/app_events';
- export class AlertTabCtrl {
- panel: any;
- panelCtrl: any;
- testing: boolean;
- testResult: any;
- subTabIndex: number;
- conditionTypes: any;
- alert: any;
- conditionModels: any;
- evalFunctions: any;
- noDataModes: any;
- addNotificationSegment;
- notifications;
- alertNotifications;
- error: string;
- appSubUrl: string;
- alertHistory: any;
- /** @ngInject */
- constructor(private $scope,
- private $timeout,
- private backendSrv,
- private dashboardSrv,
- private uiSegmentSrv,
- private $q,
- private datasourceSrv,
- private templateSrv) {
- this.panelCtrl = $scope.ctrl;
- this.panel = this.panelCtrl.panel;
- this.$scope.ctrl = this;
- this.subTabIndex = 0;
- this.evalFunctions = alertDef.evalFunctions;
- this.conditionTypes = alertDef.conditionTypes;
- this.noDataModes = alertDef.noDataModes;
- this.appSubUrl = config.appSubUrl;
- }
- $onInit() {
- this.addNotificationSegment = this.uiSegmentSrv.newPlusButton();
- // subscribe to graph threshold handle changes
- var thresholdChangedEventHandler = this.graphThresholdChanged.bind(this);
- this.panelCtrl.events.on('threshold-changed', thresholdChangedEventHandler);
- // set panel alert edit mode
- this.$scope.$on("$destroy", () => {
- this.panelCtrl.events.off("threshold-changed", thresholdChangedEventHandler);
- this.panelCtrl.editingThresholds = false;
- this.panelCtrl.render();
- });
- // build notification model
- this.notifications = [];
- this.alertNotifications = [];
- this.alertHistory = [];
- return this.backendSrv.get('/api/alert-notifications').then(res => {
- this.notifications = res;
- this.initModel();
- this.validateModel();
- });
- }
- getAlertHistory() {
- this.backendSrv.get(`/api/annotations?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50`).then(res => {
- this.alertHistory = _.map(res, ah => {
- ah.time = moment(ah.time).format('MMM D, YYYY HH:mm:ss');
- ah.stateModel = alertDef.getStateDisplayModel(ah.newState);
- ah.metrics = alertDef.joinEvalMatches(ah.data, ', ');
- return ah;
- });
- });
- }
- getNotificationIcon(type) {
- switch (type) {
- case "email": return "fa fa-envelope";
- case "slack": return "fa fa-slack";
- case "webhook": return "fa fa-cubes";
- }
- }
- getNotifications() {
- return Promise.resolve(this.notifications.map(item => {
- return this.uiSegmentSrv.newSegment(item.name);
- }));
- }
- changeTabIndex(newTabIndex) {
- this.subTabIndex = newTabIndex;
- if (this.subTabIndex === 2) {
- this.getAlertHistory();
- }
- }
- notificationAdded() {
- var model = _.find(this.notifications, {name: this.addNotificationSegment.value});
- if (!model) {
- return;
- }
- this.alertNotifications.push({
- name: model.name,
- iconClass: this.getNotificationIcon(model.type),
- isDefault: false
- });
- this.alert.notifications.push({id: model.id});
- // reset plus button
- this.addNotificationSegment.value = this.uiSegmentSrv.newPlusButton().value;
- this.addNotificationSegment.html = this.uiSegmentSrv.newPlusButton().html;
- }
- removeNotification(index) {
- this.alert.notifications.splice(index, 1);
- this.alertNotifications.splice(index, 1);
- }
- initModel() {
- var alert = this.alert = this.panel.alert;
- if (!alert) {
- return;
- }
- alert.conditions = alert.conditions || [];
- if (alert.conditions.length === 0) {
- alert.conditions.push(this.buildDefaultCondition());
- }
- alert.noDataState = alert.noDataState || 'no_data';
- alert.frequency = alert.frequency || '60s';
- alert.handler = alert.handler || 1;
- alert.notifications = alert.notifications || [];
- var defaultName = this.panel.title + ' alert';
- alert.name = alert.name || defaultName;
- this.conditionModels = _.reduce(alert.conditions, (memo, value) => {
- memo.push(this.buildConditionModel(value));
- return memo;
- }, []);
- ThresholdMapper.alertToGraphThresholds(this.panel);
- for (let addedNotification of alert.notifications) {
- var model = _.find(this.notifications, {id: addedNotification.id});
- if (model) {
- model.iconClass = this.getNotificationIcon(model.type);
- this.alertNotifications.push(model);
- }
- }
- for (let notification of this.notifications) {
- if (notification.isDefault) {
- notification.iconClass = this.getNotificationIcon(notification.type);
- notification.bgColor = "#00678b";
- this.alertNotifications.push(notification);
- }
- }
- this.panelCtrl.editingThresholds = true;
- this.panelCtrl.render();
- }
- graphThresholdChanged(evt) {
- for (var condition of this.alert.conditions) {
- if (condition.type === 'query') {
- condition.evaluator.params[evt.handleIndex] = evt.threshold.value;
- this.evaluatorParamsChanged();
- break;
- }
- }
- }
- buildDefaultCondition() {
- return {
- type: 'query',
- query: {params: ['A', '5m', 'now']},
- reducer: {type: 'avg', params: []},
- evaluator: {type: 'gt', params: [null]},
- };
- }
- validateModel() {
- if (!this.alert) {
- return;
- }
- let firstTarget;
- var fixed = false;
- let foundTarget = null;
- for (var condition of this.alert.conditions) {
- if (condition.type !== 'query') {
- continue;
- }
- for (var target of this.panel.targets) {
- if (!firstTarget) {
- firstTarget = target;
- }
- if (condition.query.params[0] === target.refId) {
- foundTarget = target;
- break;
- }
- }
- if (!foundTarget) {
- if (firstTarget) {
- condition.query.params[0] = firstTarget.refId;
- foundTarget = firstTarget;
- fixed = true;
- } else {
- this.error = "Could not find any metric queries";
- }
- }
- var datasourceName = foundTarget.datasource || this.panel.datasource;
- this.datasourceSrv.get(datasourceName).then(ds => {
- if (!ds.meta.alerting) {
- this.error = 'The datasource does not support alerting queries';
- } else if (this.templateSrv.variableExists(foundTarget.target)) {
- this.error = 'Template variables are not supported in alert queries';
- } else {
- this.error = '';
- }
- });
- }
- }
- buildConditionModel(source) {
- var cm: any = {source: source, type: source.type};
- cm.queryPart = new QueryPart(source.query, alertDef.alertQueryDef);
- cm.reducerPart = alertDef.createReducerPart(source.reducer);
- cm.evaluator = source.evaluator;
- return cm;
- }
- handleQueryPartEvent(conditionModel, evt) {
- switch (evt.name) {
- case "action-remove-part": {
- break;
- }
- case "get-part-actions": {
- return this.$q.when([]);
- }
- case "part-param-changed": {
- this.validateModel();
- }
- case "get-param-options": {
- var result = this.panel.targets.map(target => {
- return this.uiSegmentSrv.newSegment({ value: target.refId });
- });
- return this.$q.when(result);
- }
- }
- }
- handleReducerPartEvent(conditionModel, evt) {
- switch (evt.name) {
- case "action": {
- conditionModel.source.reducer.type = evt.action.value;
- conditionModel.reducerPart = alertDef.createReducerPart(conditionModel.source.reducer);
- break;
- }
- case "get-part-actions": {
- var result = [];
- for (var type of alertDef.reducerTypes) {
- if (type.value !== conditionModel.source.reducer.type) {
- result.push(type);
- }
- }
- return this.$q.when(result);
- }
- }
- }
- addCondition(type) {
- var condition = this.buildDefaultCondition();
- // add to persited model
- this.alert.conditions.push(condition);
- // add to view model
- this.conditionModels.push(this.buildConditionModel(condition));
- }
- removeCondition(index) {
- this.alert.conditions.splice(index, 1);
- this.conditionModels.splice(index, 1);
- }
- delete() {
- appEvents.emit('confirm-modal', {
- title: 'Delete Alert',
- text: 'Are you sure you want to delete this alert rule?',
- text2: 'You need to save dashboard for the delete to take effect',
- icon: 'fa-trash',
- yesText: 'Delete',
- onConfirm: () => {
- delete this.panel.alert;
- this.alert = null;
- this.panel.thresholds = [];
- this.conditionModels = [];
- this.panelCtrl.render();
- }
- });
- }
- enable() {
- this.panel.alert = {};
- this.initModel();
- }
- evaluatorParamsChanged() {
- ThresholdMapper.alertToGraphThresholds(this.panel);
- this.panelCtrl.render();
- }
- evaluatorTypeChanged(evaluator) {
- // ensure params array is correct length
- switch (evaluator.type) {
- case "lt":
- case "gt": {
- evaluator.params = [evaluator.params[0]];
- break;
- }
- case "within_range":
- case "outside_range": {
- evaluator.params = [evaluator.params[0], evaluator.params[1]];
- break;
- }
- case "no_value": {
- evaluator.params = [];
- }
- }
- this.evaluatorParamsChanged();
- }
- test() {
- this.testing = true;
- var payload = {
- dashboard: this.dashboardSrv.getCurrent().getSaveModelClone(),
- panelId: this.panelCtrl.panel.id,
- };
- return this.backendSrv.post('/api/alerts/test', payload).then(res => {
- this.testResult = res;
- this.testing = false;
- });
- }
- }
- /** @ngInject */
- export function alertTab() {
- 'use strict';
- return {
- restrict: 'E',
- scope: true,
- templateUrl: 'public/app/features/alerting/partials/alert_tab.html',
- controller: AlertTabCtrl,
- };
- }
|