Skip to content

Commit

Permalink
Merge pull request #28 from OpenNMS/smith/HZN-1357
Browse files Browse the repository at this point in the history
HZN-1357: ReST API for Situation Feedback
  • Loading branch information
j-white authored Aug 22, 2018
2 parents 175b257 + 0bf0bdd commit 67043da
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 2 deletions.
6 changes: 6 additions & 0 deletions src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {TicketerConfig} from './api/TicketerConfig';
import {AlarmDAO} from './dao/AlarmDAO';
import {EventDAO} from './dao/EventDAO';
import {NodeDAO} from './dao/NodeDAO';
import {SituationFeedbackDAO} from './dao/SituationFeedbackDAO';
import {V1FilterProcessor} from './dao/V1FilterProcessor';
import {V2FilterProcessor} from './dao/V2FilterProcessor';

Expand All @@ -40,6 +41,8 @@ import {OnmsPrimaryType, PrimaryTypes} from './model/OnmsPrimaryType';
import {OnmsServiceStatusType, ServiceStatusTypes} from './model/OnmsServiceStatusType';
import {OnmsServiceType, ServiceTypes} from './model/OnmsServiceType';
import {OnmsSeverity, Severities} from './model/OnmsSeverity';
import {OnmsSituationFeedback} from './model/OnmsSituationFeedback';
import {OnmsSituationFeedbackType} from './model/OnmsSituationFeedbackType';
import {OnmsSnmpInterface} from './model/OnmsSnmpInterface';
import {OnmsSnmpStatusType, SnmpStatusTypes} from './model/OnmsSnmpStatusType';
import {OnmsTroubleTicketState, TroubleTicketStates} from './model/OnmsTroubleTicketState';
Expand Down Expand Up @@ -87,6 +90,7 @@ const DAO = Object.freeze({
AlarmDAO,
EventDAO,
NodeDAO,
SituationFeedbackDAO,
V1FilterProcessor,
V2FilterProcessor,
});
Expand Down Expand Up @@ -120,6 +124,8 @@ const Model = Object.freeze({
ServiceTypes,
OnmsSeverity,
Severities,
OnmsSituationFeedback,
OnmsSituationFeedbackType,
OnmsSnmpInterface,
OnmsSnmpStatusType,
SnmpStatusTypes,
Expand Down
6 changes: 6 additions & 0 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {AlarmDAO} from './dao/AlarmDAO';
import {EventDAO} from './dao/EventDAO';
import {FlowDAO} from './dao/FlowDAO';
import {NodeDAO} from './dao/NodeDAO';
import {SituationFeedbackDAO} from './dao/SituationFeedbackDAO';

import {AxiosHTTP} from './rest/AxiosHTTP';

Expand Down Expand Up @@ -165,4 +166,9 @@ export class Client implements IHasHTTP {
public flows() {
return new FlowDAO(this);
}

/** Get a situationFeedback DAO for submitting and querying correlation feedback. */
public situationfeedback() {
return new SituationFeedbackDAO(this);
}
}
6 changes: 4 additions & 2 deletions src/api/OnmsServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ export class OnmsServer {
if (forFragment === undefined) {
return this.url;
}
let uri = URI(this.url);
if (forFragment.indexOf('/') === 0 || forFragment.indexOf('http') === 0) {
return forFragment;
uri = URI(forFragment);
} else {
uri = uri.segment(forFragment);
}
let uri = URI(this.url).segment(forFragment);
if (withQuery !== undefined) {
uri = uri.addQuery(withQuery);
}
Expand Down
121 changes: 121 additions & 0 deletions src/dao/SituationFeedbackDAO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {BaseDAO} from './BaseDAO';

import {IHasHTTP} from '../api/IHasHTTP';
import {IHash} from '../internal/IHash';
import {IOnmsHTTP} from '../api/IOnmsHTTP';
import {OnmsError} from '../api/OnmsError';
import {OnmsHTTPOptions} from '../api/OnmsHTTPOptions';

import {OnmsSituationFeedback} from '../model/OnmsSituationFeedback';
import {FeedbackTypes, OnmsSituationFeedbackType} from '../model/OnmsSituationFeedbackType';

import {OnmsParm} from '../model/OnmsParm';
import {OnmsServiceType} from '../model/OnmsServiceType';

import {log, catDao} from '../api/Log';
import {Category} from 'typescript-logging';

/**
* Data access for [[OnmsSituationFeedback]] objects.
* @module SituationFeedbackDAO
*/
export class SituationFeedbackDAO extends BaseDAO {

constructor(impl: IHasHTTP | IOnmsHTTP) {
super(impl);
}

/**
* Retrieve feedback.
*
* @version ReST v1
* @param {number} situationId - The alarmId of the Situation to use when querying.
* @return An array of [[OnmsSituationFeedback]] objects.
*/
public async getFeedback(situationId: number): Promise<OnmsSituationFeedback[]> {
const options = new OnmsHTTPOptions();
options.headers.accept = 'application/json';
return this.http.get(this.pathToEndpoint() + '/' + situationId, options).then((result) => {
const data = this.getData(result);
if (!Array.isArray(data)) {
if (!data) {
return [] as OnmsSituationFeedback[];
}
throw new OnmsError('Expected an array of feedback but got "' + (typeof data) + '" instead.');
}
return data.map((feedbackData) => {
return this.fromData(feedbackData);
});
});
}

/**
* Submit Correlation Feedback for a Situation.
*
* @version ReST v1
* @param {number} situationId - The alarmId of the Situation to use when querying.
* @param {OnmsSituationFeedback[]} feedback - The [[OnmsSituationFeedback]].
*/
public async saveFeedback(feedback: OnmsSituationFeedback[], situationId: number): Promise<void> {
return this.post(this.pathToEndpoint() + '/' + situationId, feedback);
}

/**
* Extracts the data from an HTTP Request result.
*
* @param result the HTTP Request result.
* @returns An array of [[OnmsSituationFeedback]] objects.
*/
public getData(result: any): OnmsSituationFeedback[] {
const data = result.data;
if (!Array.isArray(data)) {
throw new OnmsError('Expected an array of situationFeedback but got "' + (typeof data) + '" instead.');
}
return data;
}

/**
* Generate a feedback object from the given dictionary.
* @hidden
*/
public fromData(data: any) {
const feedback = new OnmsSituationFeedback();
feedback.situationKey = data.situationKey;
feedback.fingerprint = data.situationFingerprint;
feedback.alarmKey = data.alarmKey;
feedback.reason = data.reason;
feedback.user = data.user;
if (data.feedbackType) {
const fbt = data.feedbackType;
feedback.feedbackType = OnmsSituationFeedbackType.forId(fbt);
}
feedback.timestamp = this.toNumber(data.timestamp);
return feedback;
}

/**
* Call a POST request in the format the SituationFeedback API expects.
* @hidden
*/
private async post(url: string, data: any): Promise<void> {
const options = new OnmsHTTPOptions();
options.headers['content-type'] = 'application/json';
options.headers.accept = 'application/json';
options.data = data;
return this.http.post(url, options).then((result) => {
if (!result.isSuccess) {
throw result;
}
return;
});
}

/**
* Get the path to the SituationFeedback endpoint.
* @hidden
*/
private pathToEndpoint() {
return 'rest/situation-feedback';
}

}
34 changes: 34 additions & 0 deletions src/model/OnmsSituationFeedback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {IHasUrlValue} from '../api/IHasUrlValue';
import {OnmsSituationFeedbackType} from './OnmsSituationFeedbackType';

/**
* Represents an OpenNMS alarm.
* @module OnmsAlarm
*/
export class OnmsSituationFeedback implements IHasUrlValue {

/** the situation reduction key */
public situationKey: string;

/** signature of situation having given set of alarms */
public fingerprint: string;

/** the related alarm reduction key */
public alarmKey: string;

/** the related alarm reduction key */
public feedbackType: OnmsSituationFeedbackType;

/** the related alarm reduction key */
public reason: string;

/** the related alarm reduction key */
public user: string;

/** the related alarm reduction key */
public timestamp: number;

public get urlValue() {
return String(this.situationKey);
}
}
37 changes: 37 additions & 0 deletions src/model/OnmsSituationFeedbackType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {IHasUrlValue} from '../api/IHasUrlValue';

import {OnmsEnum, forId, forLabel} from '../internal/OnmsEnum';

/**
* Represents an OpenNMS "SituationFeedback" type.
* @module OnmsSituationFeedbackType
*/
export class OnmsSituationFeedbackType extends OnmsEnum<string> implements IHasUrlValue {
/** Given an ID, return the matching SituationFeedback type object. */
public static forId(id: string) {
return forId(FeedbackTypes, id);
}

/** Given a label, return the matching snmp status type object. */
public static forLabel(label: string) {
return forLabel(FeedbackTypes, label);
}

public get urlValue() {
return String(this.id);
}
}

/* tslint:disable:object-literal-sort-keys */
const FeedbackTypes = {
/** Alarm is correctly correlated */
CORRECT: new OnmsSituationFeedbackType('CORRECT', 'CORRECT'),
/** Alarm was incorrectly correlated */
FALSE_POSITIVE: new OnmsSituationFeedbackType('FALSE_POSITIVE', 'FALSE_POSITIVE'),
/** Alarm was incorrectly ommitted */
FALSE_NEGATIVE: new OnmsSituationFeedbackType('FALSE_NEGATIVE', 'FALSE_NEGATIVE'),
};

/** @hidden */
const frozen = Object.freeze(FeedbackTypes);
export {frozen as FeedbackTypes};
15 changes: 15 additions & 0 deletions test/api/OnmsServer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ describe('Given an instance of OnmsServer...', () => {
expect(server.resolveURL('foo')).toEqual(SERVER_URL + 'foo');
expect(server.resolveURL('foo/')).toEqual(SERVER_URL + 'foo');
});
it('URL starting with "/" are returned as-is.', () => {
expect(server.resolveURL('/rest/foo/')).toEqual('/rest/foo/');
});
it('Absolute with query appends query', () => {
expect(server.resolveURL('/rest/foo', 'foo=bar')).toEqual('/rest/foo?foo%3Dbar');
});
it('multi segment urls are handled.', () => {
expect(server.resolveURL('rest/foo/')).toEqual(SERVER_URL + 'rest/foo');
});
it('Colons are not escaped', () => {
expect(server.resolveURL('rest/foo/A:B:0.0.0.0:C')).toEqual(SERVER_URL + 'rest/foo/A:B:0.0.0.0:C');
});
it('Escape forward slashes', () => {
expect(server.resolveURL('rest/S%2FA%3AB%3A0.0.0.0%3AC')).toEqual(SERVER_URL + 'rest/S%2FA:B:0.0.0.0:C');
});
it('it should have a "host" property', () => {
expect(server.host).toBeDefined();
expect(server.host).toEqual('demo.opennms.org');
Expand Down
56 changes: 56 additions & 0 deletions test/dao/SituationFeedbackDAO.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// tslint:disable-next-line:one-variable-per-declaration
declare const await, describe, beforeEach, it, xit, expect, jest;

import { log, catRoot, setLogLevel } from '../../src/api/Log';
import { LogLevel } from 'typescript-logging';

import { Client } from '../../src/Client';

import { OnmsAuthConfig } from '../../src/api/OnmsAuthConfig';
import { OnmsServer } from '../../src/api/OnmsServer';
import { OnmsResult } from '../../src/api/OnmsResult';

import { Comparators } from '../../src/api/Comparator';
import { Filter } from '../../src/api/Filter';
import { Restriction } from '../../src/api/Restriction';
import { SearchPropertyTypes } from '../../src/api/SearchPropertyType';

import { SituationFeedbackDAO } from '../../src/dao/SituationFeedbackDAO';

import { OnmsSituationFeedback } from '../../src/model/OnmsSituationFeedback';

import { MockHTTP23 } from '../rest/MockHTTP23';
import { OnmsSituationFeedbackType } from '../../src/model/OnmsSituationFeedbackType';

const SERVER_NAME = 'Demo';
const SERVER_URL = 'http://demo.opennms.org/opennms/';
const SERVER_USER = 'demo';
const SERVER_PASSWORD = 'demo';

// tslint:disable-next-line:one-variable-per-declaration
let opennms: Client, server, auth, mockHTTP, dao: SituationFeedbackDAO;

describe('SituationfeedbackDAO with v1 API', () => {
beforeEach((done) => {
auth = new OnmsAuthConfig(SERVER_USER, SERVER_PASSWORD);
server = new OnmsServer(SERVER_NAME, SERVER_URL, auth);
mockHTTP = new MockHTTP23(server);
opennms = new Client(mockHTTP);
dao = new SituationFeedbackDAO(mockHTTP);
Client.getMetadata(server, mockHTTP).then((metadata) => {
server.metadata = metadata;
done();
});
});
it('SituationFeedbackDAO.get(210)', () => {
return dao.getFeedback(210).then((feedback) => {
expect(feedback).toHaveLength(4);
expect(feedback[0].alarmKey).toEqual('uei.opennms.org/alarms/trigger:localhost:0.0.0.0:FEEDBACK_C');
expect(feedback[0].fingerprint).toEqual('NDg3ZjdiMjJmNjgzMTJkMmMxYmJjOTNiMWFlYTQ0NWI=');
expect(feedback[0].feedbackType).toEqual(OnmsSituationFeedbackType.forId('CORRECT'));
expect(feedback[0].reason).toEqual('ALL_CORRECT');
expect(feedback[0].user).toEqual('admin');
expect(feedback[0].timestamp).toEqual(1533835399918);
});
});
});
38 changes: 38 additions & 0 deletions test/rest/23.0.0/get/rest/situation-feedback/feedback.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"situationKey": "uei.opennms.org/alarms/trigger:localhost:0.0.0.0:FEEDBACK_F",
"situationFingerprint": "NDg3ZjdiMjJmNjgzMTJkMmMxYmJjOTNiMWFlYTQ0NWI=",
"alarmKey": "uei.opennms.org/alarms/trigger:localhost:0.0.0.0:FEEDBACK_C",
"feedbackType": "CORRECT",
"reason": "ALL_CORRECT",
"user": "admin",
"timestamp": 1533835399918
},
{
"situationKey": "uei.opennms.org/alarms/trigger:localhost:0.0.0.0:FEEDBACK_F",
"situationFingerprint": "NDg3ZjdiMjJmNjgzMTJkMmMxYmJjOTNiMWFlYTQ0NWI=",
"alarmKey": "uei.opennms.org/alarms/trigger:localhost:0.0.0.0:FEEDBACK_A",
"feedbackType": "CORRECT",
"reason": "ALL_CORRECT",
"user": "admin",
"timestamp": 1533835399918
},
{
"situationKey": "uei.opennms.org/alarms/trigger:localhost:0.0.0.0:FEEDBACK_F",
"situationFingerprint": "NDg3ZjdiMjJmNjgzMTJkMmMxYmJjOTNiMWFlYTQ0NWI=",
"alarmKey": "uei.opennms.org/alarms/trigger:localhost:0.0.0.0:FEEDBACK_A",
"feedbackType": "CORRECT",
"reason": "ALL_CORRECT",
"user": "admin",
"timestamp": 1533835359151
},
{
"situationKey": "uei.opennms.org/alarms/trigger:localhost:0.0.0.0:FEEDBACK_F",
"situationFingerprint": "NDg3ZjdiMjJmNjgzMTJkMmMxYmJjOTNiMWFlYTQ0NWI=",
"alarmKey": "uei.opennms.org/alarms/trigger:localhost:0.0.0.0:FEEDBACK_C",
"feedbackType": "CORRECT",
"reason": "ALL_CORRECT",
"user": "admin",
"timestamp": 1533835359151
}
]
5 changes: 5 additions & 0 deletions test/rest/MockHTTP23.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ export class MockHTTP23 extends AbstractHTTP {
result.type = 'application/json';
return Promise.resolve(result);
}
case 'rest/situation-feedback/210': {
const result = OnmsResult.ok(require('./23.0.0/get/rest/situation-feedback/feedback.json'));
result.type = 'application/json';
return Promise.resolve(result);
}
}

throw new Error('Not yet implemented: GET ' + urlObj.toString());
Expand Down

0 comments on commit 67043da

Please sign in to comment.