import { OsdSmartListComponent } from './osd/osd-smart-list/osd-smart-list.component';
import { AlertListComponent } from './prometheus/alert-list/alert-list.component';
import { PrometheusTabsComponent } from './prometheus/prometheus-tabs/prometheus-tabs.component';
+import { RulesListComponent } from './prometheus/rules-list/rules-list.component';
import { SilenceFormComponent } from './prometheus/silence-form/silence-form.component';
import { SilenceListComponent } from './prometheus/silence-list/silence-list.component';
import { SilenceMatcherModalComponent } from './prometheus/silence-matcher-modal/silence-matcher-modal.component';
OsdDevicesSelectionModalComponent,
InventoryDevicesComponent,
OsdDevicesSelectionGroupsComponent,
- OsdCreationPreviewModalComponent
+ OsdCreationPreviewModalComponent,
+ RulesListComponent,
+ AlertListComponent
]
})
export class ClusterModule {}
<cd-prometheus-tabs></cd-prometheus-tabs>
+<h3 class="cd-header"
+ i18n>All Alerts</h3>
+<cd-rules-list [data]="prometheusAlertService.rules"></cd-rules-list>
+
+<h3 class="cd-header"
+ i18n>Active Alerts</h3>
<cd-table [data]="prometheusAlertService.alerts"
[columns]="columns"
identifier="fingerprint"
i18nProviders,
PermissionHelper
} from '../../../../../testing/unit-test-helper';
+import { CoreModule } from '../../../../core/core.module';
import { TableActionsComponent } from '../../../../shared/datatable/table-actions/table-actions.component';
import { SharedModule } from '../../../../shared/shared.module';
-import { PrometheusTabsComponent } from '../prometheus-tabs/prometheus-tabs.component';
+import { CephModule } from '../../../ceph.module';
+import { DashboardModule } from '../../../dashboard/dashboard.module';
+import { ClusterModule } from '../../cluster.module';
import { AlertListComponent } from './alert-list.component';
describe('AlertListComponent', () => {
TabsModule.forRoot(),
RouterTestingModule,
ToastrModule.forRoot(),
- SharedModule
+ SharedModule,
+ ClusterModule,
+ DashboardModule,
+ CephModule,
+ CoreModule
],
- declarations: [AlertListComponent, PrometheusTabsComponent],
+ declarations: [],
providers: [i18nProviders]
});
--- /dev/null
+<cd-table [data]="data"
+ [columns]="columns"
+ (updateSelection)="selectionUpdated($event)"
+ [selectionType]="'single'"></cd-table>
+
+<cd-table-key-value [data]="selectedRule"
+ [renderObjects]="true"
+ [hideKeys]="hideKeys"></cd-table-key-value>
--- /dev/null
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
+import { PrometheusService } from '../../../../shared/api/prometheus.service';
+import { SettingsService } from '../../../../shared/api/settings.service';
+import { SharedModule } from '../../../../shared/shared.module';
+import { RulesListComponent } from './rules-list.component';
+
+describe('RulesListComponent', () => {
+ let component: RulesListComponent;
+ let fixture: ComponentFixture<RulesListComponent>;
+
+ configureTestBed({
+ declarations: [RulesListComponent],
+ imports: [HttpClientTestingModule, SharedModule],
+ providers: [PrometheusService, SettingsService, i18nProviders]
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RulesListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component, Input, OnInit } from '@angular/core';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+
+import { CdTableColumn } from '../../../../shared/models/cd-table-column';
+import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
+import { PrometheusRule } from '../../../../shared/models/prometheus-alerts';
+import { DurationPipe } from '../../../../shared/pipes/duration.pipe';
+
+@Component({
+ selector: 'cd-rules-list',
+ templateUrl: './rules-list.component.html',
+ styleUrls: ['./rules-list.component.scss']
+})
+export class RulesListComponent implements OnInit {
+ @Input()
+ data: any;
+ columns: CdTableColumn[];
+ selectedRule: PrometheusRule;
+
+ /**
+ * Hide active alerts in details of alerting rules as they are already shown
+ * in the 'active alerts' table. Also hide the 'type' column as the type is
+ * always supposed to be 'alerting'.
+ */
+ hideKeys = ['alerts', 'type'];
+
+ constructor(private i18n: I18n) {}
+
+ ngOnInit() {
+ this.columns = [
+ { prop: 'name', name: this.i18n('Name') },
+ { prop: 'labels.severity', name: this.i18n('Severity') },
+ { prop: 'group', name: this.i18n('Group') },
+ { prop: 'duration', name: this.i18n('Duration'), pipe: new DurationPipe() },
+ { prop: 'query', name: this.i18n('Query'), isHidden: true },
+ { prop: 'annotations.description', name: this.i18n('Description') }
+ ];
+ }
+
+ selectionUpdated(selection: CdTableSelection) {
+ this.selectedRule = selection.first();
+ }
+}
expect(req.request.method).toBe('GET');
});
- it('should get prometheus rules', () => {
- service.getRules({}).subscribe();
- const req = httpTesting.expectOne('api/prometheus/rules');
- expect(req.request.method).toBe('GET');
+ describe('test getRules()', () => {
+ let data: {}; // Subset of PrometheusRuleGroup to keep the tests concise.
+
+ beforeEach(() => {
+ data = {
+ groups: [
+ {
+ name: 'test',
+ rules: [
+ {
+ name: 'load_0',
+ type: 'alerting'
+ },
+ {
+ name: 'load_1',
+ type: 'alerting'
+ },
+ {
+ name: 'load_2',
+ type: 'alerting'
+ }
+ ]
+ },
+ {
+ name: 'recording_rule',
+ rules: [
+ {
+ name: 'node_memory_MemUsed_percent',
+ type: 'recording'
+ }
+ ]
+ }
+ ]
+ };
+ });
+
+ it('should get rules without applying filters', () => {
+ service.getRules().subscribe((rules) => {
+ expect(rules).toEqual(data);
+ });
+
+ const req = httpTesting.expectOne('api/prometheus/rules');
+ expect(req.request.method).toBe('GET');
+ req.flush(data);
+ });
+
+ it('should get rewrite rules only', () => {
+ service.getRules('rewrites').subscribe((rules) => {
+ expect(rules).toEqual({
+ groups: [{ name: 'test', rules: [] }, { name: 'recording_rule', rules: [] }]
+ });
+ });
+
+ const req = httpTesting.expectOne('api/prometheus/rules');
+ expect(req.request.method).toBe('GET');
+ req.flush(data);
+ });
+
+ it('should get alerting rules only', () => {
+ service.getRules('alerting').subscribe((rules) => {
+ expect(rules).toEqual({
+ groups: [
+ {
+ name: 'test',
+ rules: [
+ { name: 'load_0', type: 'alerting' },
+ { name: 'load_1', type: 'alerting' },
+ { name: 'load_2', type: 'alerting' }
+ ]
+ },
+ { name: 'recording_rule', rules: [] }
+ ]
+ });
+ });
+
+ const req = httpTesting.expectOne('api/prometheus/rules');
+ expect(req.request.method).toBe('GET');
+ req.flush(data);
+ });
});
describe('ifAlertmanagerConfigured', () => {
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
import { AlertmanagerSilence } from '../models/alertmanager-silence';
import {
return this.http.get<AlertmanagerSilence[]>(`${this.baseURL}/silences`, { params });
}
- getRules(params = {}): Observable<{ groups: PrometheusRuleGroup[] }> {
- return this.http.get<{ groups: PrometheusRuleGroup[] }>(`${this.baseURL}/rules`, { params });
+ getRules(
+ type: 'all' | 'alerting' | 'rewrites' = 'all'
+ ): Observable<{ groups: PrometheusRuleGroup[] }> {
+ let rules = this.http.get<{ groups: PrometheusRuleGroup[] }>(`${this.baseURL}/rules`);
+ const filterByType = (_type: 'alerting' | 'rewrites') => {
+ return rules.pipe(
+ map((_rules) => {
+ _rules.groups = _rules.groups.map((group) => {
+ group.rules = group.rules.filter((rule) => rule.type === _type);
+ return group;
+ });
+ return _rules;
+ })
+ );
+ };
+ switch (type) {
+ case 'alerting':
+ case 'rewrites':
+ rules = filterByType(type);
+ break;
+ }
+ return rules;
}
setSilence(silence: AlertmanagerSilence) {
]);
});
+ it('should not show data supposed to be have hidden by key', () => {
+ component.data = [['a', 1], ['b', 2]];
+ component.hideKeys = ['a'];
+ component.ngOnInit();
+ expect(component.tableData).toEqual([{ key: 'b', value: 2 }]);
+ });
+
it('should remove items with objects as values', () => {
component.data = [[3, 'something'], ['will be removed', { a: 3, b: 4, c: 5 }]];
component.ngOnInit();
appendParentKey = true;
@Input()
hideEmpty = false;
+ @Input()
+ hideKeys = []; // Keys of pairs not to be displayed
// If set, the classAddingTpl is used to enable different css for different values
@Input()
if (!this.data) {
return; // Wait for data
}
- this.tableData = this.makePairs(this.data);
+ let pairs = this.makePairs(this.data);
+ if (this.hideKeys) {
+ pairs = pairs.filter((pair) => !this.hideKeys.includes(pair.key));
+ }
+ this.tableData = pairs;
}
private makePairs(data: any): KeyValueItem[] {
alerts: PrometheusAlert[]; // Shows only active alerts
health: string;
type: string;
+ group?: string; // Added field for flattened list
}
export class AlertmanagerAlert extends CommonAlertmanagerAlert {
getAlerts: () => ({ subscribe: (_fn, err) => err(resp) }),
disableAlertmanagerConfig: () => (disabledSetting = true)
} as object) as PrometheusService);
- service.refresh();
+ service.getAlerts();
expect(disabledSetting).toBe(expectation);
};
});
});
+ it('should flatten the response of getRules()', () => {
+ service = TestBed.get(PrometheusAlertService);
+ prometheusService = TestBed.get(PrometheusService);
+
+ spyOn(service['prometheusService'], 'ifPrometheusConfigured').and.callFake((fn) => fn());
+ spyOn(prometheusService, 'getRules').and.returnValue(
+ of({
+ groups: [
+ {
+ name: 'group1',
+ rules: [{ name: 'nearly_full', type: 'alerting' }]
+ },
+ {
+ name: 'test',
+ rules: [
+ { name: 'load_0', type: 'alerting' },
+ { name: 'load_1', type: 'alerting' },
+ { name: 'load_2', type: 'alerting' }
+ ]
+ }
+ ]
+ })
+ );
+
+ service.getRules();
+
+ expect(service.rules as any).toEqual([
+ { name: 'nearly_full', type: 'alerting', group: 'group1' },
+ { name: 'load_0', type: 'alerting', group: 'test' },
+ { name: 'load_1', type: 'alerting', group: 'test' },
+ { name: 'load_2', type: 'alerting', group: 'test' }
+ ]);
+ });
+
describe('refresh', () => {
beforeEach(() => {
service = TestBed.get(PrometheusAlertService);
import * as _ from 'lodash';
import { PrometheusService } from '../api/prometheus.service';
-import { AlertmanagerAlert, PrometheusCustomAlert } from '../models/prometheus-alerts';
+import {
+ AlertmanagerAlert,
+ PrometheusCustomAlert,
+ PrometheusRule
+} from '../models/prometheus-alerts';
import { PrometheusAlertFormatter } from './prometheus-alert-formatter';
@Injectable({
export class PrometheusAlertService {
private canAlertsBeNotified = false;
alerts: AlertmanagerAlert[] = [];
+ rules: PrometheusRule[] = [];
constructor(
private alertFormatter: PrometheusAlertFormatter,
private prometheusService: PrometheusService
) {}
- refresh() {
+ getAlerts() {
this.prometheusService.ifAlertmanagerConfigured(() => {
this.prometheusService.getAlerts().subscribe(
(alerts) => this.handleAlerts(alerts),
});
}
+ getRules() {
+ this.prometheusService.ifPrometheusConfigured(() => {
+ this.prometheusService.getRules('alerting').subscribe((groups) => {
+ this.rules = groups['groups'].reduce((acc, group) => {
+ return acc.concat(
+ group.rules.map((rule) => {
+ rule.group = group.name;
+ return rule;
+ })
+ );
+ }, []);
+ });
+ });
+ }
+
+ refresh() {
+ this.getAlerts();
+ this.getRules();
+ }
+
private handleAlerts(alerts: AlertmanagerAlert[]) {
if (this.canAlertsBeNotified) {
this.notifyOnAlertChanges(alerts, this.alerts);