Skip to content

Commit

Permalink
feat(alarms): add support for manipulating journal and sticky memos
Browse files Browse the repository at this point in the history
  • Loading branch information
Jesse White committed Jul 28, 2017
1 parent 36b883c commit 875e268
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 2 deletions.
43 changes: 43 additions & 0 deletions src/CLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,46 @@ const CLI = () => {
});
});

// save a sticky memo
program
.command('saveSticky <id>')
.alias('sticky')
.description('Create or update the sticky memo associated with the alarm')
.option('-u, --user <user>', 'Which user to update the memo as (only administrators can do this)')
.option('-b, --body <body>', 'Memo body')
.action((id, options) => {
id = parseInt(id, 10);
const config = readConfig();
return new Client().connect('OpenNMS', config.url, config.username, config.password).then((client) => {
return client.alarms().saveStickyMemo(id, options.body, options.user).then(() => {
console.log('Success!');
return true;
});
}).catch((err) => {
return handleError('Save failed', err);
});
});

// save a journal memo
program
.command('saveJournal <id>')
.alias('journal')
.description('Create or update the journal memo associated with the alarm')
.option('-u, --user <user>', 'Which user to update the memo as (only administrators can do this)')
.option('-b, --body <body>', 'Memo body')
.action((id, options) => {
id = parseInt(id, 10);
const config = readConfig();
return new Client().connect('OpenNMS', config.url, config.username, config.password).then((client) => {
return client.alarms().saveJournalMemo(id, options.body, options.user).then(() => {
console.log('Success!');
return true;
});
}).catch((err) => {
return handleError('Save failed', err);
});
});

createAlarmAction('unacknowledge', 'Unacknowledge an alarm', 'unack');
createAlarmAction('escalate', 'Escalate an alarm');
createAlarmAction('clear', 'Clear an alarm');
Expand All @@ -278,6 +318,9 @@ const CLI = () => {
createAlarmAction('triggerTicketUpdate', 'Trigger a trouble ticket update for an alarm', 'update');
createAlarmAction('closeTicket', 'Close a trouble ticket for an alarm', 'close');

createAlarmAction('deleteStickyMemo', 'Delete the sticky memo for an alarm', 'deleteSticky');
createAlarmAction('deleteJournalMemo', 'Delete the journal memo for an alarm', 'deleteJournal');

program.parse(process.argv);

if (!process.argv.slice(2).length) {
Expand Down
8 changes: 8 additions & 0 deletions src/api/IOnmsHTTP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,12 @@ export interface IOnmsHTTP {
* @returns An [[OnmsResult]] result object.
*/
post(url: string, options?: OnmsHTTPOptions): Promise<OnmsResult<any>>;

/**
* Perform an HTTP DELETE to the provided URL.
* @param url The URL to connect to.
* @param options The [[OnmsHTTPOptions]] options to use when connecting.
* @returns An [[OnmsResult]] result object.
*/
httpDelete(url: string, options?: OnmsHTTPOptions): Promise<OnmsResult<any>>;
}
96 changes: 96 additions & 0 deletions src/dao/AlarmDAO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,52 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
});
}

/**
* Create or update the sticky memo associated with the alarm.
*
* @version ReST v2
* @param {number|OnmsAlarm} alarm - The [[OnmsAlarm]] or alarm ID.
* @param {string} body - The memo body
* @param {string=} user - The user to update the memo as.
* (Only administrators have the right to do this.)
*/
public async saveStickyMemo(alarm: number|OnmsAlarm, body: string, user?: string): Promise<void> {
return this.saveMemo('memo', alarm, body, user);
}

/**
* Create or update the journal memo associated with the alarm.
*
* @version ReST v2
* @param {number|OnmsAlarm} alarm - The [[OnmsAlarm]] or alarm ID.
* @param {string} body - The memo body
* @param {string=} user - The user to update the memo as.
* (Only administrators have the right to do this.)
*/
public async saveJournalMemo(alarm: number|OnmsAlarm, body: string, user?: string): Promise<void> {
return this.saveMemo('journal', alarm, body, user);
}

/**
* Delete the sticky memo ticket associated with the given alarm.
*
* @version ReST v2
* @param {number|OnmsAlarm} alarm - The [[OnmsAlarm]] or alarm ID.
*/
public async deleteStickyMemo(alarm: number|OnmsAlarm): Promise<void> {
return this.deleteMemo('memo', alarm);
}

/**
* Delete the journal memo ticket associated with the given alarm.
*
* @version ReST v2
* @param {number|OnmsAlarm} alarm - The [[OnmsAlarm]] or alarm ID.
*/
public async deleteJournalMemo(alarm: number|OnmsAlarm): Promise<void> {
return this.deleteMemo('journal', alarm);
}

/**
* Generate an alarm object from the given dictionary.
* @hidden
Expand Down Expand Up @@ -369,6 +415,23 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
});
}

/**
* Call a DELETE request in the format the alarm ack API expects.
* @hidden
*/
private async httpDelete(url: string, parameters = {} as IHash<string>): Promise<void> {
const opts = this.getOptions();
opts.headers['content-type'] = 'application/x-www-form-urlencoded';
opts.headers.accept = null;
opts.parameters = parameters;
return this.http.httpDelete(url, opts).then((result) => {
if (!result.isSuccess) {
throw result;
}
return;
});
}

/**
* Get the path to the alarms endpoint for the appropriate API version.
* @hidden
Expand All @@ -377,4 +440,37 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
return this.getApiVersion() === 2 ? 'api/v2/alarms' : 'rest/alarms';
}

/**
* Save a journal or sticky memo.
* @hidden
*/
private async saveMemo(type: string, alarm: number|OnmsAlarm, body: string, user?: string): Promise<void> {
if (this.getApiVersion() === 1) {
throw new OnmsError('Save/Delete memo is only available in OpenNMS ' +
'versions that support the ReSTv2 API.');
}

const alarmId = (typeof(alarm) === 'number' ? alarm : alarm.id);
const parameters = {} as IHash<string>;
parameters.body = body;
if (user !== undefined) {
parameters.user = user;
}
return this.put(this.pathToAlarmsEndpoint() + '/' + alarmId + '/' + type, parameters);
}

/**
* Delete a journal or sticky memo
* @hidden
*/
private async deleteMemo(type: string, alarm: number|OnmsAlarm): Promise<void> {
if (this.getApiVersion() === 1) {
throw new OnmsError('Save/Delete memo is only available in OpenNMS ' +
'versions that support the ReSTv2 API.');
}

const alarmId = (typeof(alarm) === 'number' ? alarm : alarm.id);
return this.httpDelete(this.pathToAlarmsEndpoint() + '/' + alarmId + '/' + type);
}

}
3 changes: 3 additions & 0 deletions src/rest/AbstractHTTP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export abstract class AbstractHTTP implements IOnmsHTTP {
/** Make an HTTP POST call. This must be overridden by the concrete implementation. */
public abstract post(url: string, options?: OnmsHTTPOptions): Promise<OnmsResult<any>>;

/** Make an HTTP DELETE call. This must be overridden by the concrete implementation. */
public abstract httpDelete(url: string, options?: OnmsHTTPOptions): Promise<OnmsResult<any>>;

/**
* A convenience method for implementers to use to turn JSON into a javascript object.
* Use this to process a JSON response before returning it in an [[OnmsResult]] object.
Expand Down
23 changes: 23 additions & 0 deletions src/rest/AxiosHTTP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,29 @@ export class AxiosHTTP extends AbstractHTTP {
}).catch(this.handleError);
}

/**
* Make an HTTP DELETE call using `axios.request({method:'delete'})`.
*/
public httpDelete(url: string, options?: OnmsHTTPOptions) {
const realUrl = this.getServer(options).resolveURL(url);
const opts = this.getConfig(options);

const urlObj = new URI(realUrl);
urlObj.search(opts.params);
log.debug('DELETE ' + urlObj.toString(), catAxios);

opts.method = 'delete';
opts.url = realUrl;

return this.getImpl(options).request(opts).then((response) => {
let type;
if (response.headers && response.headers['content-type']) {
type = response.headers['content-type'];
}
return OnmsResult.ok(response.data, undefined, response.status, type);
}).catch(this.handleError);
}

/**
* Clear the current [[AxiosInstance]] so it is recreated on next request with the
* new server configuration.
Expand Down
21 changes: 20 additions & 1 deletion src/rest/GrafanaHTTP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export class GrafanaHTTP extends AbstractHTTP {
/** Make an HTTP POST call using the Grafana `BackendSrv`. */
public post(url: string, options?: OnmsHTTPOptions) {
const realUrl = this.getServer(options).resolveURL(url);
log.debug('PUT ' + realUrl);
log.debug('POST ' + realUrl);
const query = this.getConfig(options);
query.method = 'POST';
query.url = realUrl;
Expand All @@ -93,6 +93,25 @@ export class GrafanaHTTP extends AbstractHTTP {
}).catch(this.handleError);
}

/** Make an HTTP DELETE call using the Grafana `BackendSrv`. */
public httpDelete(url: string, options?: OnmsHTTPOptions) {
const realUrl = this.getServer(options).resolveURL(url);
log.debug('DELETE ' + realUrl);
const query = this.getConfig(options);
query.method = 'DELETE';
query.url = realUrl;
return this.backendSrv.datasourceRequest(query).then((response) => {
let type = 'application/xml';
if (query && query.headers && query.headers.accept) {
type = query.headers.accept;
}
if (response.headers && response.headers['content-type']) {
type = response.headers['content-type'];
}
return OnmsResult.ok(response.data, undefined, response.status, type);
}).catch(this.handleError);
}

/**
* Internal method to turn [[OnmsHTTPOptions]] into a Grafana `BackendSrv` request object.
* @hidden
Expand Down
34 changes: 34 additions & 0 deletions test/dao/AlarmDAO.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ describe('AlarmDAO with v1 API', () => {
it('AlarmDAO.closeTicket(404725) should reject', () => {
return expect(dao.closeTicket(404725)).rejects.toBeDefined();
});

it('AlarmDAO.saveStickyMemo(404725, "test") should reject', () => {
return expect(dao.saveStickyMemo(404725, 'test')).rejects.toBeDefined();
});
it('AlarmDAO.saveJournalMemo(404725, "test") should reject', () => {
return expect(dao.saveJournalMemo(404725, 'test')).rejects.toBeDefined();
});
it('AlarmDAO.deleteStickyMemo(404725) should reject', () => {
return expect(dao.deleteStickyMemo(404725)).rejects.toBeDefined();
});
it('AlarmDAO.deleteJournalMemo(404725) should reject', () => {
return expect(dao.deleteJournalMemo(404725)).rejects.toBeDefined();
});
});

describe('AlarmDAO with v2 API', () => {
Expand Down Expand Up @@ -175,4 +188,25 @@ describe('AlarmDAO with v2 API', () => {
return true;
})).resolves.toBeTruthy();
});

it('AlarmDAO.saveStickyMemo(404725, "test") should return a 204', () => {
return expect(dao.saveStickyMemo(404725, 'test').then(() => {
return true;
})).resolves.toBeTruthy();
});
it('AlarmDAO.saveJournalMemo(404725, "test") should return a 204', () => {
return expect(dao.saveJournalMemo(404725, 'test').then(() => {
return true;
})).resolves.toBeTruthy();
});
it('AlarmDAO.deleteStickyMemo(404725) should return a 204', () => {
return expect(dao.deleteStickyMemo(404725).then(() => {
return true;
})).resolves.toBeTruthy();
});
it('AlarmDAO.deleteJournalMemo(404725) should return a 204', () => {
return expect(dao.deleteJournalMemo(404725).then(() => {
return true;
})).resolves.toBeTruthy();
});
});
7 changes: 6 additions & 1 deletion test/rest/MockHTTP19.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,9 @@ export class MockHTTP19 extends AbstractHTTP {

return Promise.reject(OnmsResult.error('Not yet implemented: POST ' + urlObj.toString()));
}
}

public httpDelete(url: string, options?: OnmsHTTPOptions): Promise<OnmsResult<any>> {
const urlObj = new URI(url);
return Promise.reject(OnmsResult.error('Not yet implemented: DELETE ' + urlObj.toString()));
}
}
36 changes: 36 additions & 0 deletions test/rest/MockHTTP21.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ export class MockHTTP21 extends AbstractHTTP {
result.type = 'text/plain';
return Promise.resolve(result);
}
case 'api/v2/alarms/404725/memo?body=test': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
result.code = 204;
return Promise.resolve(result);
}
case 'api/v2/alarms/404725/journal?body=test': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
result.code = 204;
return Promise.resolve(result);
}
}

return Promise.reject(OnmsResult.error('Not yet implemented: PUT ' + urlObj.toString()));
Expand Down Expand Up @@ -123,4 +135,28 @@ export class MockHTTP21 extends AbstractHTTP {

return Promise.reject(OnmsResult.error('Not yet implemented: POST ' + urlObj.toString()));
}

public httpDelete(url: string, options?: OnmsHTTPOptions): Promise<OnmsResult<any>> {
const urlObj = new URI(url);
if (options && options.parameters) {
urlObj.search(options.parameters);
}

switch (urlObj.toString()) {
case 'api/v2/alarms/404725/memo': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
result.code = 204;
return Promise.resolve(result);
}
case 'api/v2/alarms/404725/journal': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
result.code = 204;
return Promise.resolve(result);
}
}

return Promise.reject(OnmsResult.error('Not yet implemented: DELETE ' + urlObj.toString()));
}
}

0 comments on commit 875e268

Please sign in to comment.