From 4ec452d8d4bab3833afc45a1fe225ab9c2441418 Mon Sep 17 00:00:00 2001 From: Daniela Barborova Date: Wed, 1 Dec 2021 14:56:35 +0100 Subject: [PATCH 1/2] api route for fetching unique featureIds by attrbuteId --- api/src/models/attributeModel.js | 31 +++++++++- api/src/openapi/apiSchema.yml | 15 +++++ api/src/routes/attributes.js | 9 +++ api/src/tests/attributes.js | 99 ++++++++++++++++++-------------- 4 files changed, 110 insertions(+), 44 deletions(-) diff --git a/api/src/models/attributeModel.js b/api/src/models/attributeModel.js index aea44fd..1782fe2 100644 --- a/api/src/models/attributeModel.js +++ b/api/src/models/attributeModel.js @@ -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; @@ -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 @@ -244,5 +272,6 @@ module.exports = { getFilteredAttributes, getLatestAttributes, getAvailableDates, + getUniqueFeatureIds, countAttributes, }; diff --git a/api/src/openapi/apiSchema.yml b/api/src/openapi/apiSchema.yml index e7fae01..816de24 100644 --- a/api/src/openapi/apiSchema.yml +++ b/api/src/openapi/apiSchema.yml @@ -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 diff --git a/api/src/routes/attributes.js b/api/src/routes/attributes.js index 3dda723..30bcb17 100644 --- a/api/src/routes/attributes.js +++ b/api/src/routes/attributes.js @@ -6,6 +6,7 @@ const { countAttributes, getFilteredAttributes, getAvailableDates, + getUniqueFeatureIds } = require('../models/attributeModel'); const logger = require('../config/winston'); @@ -54,4 +55,12 @@ 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; diff --git a/api/src/tests/attributes.js b/api/src/tests/attributes.js index c06cc71..a5ad07e 100644 --- a/api/src/tests/attributes.js +++ b/api/src/tests/attributes.js @@ -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); @@ -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']); }); -}); +}) \ No newline at end of file From e47083281046437c3b622f48f9207b5dc5c320d9 Mon Sep 17 00:00:00 2001 From: Daniela Barborova Date: Wed, 1 Dec 2021 15:03:48 +0100 Subject: [PATCH 2/2] yarn lint fix --- api/src/models/attributeModel.js | 4 ++-- api/src/routes/attributes.js | 12 +++++++----- api/src/tests/attributes.js | 2 +- api/yarn.lock | 18 ++++++++++-------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/api/src/models/attributeModel.js b/api/src/models/attributeModel.js index 1782fe2..e663584 100644 --- a/api/src/models/attributeModel.js +++ b/api/src/models/attributeModel.js @@ -254,8 +254,8 @@ const getUniqueFeatureIds = async (attributeId) => { }, ]) .toArray(); - return items.map(item => item._id); -} + return items.map((item) => item._id); +}; /** * Get count of all attributes by given values diff --git a/api/src/routes/attributes.js b/api/src/routes/attributes.js index 30bcb17..1b92026 100644 --- a/api/src/routes/attributes.js +++ b/api/src/routes/attributes.js @@ -6,7 +6,7 @@ const { countAttributes, getFilteredAttributes, getAvailableDates, - getUniqueFeatureIds + getUniqueFeatureIds, } = require('../models/attributeModel'); const logger = require('../config/winston'); @@ -55,12 +55,14 @@ router.get( }), ); -router.get('/:attributeId/uniqueFeatures', +router.get( + '/:attributeId/uniqueFeatures', swaggerValidation.validate, forwardError(async (req, res) => { let items = []; - items = await getUniqueFeatureIds(req.params.attributeId) - res.send(items) - })); + items = await getUniqueFeatureIds(req.params.attributeId); + res.send(items); + }), +); module.exports = router; diff --git a/api/src/tests/attributes.js b/api/src/tests/attributes.js index a5ad07e..e315287 100644 --- a/api/src/tests/attributes.js +++ b/api/src/tests/attributes.js @@ -125,4 +125,4 @@ describe('GET /api/attributes/:attributeId/uniqueFeatures', () => { const res = await request(app).get(`/api/attributes/Bed occupancy rate/uniqueFeatures`).expect(200); expect(res.body).toEqual(['District1', 'District2']); }); -}) \ No newline at end of file +}); diff --git a/api/yarn.lock b/api/yarn.lock index ca85303..b9c68fe 100644 --- a/api/yarn.lock +++ b/api/yarn.lock @@ -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" @@ -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"