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

Adding sub total and sorting capabilities #34

Merged
merged 3 commits into from
Mar 10, 2022
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
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();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u ended up needing these?

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

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