diff --git a/src/panel-triggers/components/AlertList/AlertCard.tsx b/src/panel-triggers/components/AlertList/AlertCard.tsx index d5c265a..46d9dd1 100644 --- a/src/panel-triggers/components/AlertList/AlertCard.tsx +++ b/src/panel-triggers/components/AlertList/AlertCard.tsx @@ -56,6 +56,7 @@ export default class AlertCard extends PureComponent 1; const cardClass = classNames('alert-rule-item', 'zbx-trigger-card', { 'zbx-trigger-highlighted': panelOptions.highlightBackground }); const descriptionClass = classNames('alert-rule-item__text', { 'zbx-description--newline': panelOptions.descriptionAtNewLine }); const severityDesc = _.find(panelOptions.triggerSeverity, s => s.priority === Number(problem.priority)); @@ -134,7 +135,7 @@ export default class AlertCard extends PureComponent - {panelOptions.datasources.length > 1 && ( + {showDatasourceName && (
diff --git a/src/panel-triggers/components/AlertList/AlertList.tsx b/src/panel-triggers/components/AlertList/AlertList.tsx index e893bbd..8d1d08b 100644 --- a/src/panel-triggers/components/AlertList/AlertList.tsx +++ b/src/panel-triggers/components/AlertList/AlertList.tsx @@ -68,7 +68,7 @@ export default class AlertList extends PureComponent {currentProblems.map(problem =>
@@ -15,50 +15,50 @@
-
+
-
{{ ds }}
+
{{ target.datasource }}
@@ -67,35 +67,35 @@
diff --git a/src/panel-triggers/specs/migrations.spec.js b/src/panel-triggers/specs/migrations.spec.ts similarity index 76% rename from src/panel-triggers/specs/migrations.spec.js rename to src/panel-triggers/specs/migrations.spec.ts index f411350..bd916e8 100644 --- a/src/panel-triggers/specs/migrations.spec.js +++ b/src/panel-triggers/specs/migrations.spec.ts @@ -5,16 +5,16 @@ import {DEFAULT_TARGET, DEFAULT_SEVERITY, PANEL_DEFAULTS} from '../triggers_pane import {CURRENT_SCHEMA_VERSION} from '../migrations'; describe('Triggers Panel schema migration', () => { - let ctx = {}; + let ctx: any = {}; let updatePanelCtrl; - let datasourceSrvMock = { + const datasourceSrvMock = { getMetricSources: () => { return [{ meta: {id: 'alexanderzobnin-zabbix-datasource'}, value: {}, name: 'zabbix_default' }]; }, get: () => Promise.resolve({}) }; - let timeoutMock = () => {}; + const timeoutMock = () => {}; beforeEach(() => { ctx = { @@ -47,14 +47,16 @@ describe('Triggers Panel schema migration', () => { }); it('should update old panel schema', () => { - let updatedPanelCtrl = updatePanelCtrl(ctx.scope); + const updatedPanelCtrl = updatePanelCtrl(ctx.scope); - let expected = _.defaultsDeep({ + const expected = _.defaultsDeep({ schemaVersion: CURRENT_SCHEMA_VERSION, - datasources: ['zabbix'], - targets: { - 'zabbix': DEFAULT_TARGET - }, + targets: [ + { + ...DEFAULT_TARGET, + datasource: 'zabbix', + } + ], ageField: true, statusField: false, severityField: false, @@ -68,29 +70,29 @@ describe('Triggers Panel schema migration', () => { it('should create new panel with default schema', () => { ctx.scope.panel = {}; - let updatedPanelCtrl = updatePanelCtrl(ctx.scope); + const updatedPanelCtrl = updatePanelCtrl(ctx.scope); - let expected = _.defaultsDeep({ + const expected = _.defaultsDeep({ schemaVersion: CURRENT_SCHEMA_VERSION, - datasources: ['zabbix_default'], - targets: { - 'zabbix_default': DEFAULT_TARGET - } + targets: [{ + ...DEFAULT_TARGET, + datasource: 'zabbix_default' + }] }, PANEL_DEFAULTS); expect(updatedPanelCtrl.panel).toEqual(expected); }); it('should set default targets for new panel with empty targets', () => { ctx.scope.panel = { - targets: [{}] + targets: [] }; - let updatedPanelCtrl = updatePanelCtrl(ctx.scope); + const updatedPanelCtrl = updatePanelCtrl(ctx.scope); - let expected = _.defaultsDeep({ - datasources: ['zabbix_default'], - targets: { - 'zabbix_default': DEFAULT_TARGET - }, + const expected = _.defaultsDeep({ + targets: [{ + ...DEFAULT_TARGET, + datasource: 'zabbix_default' + }] }, PANEL_DEFAULTS); expect(updatedPanelCtrl.panel).toEqual(expected); diff --git a/src/panel-triggers/specs/panel_ctrl.spec.js b/src/panel-triggers/specs/panel_ctrl.spec.ts similarity index 84% rename from src/panel-triggers/specs/panel_ctrl.spec.js rename to src/panel-triggers/specs/panel_ctrl.spec.ts index bdfa90b..67283c9 100644 --- a/src/panel-triggers/specs/panel_ctrl.spec.js +++ b/src/panel-triggers/specs/panel_ctrl.spec.ts @@ -5,9 +5,9 @@ import {PANEL_DEFAULTS, DEFAULT_TARGET} from '../triggers_panel_ctrl'; // import { create } from 'domain'; describe('TriggerPanelCtrl', () => { - let ctx = {}; + let ctx: any = {}; let datasourceSrvMock, zabbixDSMock; - let timeoutMock = () => {}; + const timeoutMock = () => {}; let createPanelCtrl; beforeEach(() => { @@ -61,7 +61,7 @@ describe('TriggerPanelCtrl', () => { describe('When adding new panel', () => { it('should suggest all zabbix data sources', () => { ctx.scope.panel = {}; - let panelCtrl = createPanelCtrl(); + const panelCtrl = createPanelCtrl(); expect(panelCtrl.available_datasources).toEqual([ 'zabbix_default', 'zabbix' ]); @@ -69,10 +69,8 @@ describe('TriggerPanelCtrl', () => { it('should load first zabbix data source as default', () => { ctx.scope.panel = {}; - let panelCtrl = createPanelCtrl(); - expect(panelCtrl.panel.datasources).toEqual([ - 'zabbix_default' - ]); + const panelCtrl = createPanelCtrl(); + expect(panelCtrl.panel.targets[0].datasource).toEqual('zabbix_default'); }); it('should rewrite default empty target', () => { @@ -82,7 +80,7 @@ describe('TriggerPanelCtrl', () => { "refId": "A" }], }; - let panelCtrl = createPanelCtrl(); + const panelCtrl = createPanelCtrl(); expect(panelCtrl.available_datasources).toEqual([ 'zabbix_default', 'zabbix' ]); @@ -92,16 +90,22 @@ describe('TriggerPanelCtrl', () => { describe('When refreshing panel', () => { beforeEach(() => { ctx.scope.panel.datasources = ['zabbix_default', 'zabbix']; - ctx.scope.panel.targets = { - 'zabbix_default': DEFAULT_TARGET, - 'zabbix': DEFAULT_TARGET - }; + ctx.scope.panel.targets = [ + { + ...DEFAULT_TARGET, + datasource: 'zabbix_default' + }, + { + ...DEFAULT_TARGET, + datasource: 'zabbix' + }, + ]; ctx.panelCtrl = createPanelCtrl(); }); it('should format triggers', (done) => { ctx.panelCtrl.onRefresh().then(() => { - let formattedTrigger = _.find(ctx.panelCtrl.triggerList, {triggerid: "1"}); + const formattedTrigger: any = _.find(ctx.panelCtrl.triggerList, {triggerid: "1"}); expect(formattedTrigger.host).toBe('backend01'); expect(formattedTrigger.hostTechName).toBe('backend01_tech'); expect(formattedTrigger.datasource).toBe('zabbix_default'); @@ -113,7 +117,7 @@ describe('TriggerPanelCtrl', () => { it('should sort triggers by time by default', (done) => { ctx.panelCtrl.onRefresh().then(() => { - let trigger_ids = _.map(ctx.panelCtrl.triggerList, 'triggerid'); + const trigger_ids = _.map(ctx.panelCtrl.triggerList, 'triggerid'); expect(trigger_ids).toEqual([ '2', '4', '3', '1' ]); @@ -124,7 +128,7 @@ describe('TriggerPanelCtrl', () => { it('should sort triggers by severity', (done) => { ctx.panelCtrl.panel.sortTriggersBy = { text: 'severity', value: 'priority' }; ctx.panelCtrl.onRefresh().then(() => { - let trigger_ids = _.map(ctx.panelCtrl.triggerList, 'triggerid'); + const trigger_ids = _.map(ctx.panelCtrl.triggerList, 'triggerid'); expect(trigger_ids).toEqual([ '1', '3', '2', '4' ]); @@ -134,7 +138,7 @@ describe('TriggerPanelCtrl', () => { it('should add acknowledges to trigger', (done) => { ctx.panelCtrl.onRefresh().then(() => { - let trigger = getTriggerById(1, ctx); + const trigger = getTriggerById(1, ctx); expect(trigger.acknowledges).toHaveLength(1); expect(trigger.acknowledges[0].message).toBe("event ack"); @@ -153,15 +157,15 @@ describe('TriggerPanelCtrl', () => { it('should handle new lines in trigger description', () => { ctx.panelCtrl.setTriggerSeverity = jest.fn((trigger) => trigger); - let trigger = {comments: "this is\ndescription"}; + const trigger = {comments: "this is\ndescription"}; const formattedTrigger = ctx.panelCtrl.formatTrigger(trigger); expect(formattedTrigger.comments).toBe("this is
description"); }); it('should format host name to display (default)', (done) => { ctx.panelCtrl.onRefresh().then(() => { - let trigger = getTriggerById(1, ctx); - let hostname = ctx.panelCtrl.formatHostName(trigger); + const trigger = getTriggerById(1, ctx); + const hostname = ctx.panelCtrl.formatHostName(trigger); expect(hostname).toBe('backend01'); done(); }); @@ -171,8 +175,8 @@ describe('TriggerPanelCtrl', () => { ctx.panelCtrl.panel.hostField = false; ctx.panelCtrl.panel.hostTechNameField = true; ctx.panelCtrl.onRefresh().then(() => { - let trigger = getTriggerById(1, ctx); - let hostname = ctx.panelCtrl.formatHostName(trigger); + const trigger = getTriggerById(1, ctx); + const hostname = ctx.panelCtrl.formatHostName(trigger); expect(hostname).toBe('backend01_tech'); done(); }); @@ -182,8 +186,8 @@ describe('TriggerPanelCtrl', () => { ctx.panelCtrl.panel.hostField = true; ctx.panelCtrl.panel.hostTechNameField = true; ctx.panelCtrl.onRefresh().then(() => { - let trigger = getTriggerById(1, ctx); - let hostname = ctx.panelCtrl.formatHostName(trigger); + const trigger = getTriggerById(1, ctx); + const hostname = ctx.panelCtrl.formatHostName(trigger); expect(hostname).toBe('backend01 (backend01_tech)'); done(); }); @@ -193,8 +197,8 @@ describe('TriggerPanelCtrl', () => { ctx.panelCtrl.panel.hostField = false; ctx.panelCtrl.panel.hostTechNameField = false; ctx.panelCtrl.onRefresh().then(() => { - let trigger = getTriggerById(1, ctx); - let hostname = ctx.panelCtrl.formatHostName(trigger); + const trigger = getTriggerById(1, ctx); + const hostname = ctx.panelCtrl.formatHostName(trigger); expect(hostname).toBe(""); done(); }); @@ -222,7 +226,7 @@ describe('TriggerPanelCtrl', () => { }); }); -const defaultTrigger = { +const defaultTrigger: any = { "triggerid": "13565", "value": "1", "groups": [{"groupid": "1", "name": "Backend"}] , @@ -248,7 +252,7 @@ const defaultTrigger = { "flags": "0", "type": "0", "items": [] , "error": "" }; -const defaultEvent = { +const defaultEvent: any = { "eventid": "11", "acknowledges": [ { @@ -272,8 +276,8 @@ const defaultEvent = { "objectid": "1", }; -function generateTrigger(id, timestamp, severity) { - let trigger = _.cloneDeep(defaultTrigger); +function generateTrigger(id, timestamp?, severity?): any { + const trigger = _.cloneDeep(defaultTrigger); trigger.triggerid = id.toString(); if (severity) { trigger.priority = severity.toString(); @@ -284,13 +288,13 @@ function generateTrigger(id, timestamp, severity) { return trigger; } -function createTrigger(props) { +function createTrigger(props): any { let trigger = _.cloneDeep(defaultTrigger); trigger = _.merge(trigger, props); trigger.lastEvent.objectid = trigger.triggerid; return trigger; } -function getTriggerById(id, ctx) { +function getTriggerById(id, ctx): any { return _.find(ctx.panelCtrl.triggerList, {triggerid: id.toString()}); } diff --git a/src/panel-triggers/triggers_panel_ctrl.js b/src/panel-triggers/triggers_panel_ctrl.js index d8d8247..dad0d7a 100644 --- a/src/panel-triggers/triggers_panel_ctrl.js +++ b/src/panel-triggers/triggers_panel_ctrl.js @@ -10,6 +10,7 @@ import { triggerPanelTriggersTab } from './triggers_tab'; import { migratePanelSchema, CURRENT_SCHEMA_VERSION } from './migrations'; import ProblemList from './components/Problems/Problems'; import AlertList from './components/AlertList/AlertList'; +import { getNextRefIdChar } from './utils'; const ZABBIX_DS_ID = 'alexanderzobnin-zabbix-datasource'; const PROBLEM_EVENTS_LIMIT = 100; @@ -23,7 +24,17 @@ export const DEFAULT_TARGET = { proxy: {filter: ""}, }; -export const getDefaultTarget = () => DEFAULT_TARGET; +export const getDefaultTarget = (targets) => { + return { + group: {filter: ""}, + host: {filter: ""}, + application: {filter: ""}, + trigger: {filter: ""}, + tags: {filter: ""}, + proxy: {filter: ""}, + refId: getNextRefIdChar(targets), + }; +}; export const DEFAULT_SEVERITY = [ { priority: 0, severity: 'Not classified', color: 'rgb(108, 108, 108)', show: true}, @@ -40,8 +51,7 @@ const DEFAULT_TIME_FORMAT = "DD MMM YYYY HH:mm:ss"; export const PANEL_DEFAULTS = { schemaVersion: CURRENT_SCHEMA_VERSION, - datasources: [], - targets: {}, + targets: [getDefaultTarget([])], // Fields hostField: true, hostTechNameField: false, @@ -108,11 +118,8 @@ export class TriggerPanelCtrl extends PanelCtrl { _.defaultsDeep(this.panel, _.cloneDeep(PANEL_DEFAULTS)); this.available_datasources = _.map(this.getZabbixDataSources(), 'name'); - if (this.panel.datasources.length === 0) { - this.panel.datasources.push(this.available_datasources[0]); - } - if (this.isEmptyTargets()) { - this.panel.targets[this.panel.datasources[0]] = getDefaultTarget(); + if (this.panel.targets && !this.panel.targets[0].datasource) { + this.panel.targets[0].datasource = this.available_datasources[0]; } this.initDatasources(); @@ -138,7 +145,11 @@ export class TriggerPanelCtrl extends PanelCtrl { } initDatasources() { - let promises = _.map(this.panel.datasources, (ds) => { + if (!this.panel.targets) { + return; + } + const targetDatasources = _.compact(this.panel.targets.map(target => target.datasource)); + let promises = targetDatasources.map(ds => { // Load datasource return this.datasourceSrv.get(ds) .then(datasource => { @@ -236,14 +247,15 @@ export class TriggerPanelCtrl extends PanelCtrl { const timeTo = Math.ceil(dateMath.parse(this.range.to) / 1000); const userIsEditor = this.contextSrv.isEditor || this.contextSrv.isGrafanaAdmin; - let promises = _.map(this.panel.datasources, (ds) => { + let promises = _.map(this.panel.targets, (target) => { + const ds = target.datasource; let proxies; let showAckButton = true; return this.datasourceSrv.get(ds) .then(datasource => { const zabbix = datasource.zabbix; const showEvents = this.panel.showEvents.value; - const triggerFilter = this.panel.targets[ds]; + const triggerFilter = target; const showProxy = this.panel.hostProxy; const getProxiesPromise = showProxy ? zabbix.getProxies() : () => []; showAckButton = !datasource.disableReadOnlyUsersAck || userIsEditor; @@ -284,8 +296,8 @@ export class TriggerPanelCtrl extends PanelCtrl { }) .then(triggers => this.setMaintenanceStatus(triggers)) .then(triggers => this.setAckButtonStatus(triggers, showAckButton)) - .then(triggers => this.filterTriggersPre(triggers, ds)) - .then(triggers => this.addTriggerDataSource(triggers, ds)) + .then(triggers => this.filterTriggersPre(triggers, target)) + .then(triggers => this.addTriggerDataSource(triggers, target)) .then(triggers => this.addTriggerHostProxy(triggers, proxies)); }); @@ -339,16 +351,17 @@ export class TriggerPanelCtrl extends PanelCtrl { return triggers; } - filterTriggersPre(triggerList, ds) { + filterTriggersPre(triggerList, target) { // Filter triggers by description - let triggerFilter = this.panel.targets[ds].trigger.filter; + const ds = target.datasource; + let triggerFilter = target.trigger.filter; triggerFilter = this.datasources[ds].replaceTemplateVars(triggerFilter); if (triggerFilter) { triggerList = filterTriggers(triggerList, triggerFilter); } // Filter by tags - const target = this.panel.targets[ds]; + // const target = this.panel.targets[ds]; if (target.tags.filter) { let tagsFilter = this.datasources[ds].replaceTemplateVars(target.tags.filter); // replaceTemplateVars() builds regex-like string, so we should trim it. @@ -406,9 +419,9 @@ export class TriggerPanelCtrl extends PanelCtrl { return triggers; } - addTriggerDataSource(triggers, ds) { + addTriggerDataSource(triggers, target) { _.each(triggers, (trigger) => { - trigger.datasource = ds; + trigger.datasource = target.datasource; }); return triggers; } @@ -479,24 +492,24 @@ export class TriggerPanelCtrl extends PanelCtrl { return _.map(tags, (tag) => `${tag.tag}:${tag.value}`).join(', '); } - addTagFilter(tag, ds) { - let tagFilter = this.panel.targets[ds].tags.filter; + addTagFilter(tag, target) { + let tagFilter = target.tags.filter; let targetTags = this.parseTags(tagFilter); let newTag = {tag: tag.tag, value: tag.value}; targetTags.push(newTag); targetTags = _.uniqWith(targetTags, _.isEqual); let newFilter = this.tagsToString(targetTags); - this.panel.targets[ds].tags.filter = newFilter; + target.tags.filter = newFilter; this.refresh(); } - removeTagFilter(tag, ds) { - let tagFilter = this.panel.targets[ds].tags.filter; + removeTagFilter(tag, target) { + let tagFilter = target.tags.filter; let targetTags = this.parseTags(tagFilter); _.remove(targetTags, t => t.tag === tag.tag && t.value === tag.value); targetTags = _.uniqWith(targetTags, _.isEqual); let newFilter = this.tagsToString(targetTags); - this.panel.targets[ds].tags.filter = newFilter; + target.tags.filter = newFilter; this.refresh(); } diff --git a/src/panel-triggers/triggers_tab.js b/src/panel-triggers/triggers_tab.js index db230a0..9b3032b 100644 --- a/src/panel-triggers/triggers_tab.js +++ b/src/panel-triggers/triggers_tab.js @@ -10,7 +10,7 @@ class TriggersTabCtrl { this.panelCtrl = $scope.ctrl; this.panel = this.panelCtrl.panel; this.templateSrv = templateSrv; - this.datasources = this.panelCtrl.datasources; + this.datasources = {}; // Load scope defaults var scopeDefaults = { @@ -21,6 +21,7 @@ class TriggersTabCtrl { oldTarget: _.cloneDeep(this.panel.targets) }; _.defaultsDeep(this, scopeDefaults); + this.selectedDatasources = this.getSelectedDatasources(); this.initDatasources(); this.panelCtrl.refresh(); @@ -30,6 +31,7 @@ class TriggersTabCtrl { return this.panelCtrl.initDatasources() .then((datasources) => { _.each(datasources, (datasource) => { + this.datasources[datasource.name] = datasource; this.bindSuggestionFunctions(datasource); }); }); @@ -44,6 +46,10 @@ class TriggersTabCtrl { this.getProxyNames[ds] = _.bind(this.suggestProxies, this, datasource); } + getSelectedDatasources() { + return _.compact(this.panel.targets.map(target => target.datasource)); + } + suggestGroups(datasource, query, callback) { return datasource.zabbix.getAllGroups() .then(groups => { @@ -53,7 +59,8 @@ class TriggersTabCtrl { } suggestHosts(datasource, query, callback) { - let groupFilter = datasource.replaceTemplateVars(this.panel.targets[datasource.name].group.filter); + const target = this.panel.targets.find(t => t.datasource === datasource.name); + let groupFilter = datasource.replaceTemplateVars(target.group.filter); return datasource.zabbix.getAllHosts(groupFilter) .then(hosts => { return _.map(hosts, 'name'); @@ -62,8 +69,9 @@ class TriggersTabCtrl { } suggestApps(datasource, query, callback) { - let groupFilter = datasource.replaceTemplateVars(this.panel.targets[datasource.name].group.filter); - let hostFilter = datasource.replaceTemplateVars(this.panel.targets[datasource.name].host.filter); + const target = this.panel.targets.find(t => t.datasource === datasource.name); + let groupFilter = datasource.replaceTemplateVars(target.group.filter); + let hostFilter = datasource.replaceTemplateVars(target.host.filter); return datasource.zabbix.getAllApps(groupFilter, hostFilter) .then(apps => { return _.map(apps, 'name'); @@ -78,16 +86,17 @@ class TriggersTabCtrl { } datasourcesChanged() { - _.each(this.panel.datasources, (ds) => { - if (!this.panel.targets[ds]) { - this.panel.targets[ds] = getDefaultTarget(); - } - }); - // Remove unchecked targets - _.each(this.panel.targets, (target, ds) => { - if (!_.includes(this.panel.datasources, ds)) { - delete this.panel.targets[ds]; + const newTargets = []; + _.each(this.selectedDatasources, (ds) => { + const dsTarget = this.panel.targets.find((target => target.datasource === ds)); + if (dsTarget) { + newTargets.push(dsTarget); + } else { + const newTarget = getDefaultTarget(this.panel.targets); + newTarget.datasource = ds; + newTargets.push(newTarget); } + this.panel.targets = newTargets; }); this.parseTarget(); } diff --git a/src/panel-triggers/types.ts b/src/panel-triggers/types.ts index 477d8fb..ffa9e65 100644 --- a/src/panel-triggers/types.ts +++ b/src/panel-triggers/types.ts @@ -1,7 +1,7 @@ export interface ProblemsPanelOptions { schemaVersion: number; datasources: any[]; - targets: Map; + targets: ProblemsPanelTarget[]; // Fields hostField?: boolean; hostTechNameField?: boolean; @@ -62,6 +62,7 @@ export interface ProblemsPanelTarget { proxy: { filter: string }; + datasource: string; } export interface TriggerSeverity { diff --git a/src/panel-triggers/utils.ts b/src/panel-triggers/utils.ts index d88661f..d9e2124 100644 --- a/src/panel-triggers/utils.ts +++ b/src/panel-triggers/utils.ts @@ -1,4 +1,6 @@ +import _ from 'lodash'; import moment from 'moment'; +import { DataQuery } from '@grafana/ui/'; import * as utils from '../datasource-zabbix/utils'; import { ZBXTrigger } from './types'; @@ -20,3 +22,13 @@ export function formatLastChange(lastchangeUnix: number, customFormat?: string) const lastchange = timestamp.format(format); return lastchange; } + +export const getNextRefIdChar = (queries: DataQuery[]): string => { + const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return _.find(letters, refId => { + return _.every(queries, other => { + return other.refId !== refId; + }); + }); +}; diff --git a/tslint.json b/tslint.json index 4c7ea71..ab2614b 100644 --- a/tslint.json +++ b/tslint.json @@ -64,7 +64,6 @@ ], "variable-name": [ true, - "check-format", "ban-keywords", "allow-leading-underscore", "allow-trailing-underscore",