Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HZN-1357: ReST API for Situation Feedback #28

Merged
merged 5 commits into from
Aug 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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