Skip to content

Commit

Permalink
Merge pull request #34 from Buildings-IOT/sorting-and-sub-total
Browse files Browse the repository at this point in the history
Adding sub total and sorting capabilities
  • Loading branch information
slevertbiot committed Mar 10, 2022
2 parents 76dcaed + 7559a8f commit c544554
Show file tree
Hide file tree
Showing 15 changed files with 65 additions and 55 deletions.
5 changes: 5 additions & 0 deletions udmd/api/src/__tests__/device/DeviceDataSource.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@ describe('DeviceDataSource.getDevice()', () => {
const result: DevicesResponse = await deviceDS.getDevices(searchOptions);
await expect(result.totalCount).not.toBe(0);
});

test('returns a total filtered count', async () => {
const result: DevicesResponse = await deviceDS.getDevices(searchOptions);
await expect(result.totalFilteredCount).not.toBe(0);
});
});
2 changes: 1 addition & 1 deletion udmd/api/src/__tests__/device/MockDeviceDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ export default class MockDeviceDataSource extends GraphQLDataSource<object> {

async getDevices(searchOptions: SearchOptions): Promise<DevicesResponse> {
const devices: Device[] = createDevices(30);
return { devices, totalCount: 30 };
return { devices, totalCount: 30, totalFilteredCount: 10 };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ Object {
},
],
"totalCount": 30,
"totalFilteredCount": 10,
},
},
"errors": undefined,
Expand Down
24 changes: 19 additions & 5 deletions udmd/api/src/__tests__/device/dao/mongo/MongoDeviceDAO.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ import { MongoDeviceDAO } from '../../../../device/dao/mongodb/MongoDeviceDAO';
import { Device, SearchOptions, SORT_DIRECTION } from '../../../../device/model';
import { createDevices } from '../../data';

const mockDevices: Device[] = createDevices(100);

// mongo objects
let connection: MongoClient;
let db: Db;
let deviceCollection: Collection;

// object under test
let mongoDeviceDAO: MongoDeviceDAO;
let mockDevices: Device[];

// spies
let findSpy;
let sortSpy;
let limitSpy;
Expand All @@ -24,15 +30,12 @@ afterAll(async () => {
});

beforeEach(async () => {
// object under test
mongoDeviceDAO = new MongoDeviceDAO(db);

// data prep
mockDevices = createDevices(100);
deviceCollection = db.collection('device');
deviceCollection.insertMany(mockDevices);
await deviceCollection.insertMany(mockDevices);

// spies
findSpy = jest.spyOn(Collection.prototype, 'find');
sortSpy = jest.spyOn(FindCursor.prototype, 'sort');
limitSpy = jest.spyOn(FindCursor.prototype, 'limit');
Expand All @@ -41,6 +44,7 @@ beforeEach(async () => {

afterEach(async () => {
await db.collection('device').deleteMany({});

jest.resetAllMocks();
jest.restoreAllMocks();
});
Expand Down Expand Up @@ -150,3 +154,13 @@ describe('MongoDeviceDAO.getDeviceCount', () => {
expect(count).toBe(mockDevices.length);
});
});

describe('MongoDeviceDAO.getFilteredDeviceCount', () => {
test('returns the filtered device count in the mongo db', async () => {
const searchOptions: SearchOptions = { batchSize: 10 };

const filterdDeviceCount: number = await mongoDeviceDAO.getFilteredDeviceCount(searchOptions);

expect(filterdDeviceCount).toBe(mockDevices.length);
});
});
14 changes: 10 additions & 4 deletions udmd/api/src/__tests__/device/dao/mongo/MongoFilterBuilder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { getFilter } from '../../../../device/dao/mongodb/MongoFilterBuilder';

const emptyMongoFilter = {};

const filters = [
const containfilters = [
[getContainsFilter('make', 'value'), getExpectedRegex('make', 'value')],
[getContainsFilter('model', 'value'), getExpectedRegex('model', 'value')],
[getContainsFilter('operational', 'true'), emptyMongoFilter],
[getEqualsFilter('make', 'value'), emptyMongoFilter],
[getEqualsFilter('make', 'value'), getExpectedEqualsExpression('make', 'value')],
];

describe('MongoFilterBuilder.getFilter', () => {
test.each(filters)(
'returns a regex filter object when a ~ is the operator',
test.each(containfilters)(
'returns a regex filter object when ~ is the operator',
async (filter: Filter, expectedResult) => {
const jsonFilters: Filter[] = [filter];
expect(getFilter(jsonFilters)).toEqual(expectedResult);
Expand All @@ -28,6 +28,12 @@ function getEqualsFilter(field: string, value: string): Filter {
return { field, operator: '=', value };
}

function getExpectedEqualsExpression(field: string, value: string): any {
const equalsExpression = {};
equalsExpression[field] = value;
return equalsExpression;
}

function getExpectedRegex(field: string, value: string): any {
const regex = {};
regex[field] = { $regex: value, $options: 'i' };
Expand Down
21 changes: 0 additions & 21 deletions udmd/api/src/__tests__/device/dao/static/StaticDeviceDAO.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,6 @@ describe('StaticDeviceDAO.getDevices', () => {
searchOptions = createSearchOptions(batchSize, offset);
await expect(deviceDAO.getDevices(searchOptions)).resolves.toEqual(allDevices.slice(offset, offset + batchSize));
});

test('that providing an offset greater than the number of devices throws an error', async () => {
const batchSize = 20;
const offset = 200;

const searchOptions: SearchOptions = createSearchOptions(batchSize, offset);

await expect(deviceDAO.getDevices(searchOptions)).rejects.toThrowError(
'An invalid offset that is greater than the total number of devices was provided.'
);
});

test('that if a batch size of 0 is provided, an error is thrown', async () => {
const batchSize = 0;

const searchOptions: SearchOptions = createSearchOptions(batchSize);

await expect(deviceDAO.getDevices(searchOptions)).rejects.toThrowError(
'A batch size greater than zero must be provided'
);
});
});

describe('StaticDeviceDAO.getDeviceCount', () => {
Expand Down
1 change: 1 addition & 0 deletions udmd/api/src/__tests__/device/resolve.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const QUERY_DEVICES = gql`
query {
devices(searchOptions: { batchSize: 10, offset: 10, sortOptions: { direction: DESC, field: "name" }, filter: "" }) {
totalCount
totalFilteredCount
devices {
id
name
Expand Down
3 changes: 2 additions & 1 deletion udmd/api/src/device/DeviceDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class DeviceDataSource extends GraphQLDataSource {
async getDevices(searchOptions: SearchOptions): Promise<DevicesResponse> {
const totalCount = await this.deviceDAO.getDeviceCount();
const devices: Device[] = await this.deviceDAO.getDevices(searchOptions);
return { devices, totalCount };
const totalFilteredCount: number = await this.deviceDAO.getFilteredDeviceCount(searchOptions);
return { devices, totalCount, totalFilteredCount };
}
}
1 change: 1 addition & 0 deletions udmd/api/src/device/dao/DeviceDAO.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Device, SearchOptions } from '../model';

export interface DeviceDAO {
getFilteredDeviceCount(searchOptions: SearchOptions): Promise<number>;
getDevices(searchOptions: SearchOptions): Promise<Device[]>;
getDeviceCount(): Promise<number>;
}
13 changes: 0 additions & 13 deletions udmd/api/src/device/dao/firestore/FirestoreDeviceDAO.ts

This file was deleted.

8 changes: 6 additions & 2 deletions udmd/api/src/device/dao/mongodb/MongoDeviceDAO.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SearchOptions, Device } from '../../model';
import { DeviceDAO } from '../DeviceDAO';
import { Db } from 'mongodb';
import { Db, Filter, FindCursor } from 'mongodb';
import { fromString } from '../../../device/FilterParser';
import { getFilter } from './MongoFilterBuilder';
import { getSort } from './MongoSortBuilder';
Expand All @@ -20,11 +20,15 @@ export class MongoDeviceDAO implements DeviceDAO {
.toArray();
}

async getFilteredDeviceCount(searchOptions: SearchOptions): Promise<number> {
return await this.db.collection<Device>('device').countDocuments(this.getFilter(searchOptions));
}

async getDeviceCount(): Promise<number> {
return this.db.collection<Device>('device').countDocuments();
}

private getFilter(searchOptions: SearchOptions): any {
private getFilter(searchOptions: SearchOptions): Filter<Device> {
return searchOptions.filter ? getFilter(fromString(searchOptions.filter)) : {};
}

Expand Down
10 changes: 8 additions & 2 deletions udmd/api/src/device/dao/mongodb/MongoFilterBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ export function getFilter(jsonFilters: Filter[]): any {
const mongoFilters: any = {};

jsonFilters.forEach((filter) => {
if (filter.field === 'operational') {
// do nothing for filtering on the operational boolean yet
// TODO: resolve in a future PR
}
// '~' is our symbol for 'contains'
// the operational field is a boolean so do not try a partial match on this field
if (filter.operator === '~' && filter.field !== 'operational') {
else if (filter.operator === '~') {
// this means we need to do a case insensitive regex match for the value of the field
mongoFilters[filter.field] = { $regex: filter.value, $options: 'i' };
} else if (filter.operator === '=') {
// this means we need to do an exact match for the value of the field
mongoFilters[filter.field] = filter.value;
}
});

Expand Down
14 changes: 8 additions & 6 deletions udmd/api/src/device/dao/static/StaticDeviceDAO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,17 @@ export class StaticDeviceDAO implements DeviceDAO {
return this.devices.length;
}

public async getDevices(searchOptions: SearchOptions): Promise<Device[]> {
if (searchOptions.batchSize == 0) {
throw new Error('A batch size greater than zero must be provided.');
public async getFilteredDeviceCount(searchOptions: SearchOptions): Promise<number> {
let filteredDevices = this.devices;
if (searchOptions.filter) {
const filters: Filter[] = fromString(searchOptions.filter);
filteredDevices = filterDevices(filters, filteredDevices);
}

if (searchOptions.offset > this.devices.length) {
throw new Error('An invalid offset that is greater than the total number of devices was provided.');
}
return filterDevices.length;
}

public async getDevices(searchOptions: SearchOptions): Promise<Device[]> {
let filteredDevices = this.devices;
if (searchOptions.filter) {
const filters: Filter[] = fromString(searchOptions.filter);
Expand Down
1 change: 1 addition & 0 deletions udmd/api/src/device/model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface DevicesResponse {
devices: Device[];
totalCount: number;
totalFilteredCount: number;
}

export interface SearchOptions {
Expand Down
2 changes: 2 additions & 0 deletions udmd/api/src/device/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ type DevicesResponse {
devices: [Device!]!
# the total number of devices in the system
totalCount: Int!
# the total number of devices after the filter in the search options has been applied
totalFilteredCount: Int!
}

type Device {
Expand Down

0 comments on commit c544554

Please sign in to comment.