Skip to content

Commit

Permalink
RN-645: Add internal data tables for fetching entities and entity rel…
Browse files Browse the repository at this point in the history
  • Loading branch information
rohan-bes authored Oct 21, 2022
1 parent fba27ca commit 1cc055f
Show file tree
Hide file tree
Showing 27 changed files with 792 additions and 200 deletions.
7 changes: 6 additions & 1 deletion packages/data-table-server/examples.http
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,9 @@ Authorization: {{authorization}}
"hierarchy": "psss",
"startDate": "2020-01-01",
"endDate" : "2020-12-31"
}
}

### Get parameters for analytics data-table
GET http://{{host}}/{{version}}/dataTable/analytics/parameters HTTP/1.1
content-type: {{contentType}}
Authorization: {{authorization}}
3 changes: 3 additions & 0 deletions packages/data-table-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,8 @@
"dotenv": "^8.2.0",
"express": "^4.16.2",
"winston": "^3.2.1"
},
"devDependencies": {
"mockdate": "^3.0.5"
}
}
8 changes: 7 additions & 1 deletion packages/data-table-server/src/@types/express/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@

import { AccessPolicy } from '@tupaia/access-policy';
import { TupaiaApiClient } from '@tupaia/api-client';
import { DataTableService } from '../../dataTableService';
import { DataTableType } from '../../models';
import { DataTableServerModelRegistry } from '../../types';

declare global {
namespace Express {
export interface Request {
accessPolicy: AccessPolicy;
models: DataTableServerModelRegistry;
ctx: { services: TupaiaApiClient };
ctx: {
services: TupaiaApiClient;
dataTable: DataTableType;
dataTableService: DataTableService;
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
* Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd
*/

import MockDate from 'mockdate';
import { AccessPolicy } from '@tupaia/access-policy';
import { TupaiaApiClient } from '@tupaia/api-client';
import { DataTableServiceBuilder } from '../../../dataTableService';

const CURRENT_DATE_STUB = '2020-12-31';

const TEST_ANALYTICS = [
{ period: '2020-01-01', organisationUnit: 'TO', dataElement: 'PSSS_AFR_Cases', value: 7 },
{ period: '2020-01-08', organisationUnit: 'TO', dataElement: 'PSSS_AFR_Cases', value: 12 },
Expand All @@ -21,7 +24,7 @@ const fetchFakeAnalytics = (
{
organisationUnitCodes,
startDate = '2020-01-01',
endDate = '2020-12-31',
endDate = CURRENT_DATE_STUB,
}: { organisationUnitCodes: string[]; startDate?: string; endDate?: string },
) => {
return {
Expand All @@ -47,8 +50,20 @@ jest.mock('@tupaia/data-broker', () => ({

const accessPolicy = new AccessPolicy({ DL: ['Public'] });
const apiClient = {} as TupaiaApiClient;
const analyticsDataTableService = new DataTableServiceBuilder()
.setServiceType('analytics')
.setContext({ apiClient, accessPolicy })
.build();

describe('AnalyticsDataTableService', () => {
beforeEach(() => {
MockDate.set(CURRENT_DATE_STUB);
});

afterEach(() => {
MockDate.reset();
});

describe('parameter validation', () => {
const testData: [string, unknown, string][] = [
[
Expand All @@ -59,14 +74,6 @@ describe('AnalyticsDataTableService', () => {
},
'organisationUnitCodes is a required field',
],
[
'missing hierarchy',
{
organisationUnitCodes: ['TO'],
dataElementCodes: ['PSSS_AFR_Cases'],
},
'hierarchy is a required field',
],
[
'missing dataElementCodes',
{
Expand All @@ -83,7 +90,7 @@ describe('AnalyticsDataTableService', () => {
dataElementCodes: ['PSSS_AFR_Cases'],
startDate: 'cat',
},
'startDate should be in ISO 8601 format',
'startDate must be a `date` type',
],
[
'endDate wrong format',
Expand All @@ -93,7 +100,7 @@ describe('AnalyticsDataTableService', () => {
dataElementCodes: ['PSSS_AFR_Cases'],
endDate: 'dog',
},
'endDate should be in ISO 8601 format',
'endDate must be a `date` type',
],
[
'aggregations wrong format',
Expand All @@ -108,62 +115,66 @@ describe('AnalyticsDataTableService', () => {
];

it.each(testData)('%s', (_, parameters: unknown, expectedError: string) => {
const analyticsDataTableService = new DataTableServiceBuilder()
.setServiceType('analytics')
.setContext({ apiClient, accessPolicy })
.build();

expect(() => analyticsDataTableService.fetchData(parameters)).toThrow(expectedError);
});
});

it('can fetch data from Aggregator.fetchAnalytics()', async () => {
const analyticsDataTableService = new DataTableServiceBuilder()
.setServiceType('analytics')
.setContext({ apiClient, accessPolicy })
.build();
it('getParameters', () => {
const parameters = analyticsDataTableService.getParameters();
expect(parameters).toEqual([
{ config: { defaultValue: 'explore', type: 'string' }, name: 'hierarchy' },
{
config: { innerType: { required: true, type: 'string' }, required: true, type: 'array' },
name: 'organisationUnitCodes',
},
{
config: { innerType: { required: true, type: 'string' }, required: true, type: 'array' },
name: 'dataElementCodes',
},
{ config: { defaultValue: new Date('2017-01-01'), type: 'date' }, name: 'startDate' },
{ config: { defaultValue: new Date(), type: 'date' }, name: 'endDate' },
]);
});

const dataElementCodes = ['PSSS_AFR_Cases'];
const organisationUnitCodes = ['TO'];
describe('fetchData', () => {
it('can fetch data from Aggregator.fetchAnalytics()', async () => {
const dataElementCodes = ['PSSS_AFR_Cases'];
const organisationUnitCodes = ['TO'];

const analytics = await analyticsDataTableService.fetchData({
hierarchy: 'psss',
organisationUnitCodes,
dataElementCodes,
});
const analytics = await analyticsDataTableService.fetchData({
hierarchy: 'psss',
organisationUnitCodes,
dataElementCodes,
});

const { results: expectedAnalytics } = fetchFakeAnalytics(dataElementCodes, {
organisationUnitCodes,
});

expect(analytics).toEqual(expectedAnalytics);
});
const { results: expectedAnalytics } = fetchFakeAnalytics(dataElementCodes, {
organisationUnitCodes,
});

it('passes all parameters to Aggregator.fetchAnalytics()', async () => {
const analyticsDataTableService = new DataTableServiceBuilder()
.setServiceType('analytics')
.setContext({ apiClient, accessPolicy })
.build();

const dataElementCodes = ['PSSS_AFR_Cases', 'PSSS_ILI_Cases'];
const organisationUnitCodes = ['PG'];
const startDate = '2020-01-05';
const endDate = '2020-01-10';

const analytics = await analyticsDataTableService.fetchData({
hierarchy: 'psss',
organisationUnitCodes,
dataElementCodes,
startDate,
endDate,
expect(analytics).toEqual(expectedAnalytics);
});

const { results: expectedAnalytics } = fetchFakeAnalytics(dataElementCodes, {
organisationUnitCodes,
startDate,
endDate,
it('passes all parameters to Aggregator.fetchAnalytics()', async () => {
const dataElementCodes = ['PSSS_AFR_Cases', 'PSSS_ILI_Cases'];
const organisationUnitCodes = ['PG'];
const startDate = '2020-01-05';
const endDate = '2020-01-10';

const analytics = await analyticsDataTableService.fetchData({
hierarchy: 'psss',
organisationUnitCodes,
dataElementCodes,
startDate,
endDate,
});

const { results: expectedAnalytics } = fetchFakeAnalytics(dataElementCodes, {
organisationUnitCodes,
startDate,
endDate,
});

expect(analytics).toEqual(expectedAnalytics);
});

expect(analytics).toEqual(expectedAnalytics);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Tupaia
* Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd
*/

import { TupaiaApiClient } from '@tupaia/api-client';
import { DataTableServiceBuilder } from '../../../dataTableService';

const entitiesDataTableService = new DataTableServiceBuilder()
.setServiceType('entities')
.setContext({ apiClient: {} as TupaiaApiClient })
.build();

describe('EntitiesDataTableService', () => {
describe('parameter validation', () => {
const testData: [string, unknown, string][] = [
['missing entityCodes', {}, 'entityCodes is a required field'],
];

it.each(testData)('%s', (_, parameters: unknown, expectedError: string) => {
expect(() => entitiesDataTableService.fetchData(parameters)).toThrow(expectedError);
});
});

it('getParameters', () => {
const parameters = entitiesDataTableService.getParameters();
expect(parameters).toEqual([
{ config: { defaultValue: 'explore', type: 'string' }, name: 'hierarchy' },
{
config: { innerType: { required: true, type: 'string' }, required: true, type: 'array' },
name: 'entityCodes',
},
{
config: { type: 'object' },
name: 'filter',
},
{
config: {
defaultValue: ['code'],
type: 'array',
innerType: { type: 'string', required: true },
},
name: 'fields',
},
{ config: { defaultValue: false, type: 'boolean' }, name: 'includeDescendants' },
]);
});

describe('fetchData', () => {
// TODO: Implement these tests when RN-685 is done
// it('can fetch entities', async () => {});
// it('can fetch entities and descendants', async () => {});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Tupaia
* Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd
*/

import { TupaiaApiClient } from '@tupaia/api-client';
import { DataTableServiceBuilder } from '../../../dataTableService';

const entityRelationsDataTableService = new DataTableServiceBuilder()
.setServiceType('entity_relations')
.setContext({ apiClient: {} as TupaiaApiClient })
.build();

describe('EntityRelationsDataTableService', () => {
describe('parameter validation', () => {
const testData: [string, unknown, string][] = [
[
'missing entityCodes',
{ ancestorType: 'district', descendantType: 'sub_district' },
'entityCodes is a required field',
],
[
'missing ancestorType',
{ entityCodes: ['TO'], descendantType: 'sub_district' },
'ancestorType is a required field',
],
[
'missing descendantType',
{ entityCodes: ['TO'], ancestorType: 'district' },
'descendantType is a required field',
],
];

it.each(testData)('%s', (_, parameters: unknown, expectedError: string) => {
expect(() => entityRelationsDataTableService.fetchData(parameters)).toThrow(expectedError);
});
});

it('getParameters', () => {
const parameters = entityRelationsDataTableService.getParameters();
expect(parameters).toEqual([
{ config: { defaultValue: 'explore', type: 'string' }, name: 'hierarchy' },
{
config: { innerType: { required: true, type: 'string' }, required: true, type: 'array' },
name: 'entityCodes',
},
{ config: { type: 'string', required: true }, name: 'ancestorType' },
{ config: { type: 'string', required: true }, name: 'descendantType' },
]);
});

describe('fetchData', () => {
// TODO: Implement these tests when RN-685 is done
// it('can fetch entities', async () => {});
// it('can fetch entities and descendants', async () => {});
});
});
Loading

0 comments on commit 1cc055f

Please sign in to comment.