Skip to content
This repository has been archived by the owner on Aug 1, 2024. It is now read-only.

api route for fetching unique featureIds by attrbuteId #86

Merged
merged 2 commits into from
Dec 1, 2021
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
31 changes: 30 additions & 1 deletion api/src/models/attributeModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ const getDocumentsCount = async (filter) => {

return count;
};

/**
* Gets all unique dates for given attributeId
* @param {string} attributeId
*/
const getAvailableDates = async (attributeId) => {
const { connection } = mongoose;
const { db } = connection;
Expand All @@ -229,6 +232,31 @@ const getAvailableDates = async (attributeId) => {
}));
};

/**
* Gets all unique featureIds for given attributeId
* @param {string} attributeId
*/
const getUniqueFeatureIds = async (attributeId) => {
const { connection } = mongoose;
const { db } = connection;

const items = await db
.collection(ATTRIBUTES_COLLECTION_NAME)
.aggregate([
{ $match: { attributeId } },
{
$group: {
_id: '$featureId',
},
},
{
$sort: { _id: 1 },
},
])
.toArray();
return items.map((item) => item._id);
};

/**
* Get count of all attributes by given values
* @param {array} attributeIds - ids of attributes
Expand All @@ -244,5 +272,6 @@ module.exports = {
getFilteredAttributes,
getLatestAttributes,
getAvailableDates,
getUniqueFeatureIds,
countAttributes,
};
15 changes: 15 additions & 0 deletions api/src/openapi/apiSchema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ paths:
"200":
description: Successful response

/attributes/{attributeId}/uniqueFeatures:
get:
summary: Get featureIds of values for this attributeId
parameters:
- name: attributeId
in: path
required: true
description: Parameter for data filtering. The identifier of the attribute for which we want to get unique featureId
schema:
type: string
example: "Population on admin2 level"
responses:
"200":
description: Successful response

/api/pointAttributes:
get:
summary: Getting data with single point coordinates
Expand Down
11 changes: 11 additions & 0 deletions api/src/routes/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
countAttributes,
getFilteredAttributes,
getAvailableDates,
getUniqueFeatureIds,
} = require('../models/attributeModel');
const logger = require('../config/winston');

Expand Down Expand Up @@ -54,4 +55,14 @@ router.get(
}),
);

router.get(
'/:attributeId/uniqueFeatures',
swaggerValidation.validate,
forwardError(async (req, res) => {
let items = [];
items = await getUniqueFeatureIds(req.params.attributeId);
res.send(items);
}),
);

module.exports = router;
97 changes: 55 additions & 42 deletions api/src/tests/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,52 @@ jest.mock('../config/config.js', () => {
};
});

describe('GET /api/attributes', () => {
it('should return only attributes with correct start of attributeId', async () => {
await Attribute.insertMany([...categoryStringAttr]);
const res = await request(app).get(`/api/attributes?attributeIdCategory=categorised`).expect(200);
expect(Object.keys(res.body)).toHaveLength(1);
expect(res.body[categoryStringAttr[0].attributeId]).toHaveLength(1);
});

it('should return 500 when attributeIdCategories and attributeId are missing', async () => {
await DataDateAttribute.insertMany([...bedOccupancyAttrToDb]);
await request(app).get(`/api/attributes?latestValues=true`).expect(500);
});

it('should return correct data structure', async () => {
await DataDateAttribute.insertMany([...bedOccupancyAttrToDb]);
const res = await request(app).get(`/api/attributes?attributeId=Bed occupancy rate&latestValues=true`).expect(200);
expect(Object.keys(res.body)).toHaveLength(1);
expect(Object.keys(res.body)).toEqual([bedOccupancyAttrToDb[0].attributeId]);
expect(Object.keys(res.body[bedOccupancyAttrToDb[0].attributeId][0]).includes('date')).toEqual(true);
expect(Object.keys(res.body[bedOccupancyAttrToDb[0].attributeId][0]).includes('dataDate')).toEqual(true);
expect(Object.keys(res.body[bedOccupancyAttrToDb[0].attributeId][0]).includes('featureId')).toEqual(true);
expect(Object.keys(res.body[bedOccupancyAttrToDb[0].attributeId][0]).includes('attributeId')).toEqual(true);
expect(Object.keys(res.body[bedOccupancyAttrToDb[0].attributeId][0]).includes('value')).toEqual(true);
});

it('should return one document for the last reported date for each geographic unit e.g. featureId', async () => {
await DataDateAttribute.insertMany([bedOccupancyAttrToDb[0], bedOccupancyAttrToDb[1]]);
const res = await request(app).get(`/api/attributes?attributeId=Bed occupancy rate&latestValues=true`).expect(200);
expect(res.body[bedOccupancyAttrToDb[0].attributeId][0].date).toEqual(bedOccupancyAttrToDb[1].date);
expect(res.body[bedOccupancyAttrToDb[0].attributeId]).toHaveLength(1);
});

it('should return one document for the last reported date for each geographic unit e.g. featureId if reported period e.g. dataDate is missing', async () => {
await NumberAttribute.insertMany([...bedOccupancyWithoutDataDateAttrToDb]);
const res = await request(app).get(`/api/attributes?attributeId=Bed occupancy rate&latestValues=true`).expect(200);
expect(res.body[bedOccupancyAttrToDb[0].attributeId][0].date).toEqual(bedOccupancyAttrToDb[1].date);
expect(res.body[bedOccupancyAttrToDb[0].attributeId]).toHaveLength(1);
});

it('should return documents with the right attributeId from the query', async () => {
await DataDateAttribute.insertMany([...bedOccupancyAttrToDb, ...hospitalStayAttrToDb]);
const res = await request(app).get(`/api/attributes?attributeId=Bed occupancy rate&latestValues=true`).expect(200);
expect(Object.keys(res.body)).toEqual([bedOccupancyAttrToDb[0].attributeId]);
});
});

describe('GET api/attributes/:attributeId/availableDates', () => {
it('should return empty array when attributeId is an unknown string', async () => {
const res = await request(app).get(`/api/attributes/unknownAttributeId/availableDates`).expect(200);
Expand Down Expand Up @@ -68,48 +114,15 @@ describe('GET api/attributes/:attributeId/availableDates', () => {
});
});

describe('GET /api/attributes', () => {
it('should return only attributes with correct start of attributeId', async () => {
await Attribute.insertMany([...categoryStringAttr]);
const res = await request(app).get(`/api/attributes?attributeIdCategory=categorised`).expect(200);
expect(Object.keys(res.body)).toHaveLength(1);
expect(res.body[categoryStringAttr[0].attributeId]).toHaveLength(1);
});

it('should return 500 when attributeIdCategories and attributeId are missing', async () => {
await DataDateAttribute.insertMany([...bedOccupancyAttrToDb]);
await request(app).get(`/api/attributes?latestValues=true`).expect(500);
});

it('should return correct data structure', async () => {
await DataDateAttribute.insertMany([...bedOccupancyAttrToDb]);
const res = await request(app).get(`/api/attributes?attributeId=Bed occupancy rate&latestValues=true`).expect(200);
expect(Object.keys(res.body)).toHaveLength(1);
expect(Object.keys(res.body)).toEqual([bedOccupancyAttrToDb[0].attributeId]);
expect(Object.keys(res.body[bedOccupancyAttrToDb[0].attributeId][0]).includes('date')).toEqual(true);
expect(Object.keys(res.body[bedOccupancyAttrToDb[0].attributeId][0]).includes('dataDate')).toEqual(true);
expect(Object.keys(res.body[bedOccupancyAttrToDb[0].attributeId][0]).includes('featureId')).toEqual(true);
expect(Object.keys(res.body[bedOccupancyAttrToDb[0].attributeId][0]).includes('attributeId')).toEqual(true);
expect(Object.keys(res.body[bedOccupancyAttrToDb[0].attributeId][0]).includes('value')).toEqual(true);
});

it('should return one document for the last reported date for each geographic unit e.g. featureId', async () => {
await DataDateAttribute.insertMany([bedOccupancyAttrToDb[0], bedOccupancyAttrToDb[1]]);
const res = await request(app).get(`/api/attributes?attributeId=Bed occupancy rate&latestValues=true`).expect(200);
expect(res.body[bedOccupancyAttrToDb[0].attributeId][0].date).toEqual(bedOccupancyAttrToDb[1].date);
expect(res.body[bedOccupancyAttrToDb[0].attributeId]).toHaveLength(1);
});

it('should return one document for the last reported date for each geographic unit e.g. featureId if reported period e.g. dataDate is missing', async () => {
await NumberAttribute.insertMany([...bedOccupancyWithoutDataDateAttrToDb]);
const res = await request(app).get(`/api/attributes?attributeId=Bed occupancy rate&latestValues=true`).expect(200);
expect(res.body[bedOccupancyAttrToDb[0].attributeId][0].date).toEqual(bedOccupancyAttrToDb[1].date);
expect(res.body[bedOccupancyAttrToDb[0].attributeId]).toHaveLength(1);
describe('GET /api/attributes/:attributeId/uniqueFeatures', () => {
it('should get empty array for wrong attributeId', async () => {
await Attribute.insertMany(bedOccupancyAttrToDb);
const res = await request(app).get(`/api/attributes/wrongAttributeId/uniqueFeatures`).expect(200);
expect(res.body).toEqual([]);
});

it('should return documents with the right attributeId from the query', async () => {
await DataDateAttribute.insertMany([...bedOccupancyAttrToDb, ...hospitalStayAttrToDb]);
const res = await request(app).get(`/api/attributes?attributeId=Bed occupancy rate&latestValues=true`).expect(200);
expect(Object.keys(res.body)).toEqual([bedOccupancyAttrToDb[0].attributeId]);
it('should get all values for attributeId in database', async () => {
await Attribute.insertMany(bedOccupancyAttrToDb);
const res = await request(app).get(`/api/attributes/Bed occupancy rate/uniqueFeatures`).expect(200);
expect(res.body).toEqual(['District1', 'District2']);
});
});
18 changes: 10 additions & 8 deletions api/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2951,10 +2951,12 @@ hide-powered-by@1.1.0:
resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.1.0.tgz#be3ea9cab4bdb16f8744be873755ca663383fa7a"
integrity sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==

hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
hosted-git-info@^2.1.4, hosted-git-info@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d"
integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==
dependencies:
lru-cache "^6.0.0"

hpkp@2.0.0:
version "2.0.0"
Expand Down Expand Up @@ -4601,10 +4603,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==

normalize-url@^4.1.0:
version "4.5.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
normalize-url@^4.1.0, normalize-url@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==

npm-run-path@^2.0.0:
version "2.0.2"
Expand Down