Skip to content

Commit d9f7642

Browse files
umut0mpfeil
andauthored
Feat download by grouptag (#660)
* download measurements by grouptag * allow multiple grouptags and clean code * tests and docs for download by grouptag Co-authored-by: Matthias Pfeil <[email protected]>
1 parent 31c5ab4 commit d9f7642

File tree

6 files changed

+142
-2
lines changed

6 files changed

+142
-2
lines changed

docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ services:
1515
# - ./dumps/measurements:/exports/measurements
1616
- ./.scripts/mongodb/osem_admin.sh:/docker-entrypoint-initdb.d/osem_admin.sh
1717
# - ./.scripts/mongodb/osem_seed_boxes.sh:/docker-entrypoint-initdb.d/osem_seed_boxes.sh
18-
# - ./.scripts/mongodb/osem_seed_measurements.sh:/docker-entrypoint-initdb.d/osem_seed_measurements.sh
18+
# - ./.scripts/mongodb/osem_seed_measurements.sh:/docker-entrypoint-initdb.d/osem_seed_measurements.sh
19+

packages/api/lib/controllers/measurementsController.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,46 @@ const getDataMulti = async function getDataMulti (req, res, next) {
233233
}
234234
};
235235

236+
237+
/**
238+
* @api {get,post} /boxes/data?grouptag=:grouptag Get latest measurements for a grouptag as JSON
239+
* @apiDescription Download data of a given grouptag from multiple senseBoxes as JSON
240+
* @apiGroup Measurements
241+
* @apiName getDataByGroupTag
242+
* @apiParam {String} grouptag The grouptag to search by.
243+
*/
244+
const getDataByGroupTag = async function getDataByGroupTag (req, res, next) {
245+
const { grouptag, format } = req._userParams;
246+
const queryTags = grouptag.split(',');
247+
// build query
248+
const queryParams = {};
249+
if (grouptag) {
250+
queryParams['grouptag'] = { '$all': queryTags };
251+
}
252+
253+
try {
254+
let stream = await Box.findMeasurementsOfBoxesByTagStream({
255+
query: queryParams
256+
});
257+
stream = stream
258+
.on('error', function (err) {
259+
return handleError(err, next);
260+
});
261+
switch (format) {
262+
case 'json':
263+
res.header('Content-Type', 'application/json');
264+
stream = stream
265+
.pipe(jsonstringify({ open: '[', close: ']' }));
266+
break;
267+
}
268+
269+
stream
270+
.pipe(res);
271+
} catch (err) {
272+
handleError(err, next);
273+
}
274+
};
275+
236276
/**
237277
* @api {post} /boxes/:senseBoxId/:sensorId Post new measurement
238278
* @apiDescription Posts a new measurement to a specific sensor of a box.
@@ -433,6 +473,13 @@ module.exports = {
433473
validateFromToTimeParams,
434474
getDataMulti
435475
],
476+
getDataByGroupTag: [
477+
retrieveParameters([
478+
{ name: 'grouptag', required: true },
479+
{ name: 'format', defaultValue: 'json', allowedValues: ['json'] }
480+
]),
481+
getDataByGroupTag
482+
],
436483
getLatestMeasurements: [
437484
retrieveParameters([
438485
{ predef: 'boxId', required: true },

packages/api/lib/routes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ const routes = {
7878
{ path: `${statisticsPath}/descriptive`, method: 'get', handler: statisticsController.descriptiveStatisticsHandler, reference: 'api-Statistics-descriptive' },
7979
{ path: `${boxesPath}`, method: 'get', handler: boxesController.getBoxes, reference: 'api-Boxes-getBoxes' },
8080
{ path: `${boxesPath}/data`, method: 'get', handler: measurementsController.getDataMulti, reference: 'api-Measurements-getDataMulti' },
81+
{ path: `${boxesPath}/data/bytag`, method: 'get', handler: measurementsController.getDataByGroupTag, reference: 'api-Measurements-getDataByGroupTag' },
8182
{ path: `${boxesPath}/:boxId`, method: 'get', handler: boxesController.getBox, reference: 'api-Boxes-getBox' },
8283
{ path: `${boxesPath}/:boxId/sensors`, method: 'get', handler: measurementsController.getLatestMeasurements, reference: 'api-Measurements-getLatestMeasurements' },
8384
{ path: `${boxesPath}/:boxId/sensors/:sensorId`, method: 'get', handler: measurementsController.getLatestMeasurements, reference: 'api-Measurements-getLatestMeasurementOfSensor' },

packages/models/src/box/box.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,46 @@ boxSchema.statics.findMeasurementsOfBoxesStream = function findMeasurementsOfBox
767767
});
768768
};
769769

770+
boxSchema.statics.findMeasurementsOfBoxesByTagStream = function findMeasurementsOfBoxesByTagStream (opts) {
771+
const { query } = opts;
772+
773+
return this.find(query, BOX_PROPS_FOR_POPULATION)
774+
.lean()
775+
.then(function (boxData) {
776+
if (boxData.length === 0) {
777+
throw new ModelError('No senseBoxes found', { type: 'NotFoundError' });
778+
}
779+
780+
const sensors = Object.create(null);
781+
// store all matching sensors under sensors[sensorId]
782+
for (let i = 0, len = boxData.length; i < len; i++) {
783+
for (let j = 0, sensorslen = boxData[i].sensors.length; j < sensorslen; j++) {
784+
const sensor = boxData[i].sensors[j];
785+
786+
sensor.boxId = boxData[i]._id.toString();
787+
sensor.sensorId = sensor._id.toString();
788+
789+
sensors[boxData[i].sensors[j]['_id']] = sensor;
790+
}
791+
}
792+
793+
// // construct a stream transformer applied to queried measurements
794+
// // that augments each measure with queried columns (location, ...)
795+
// // and applies transformations to timestamps
796+
const transformer = measurementTransformer(['sensorId', 'boxId'], sensors, undefined);
797+
798+
const measureQuery = {
799+
'sensor_id': { '$in': Object.keys(sensors) },
800+
// 'createdAt': { '$gt': from, '$lt': to }
801+
};
802+
803+
return Measurement.find(measureQuery, { 'createdAt': 1, 'value': 1, 'location': 1, '_id': 0, 'sensor_id': 1 })
804+
.cursor({ lean: true, order: 1 })
805+
.map(transformer);
806+
});
807+
};
808+
809+
770810
// try to add sensors defined in addons to the box. If the sensors already exist,
771811
// nothing is done.
772812
boxSchema.methods.addAddon = function addAddon (addon) {

tests/tests/005-create-boxes-test.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,41 @@ describe('openSenseMap API Routes: /boxes', function () {
595595
return chakram.wait();
596596
});
597597
});
598+
it('should allow to update the box via PUT with array as grouptags', function () {
599+
const update_payload = { name: 'neuername', exposure: 'outdoor', grouptag: ['newgroup'], description: 'total neue beschreibung', location: { lat: 54.2, lng: 21.1 }, weblink: 'http://www.google.de', image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=' };
600+
601+
return chakram.put(`${BASE_URL}/boxes/${boxIds[2]}`, update_payload, { headers: { 'Authorization': `Bearer ${jwt2}` } })
602+
.then(function (response) {
603+
expect(response).to.have.status(200);
604+
expect(response).to.comprise.of.json('data.name', update_payload.name);
605+
expect(response).to.comprise.of.json('data.exposure', update_payload.exposure);
606+
expect(response.body.data.grouptag).to.have.same.members(update_payload.grouptag);
607+
expect(response).to.comprise.of.json('data.description', update_payload.description);
608+
expect(response).to.comprise.of.json('data.currentLocation', {
609+
type: 'Point',
610+
coordinates: [update_payload.location.lng, update_payload.location.lat]
611+
});
612+
613+
// loc field with old schema (backwards compat):
614+
expect(response).to.comprise.of.json('data.loc', [{
615+
type: 'Feature',
616+
geometry: {
617+
type: 'Point',
618+
coordinates: [
619+
update_payload.location.lng,
620+
update_payload.location.lat
621+
]
622+
}
623+
}]);
624+
625+
// image should contain timestamp. request duration should be less than 1s.
626+
expect(response).to.comprise.of.json('data.image', function (image) {
627+
return expect(moment().diff(moment(parseInt(image.split('_')[1].slice(0, -4), 36) * 1000))).to.be.below(1000);
628+
});
629+
630+
return chakram.wait();
631+
});
632+
});
598633

599634
it('should deny to update a box of other users', function () {
600635
let otherJwt, otherBoxId;
@@ -677,7 +712,7 @@ describe('openSenseMap API Routes: /boxes', function () {
677712
expect(response).to.have.status(200);
678713
expect(response).to.have.header('content-type', 'application/json; charset=utf-8');
679714
expect(Array.isArray(response.body)).to.be.true;
680-
expect(response.body.length).to.be.equal(1);
715+
expect(response.body.length).to.be.equal(2);
681716

682717
return chakram.wait();
683718
});

tests/tests/007-download-data-test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,4 +584,20 @@ describe('downloading data', function () {
584584

585585
});
586586

587+
describe('/boxes/data/bytag?grouptag=newgroup', function () {
588+
589+
const expectedMeasurementsCount = 40;
590+
591+
it('should allow download data by Grouptag /boxes/data/bytag=newgroup as json', function () {
592+
return chakram.get(`${BASE_URL}/boxes/data/bytag?grouptag=newgroup`)
593+
.then(function (response) {
594+
expect(response).to.have.status(200);
595+
expect(response.body.length).to.be.equal(expectedMeasurementsCount);
596+
expect(response).to.have.header('content-type', 'application/json');
597+
598+
return chakram.wait();
599+
});
600+
});
601+
});
602+
587603
});

0 commit comments

Comments
 (0)