Skip to content

Commit

Permalink
feat(api): add orderBy support to filter queries (HELM-174)
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin Reed committed Aug 29, 2019
1 parent 138f524 commit 6e7edfa
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 6 deletions.
13 changes: 12 additions & 1 deletion src/api/Filter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {NestedRestriction} from './NestedRestriction';
import {OrderBy} from './OrderBy';

/**
* A query filter for DAOs.
Expand All @@ -12,12 +13,22 @@ export class Filter extends NestedRestriction {
newFilter.limit = filter.limit;
const nested = NestedRestriction.fromJson(filter);
newFilter.clauses = nested.clauses;
if (filter.orderBy && filter.orderBy.length > 0) {
newFilter.orderBy = filter.orderBy.map((o: any) => OrderBy.fromJson(o));
}
}
return newFilter;
}

/** how many results to get back by default */
public limit = 1000;

/** TODO: add (multiple) orderBy/order support */
/** how to sort results */
public orderBy: OrderBy[] = [];

/** Add the given order criteria to the filter. */
public withOrderBy(order: OrderBy) {
this.orderBy.push(order);
return this;
}
}
51 changes: 51 additions & 0 deletions src/api/OrderBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// tslint:disable:max-classes-per-file

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

/**
* Represents a sort order.
* @category Filtering API
*/
export class Order extends OnmsEnum<string> {
/** Given a label ('ASC', 'DESC'), return the corresponding order. */
public static forLabel(label: string) {
return forLabel(Orders, label);
}

/** Whether this order matches the given order string. */
public matches(label: string) {
return (label.toLowerCase() === this.label.toLowerCase());
}
}

const Orders = {
ASC: new Order('ASC', 'ASC'),
DESC: new Order('DESC', 'DESC'),
};
const frozen = Object.freeze(Orders);
export {frozen as Orders};

/**
* Column ordering.
* @category Filtering API
*/
export class OrderBy {
/** given an OrderBy JSON structure, return an OrderBy object */
public static fromJson(orderBy: any): OrderBy|undefined {
if (orderBy && orderBy.attribute) {
return new OrderBy(orderBy.attribute, Order.forLabel(orderBy.order.label));
}
return undefined;
}

/** the attribute to order by */
public readonly attribute: string;

/** the order to sort */
public readonly order: Order;

public constructor(attribute: string, order?: Order) {
this.attribute = attribute;
this.order = order || Orders.ASC;
}
}
11 changes: 11 additions & 0 deletions src/dao/V1FilterProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ export class V1FilterProcessor implements IFilterProcessor {
}
}

if (filter.orderBy && filter.orderBy.length > 0) {
const orders = filter.orderBy.map((o) => o.order.label).filter((val, index, self) => self.indexOf(val) === index);
if (orders.length > 1) {
throw new OnmsError('The V1 ReST API only supports one order (ASC or DESC), they cannot be mixed.');
}
addParameter(ret, 'order', orders[0] || 'DESC');
for (const orderBy of filter.orderBy) {
addParameter(ret, 'orderBy', orderBy.attribute);
}
}

return ret;
}
}
11 changes: 11 additions & 0 deletions src/dao/V2FilterProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ export class V2FilterProcessor implements IFilterProcessor {
addParameter(ret, '_s', search);
}

if (filter.orderBy && filter.orderBy.length > 0) {
const orders = filter.orderBy.map((o) => o.order.label).filter((val, index, self) => self.indexOf(val) === index);
if (orders.length > 1) {
throw new OnmsError('The V2 ReST API only supports one order (ASC or DESC), they cannot be mixed.');
}
addParameter(ret, 'order', orders[0] || 'DESC');
for (const orderBy of filter.orderBy) {
addParameter(ret, 'orderBy', orderBy.attribute);
}
}

return ret;
}

Expand Down
24 changes: 22 additions & 2 deletions test/dao/V1FilterProcessor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
declare const await, describe, beforeEach, it, expect, jest;
declare const describe, it, expect;

import {OnmsError} from '../../src/api/OnmsError';
import {Comparators} from '../../src/api/Comparator';
import {Filter} from '../../src/api/Filter';
import {OnmsError} from '../../src/api/OnmsError';
import {OrderBy, Orders} from '../../src/api/OrderBy';
import {Restriction} from '../../src/api/Restriction';

import {Severities} from '../../src/model/OnmsSeverity';
Expand Down Expand Up @@ -81,4 +82,23 @@ describe('V1FilterProcessor', () => {
lastEventTime: '1976-04-14T00:00:00.000+0000',
});
});
it('alarm filter: orderBy=lastEventTime&orderBy=id&order=DESC', () => {
const filter = new Filter();
filter
.withOrderBy(new OrderBy('lastEventTime', Orders.DESC))
.withOrderBy(new OrderBy('id', Orders.DESC));
const proc = new V1FilterProcessor();
expect(proc.getParameters(filter)).toMatchObject({
order: 'DESC',
orderBy: ['lastEventTime', 'id'],
});
});
it('alarm filter: orderBy=lastEventTime&order=DESC&orderBy=id&order=ASC', () => {
const filter = new Filter();
filter
.withOrderBy(new OrderBy('lastEventTime', Orders.DESC))
.withOrderBy(new OrderBy('id', Orders.ASC));
const proc = new V1FilterProcessor();
expect(() => { proc.getParameters(filter); }).toThrow();
});
});
26 changes: 23 additions & 3 deletions test/dao/V2FilterProcessor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
declare const await, describe, beforeEach, it, expect, jest;
declare const describe, it, expect;

import {Comparators} from '../../src/api/Comparator';
import {Filter} from '../../src/api/Filter';
import {NestedRestriction} from '../../src/api/NestedRestriction';
import {OrderBy, Orders} from '../../src/api/OrderBy';
import {Restriction} from '../../src/api/Restriction';
import {SearchProperty} from '../../src/api/SearchProperty';
import {SearchPropertyTypes} from '../../src/api/SearchPropertyType';
import {NestedRestriction} from '../../src/api/NestedRestriction';

import {Severities} from '../../src/model/OnmsSeverity';

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

describe('V2FilterProcessor', () => {

// tslint:disable-next-line:completed-docs
function toSearch(filter: Filter, processor?: V2FilterProcessor) {
if (!processor) {
processor = new V2FilterProcessor();
Expand Down Expand Up @@ -110,4 +111,23 @@ describe('V2FilterProcessor', () => {
+ ';alarmAckTime!=1970-01-01T00%3A00%3A00.000%2B0000'
+ ';id==\u0000;id!=\u0000');
});
it('alarm filter: orderBy=lastEventTime&orderBy=id&order=DESC', () => {
const filter = new Filter();
filter
.withOrderBy(new OrderBy('lastEventTime', Orders.DESC))
.withOrderBy(new OrderBy('id', Orders.DESC));
const proc = new V2FilterProcessor();
expect(proc.getParameters(filter)).toMatchObject({
order: 'DESC',
orderBy: ['lastEventTime', 'id'],
});
});
it('alarm filter: orderBy=lastEventTime&order=DESC&orderBy=id&order=ASC', () => {
const filter = new Filter();
filter
.withOrderBy(new OrderBy('lastEventTime', Orders.DESC))
.withOrderBy(new OrderBy('id', Orders.ASC));
const proc = new V2FilterProcessor();
expect(() => { proc.getParameters(filter); }).toThrow();
});
});

0 comments on commit 6e7edfa

Please sign in to comment.