From d39942b35e6db957966d1a4c181e68391eb869b4 Mon Sep 17 00:00:00 2001 From: Pallavi Singh Date: Fri, 21 Oct 2016 12:05:19 -0700 Subject: [PATCH] filter GET /v1/subjects/ by tags --- api/v1/constants.js | 1 + api/v1/helpers/nouns/subjects.js | 1 + api/v1/helpers/verbs/findUtils.js | 52 +++++++++++++++++++++---------- api/v1/swagger.yaml | 11 +++++++ config.js | 4 +++ tests/api/v1/subjects/get.js | 38 ++++++++++++++++++++++ 6 files changed, 90 insertions(+), 17 deletions(-) diff --git a/api/v1/constants.js b/api/v1/constants.js index 1d7f68806e..82bc0d19cd 100644 --- a/api/v1/constants.js +++ b/api/v1/constants.js @@ -38,6 +38,7 @@ module.exports = { SEQ_DEFAULT_SCOPE: 'defaultScope', SEQ_DESC: 'DESC', SEQ_LIKE: '$iLike', + SEQ_CONTAINS: '$contains', SEQ_OR: '$or', SEQ_WILDCARD: '%', SLASH: '/', diff --git a/api/v1/helpers/nouns/subjects.js b/api/v1/helpers/nouns/subjects.js index 0647f40271..2f9755914b 100644 --- a/api/v1/helpers/nouns/subjects.js +++ b/api/v1/helpers/nouns/subjects.js @@ -304,4 +304,5 @@ module.exports = { fieldsWithJsonArrayType, fieldsWithArrayType, loggingEnabled, + tagFilterName: 'tags', }; // exports diff --git a/api/v1/helpers/verbs/findUtils.js b/api/v1/helpers/verbs/findUtils.js index 5f6bd2837a..e2e9c7819c 100644 --- a/api/v1/helpers/verbs/findUtils.js +++ b/api/v1/helpers/verbs/findUtils.js @@ -14,6 +14,7 @@ const u = require('./utils'); const constants = require('../../constants'); const defaults = require('../../../../config').api.defaults; +const filterSubjByTags = require('../../../../config').filterSubjByTags; /** * Escapes all percent literals so they're not treated as wildcards. @@ -52,10 +53,17 @@ function toSequelizeWildcards(val) { * case-insensitive string matching * * @param {String} val - The value to transform into a Sequelize where clause + * @param {Object} props - The helpers/nouns module for the given DB model. * @returns {Object} a Sequelize where clause using "$ilike" for * case-insensitive string matching */ -function toWhereClause(val) { +function toWhereClause(val, props) { + if (filterSubjByTags && Array.isArray(val) && props.tagFilterName) { + const containsClause = {}; + containsClause[constants.SEQ_CONTAINS] = val; + return containsClause; + } + // TODO handle non-string data types like dates and numbers if (typeof val !== 'string') { return val; @@ -72,9 +80,10 @@ function toWhereClause(val) { * a Sequelize "where" object. * * @param {Object} filter + * @param {Object} props - The helpers/nouns module for the given DB model. * @returns {Object} a Sequelize "where" object */ -function toSequelizeWhere(filter) { +function toSequelizeWhere(filter, props) { const where = {}; const keys = Object.keys(filter); for (let i = 0; i < keys.length; i++) { @@ -85,23 +94,32 @@ function toSequelizeWhere(filter) { } const values = []; - for (let j = 0; j < filter[key].length; j++) { - const v = filter[key][j]; - if (typeof v === 'boolean') { - values.push(v); - } else if (typeof v === 'string') { - const arr = v.split(constants.COMMA); - for (let k = 0; k < arr.length; k++) { - values.push(toWhereClause(arr[k])); + // if tag filter is enables and key is "tags", then create a contains + // clause and add it to where clause, + // like, { where : { '$contains': ['tag1', 'tag2'] } } + if (filterSubjByTags && props.tagFilterName && key === props.tagFilterName) { + const tagArr = filter[key]; + values.push(toWhereClause(tagArr, props)); + where[key] = values[0]; + } else { + for (let j = 0; j < filter[key].length; j++) { + const v = filter[key][j]; + if (typeof v === 'boolean') { + values.push(v); + } else if (typeof v === 'string') { + const arr = v.split(constants.COMMA); + for (let k = 0; k < arr.length; k++) { + values.push(toWhereClause(arr[k])); + } } } - } - if (values.length === 1) { - where[key] = values[0]; - } else if (values.length > 1) { - where[key] = {}; - where[key][constants.SEQ_OR] = values; + if (values.length === 1) { + where[key] = values[0]; + } else if (values.length > 1) { + where[key] = {}; + where[key][constants.SEQ_OR] = values; + } } } } @@ -172,7 +190,7 @@ function options(params, props) { } if (filter) { - opts.where = toSequelizeWhere(filter); + opts.where = toSequelizeWhere(filter, props); } return opts; diff --git a/api/v1/swagger.yaml b/api/v1/swagger.yaml index 831c31f154..bc49bd2676 100644 --- a/api/v1/swagger.yaml +++ b/api/v1/swagger.yaml @@ -3094,6 +3094,17 @@ paths: Filter by parentAbsolutePath; asterisk (*) wildcards ok. required: false type: string + - + name: tags + in: query + items: + type: string + maxLength: 60 + pattern: ^[0-9A-Za-z_][0-9A-Za-z_\\-]{1,59}$ + description: >- + Filter by tags. + required: false + type: array responses: 200: description: >- diff --git a/config.js b/config.js index ed362d15f9..85f5af25a5 100644 --- a/config.js +++ b/config.js @@ -53,6 +53,9 @@ const optimizeUpsert = pe.OPTIMIZE_UPSERT === 'true' || // env variable to enable caching for /GET /v1/perspectives/{key} const enableCachePerspective = pe.ENABLE_CACHE_PERSPECTIVE || false; +const filterSubjByTags = pe.FILTER_SUBJ_BY_TAGS === 'true' || + pe.FILTER_SUBJ_BY_TAGS === true || false; + module.exports = { api: { @@ -247,4 +250,5 @@ module.exports = { optimizeUpsert, enableCachePerspective, enableClockDyno, + filterSubjByTags, }; diff --git a/tests/api/v1/subjects/get.js b/tests/api/v1/subjects/get.js index c29bc7d267..b714900011 100644 --- a/tests/api/v1/subjects/get.js +++ b/tests/api/v1/subjects/get.js @@ -19,6 +19,7 @@ const u = require('./utils'); const Subject = tu.db.Subject; const path = '/v1/subjects'; const expect = require('chai').expect; +const filterSubjByTags = require('../../../../config').filterSubjByTags; describe(`api: GET ${path}`, () => { let token; @@ -30,10 +31,12 @@ describe(`api: GET ${path}`, () => { const us = { name: `${tu.namePrefix}UnitedStates`, description: 'country', + tags: ['US'], }; const vt = { name: `${tu.namePrefix}Vermont`, description: 'state', + tags: ['US', 'NE'], }; before((done) => { @@ -185,6 +188,41 @@ describe(`api: GET ${path}`, () => { }); }); + if (filterSubjByTags) { + it('GET with tag filter :: one tag', (done) => { + api.get(`${path}/?tags=US`) + .set('Authorization', token) + .expect(constants.httpStatus.OK) + .end((err, res) => { + if (err) { + return done(err); + } + + expect(res.body.length).to.equal(2); + expect(res.body[0].tags).to.eql(['US']); + expect(res.body[1].tags).to.eql(['US', 'NE']); + + done(); + }); + }); + + it('GET with tag filter :: multiple tags', (done) => { + api.get(`${path}/?tags=NE,US`) + .set('Authorization', token) + .expect(constants.httpStatus.OK) + .end((err, res) => { + if (err) { + return done(err); + } + + expect(res.body.length).to.equal(1); + expect(res.body[0].tags).to.eql(['US', 'NE']); + + done(); + }); + }); + } + it('pagination tests'); it('childCount, descendentCount'); it('by id');