Skip to content

Add new filters for projects list (without ID) #256

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 20, 2019
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
110 changes: 106 additions & 4 deletions postman.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "97085cd7-b298-4f1c-9629-24af14ff5f13",
"_postman_id": "4fc2b7cf-404a-4fd7-b6d2-4828a3994859",
"name": "tc-project-service",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
Expand Down Expand Up @@ -1452,7 +1452,7 @@
"response": []
},
{
"name": "List projects with filters applied",
"name": "List projects with filters - type (exact)",
"request": {
"method": "GET",
"header": [
Expand All @@ -1466,7 +1466,7 @@
"raw": ""
},
"url": {
"raw": "{{api-url}}/v4/projects?filter=type%3Dgeneric",
"raw": "{{api-url}}/v4/projects?filter=type%3Dapp",
"host": [
"{{api-url}}"
],
Expand All @@ -1477,7 +1477,109 @@
"query": [
{
"key": "filter",
"value": "type%3Dgeneric"
"value": "type%3Dapp"
}
]
},
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
},
"response": []
},
{
"name": "List projects with filters - id (exact)",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
}
],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "{{api-url}}/v4/projects?filter=id%3D1",
"host": [
"{{api-url}}"
],
"path": [
"v4",
"projects"
],
"query": [
{
"key": "filter",
"value": "id%3D1"
}
]
},
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
},
"response": []
},
{
"name": "List projects with filters - name, code, customer, manager",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
}
],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "{{api-url}}/v4/projects?filter=id%3D1*%26name%3Dtes*%26code=test*%26customer%3DDiya*%26manager=first*",
"host": [
"{{api-url}}"
],
"path": [
"v4",
"projects"
],
"query": [
{
"key": "filter",
"value": "id%3D1*%26name%3Dtes*%26code=test*%26customer%3DDiya*%26manager=first*"
}
]
},
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
},
"response": []
},
{
"name": "List projects with filters - code",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
}
],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "{{api-url}}/v4/projects?filter=code%3Dtest*",
"host": [
"{{api-url}}"
],
"path": [
"v4",
"projects"
],
"query": [
{
"key": "filter",
"value": "code%3Dtest*"
}
]
},
Expand Down
125 changes: 119 additions & 6 deletions src/routes/projects/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,87 @@ const buildEsFullTextQuery = (keyword, matchType, singleFieldName) => {
};
};

/**
* Build ES query search request body based on value, keyword, matchType and fieldName
*
* @param {String} value the value to build request body for
* @param {String} keyword the keyword to query
* @param {String} matchType wildcard match or exact match
* @param {Array} fieldName the fieldName
* @return {Object} search request body that can be passed to .search api call
*/
const buildEsQueryWithFilter = (value, keyword, matchType, fieldName) => {
let should = [];
if (value !== 'details' && value !== 'customer' && value !== 'manager') {
should = _.concat(should, {
query_string: {
query: keyword,
analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD),
fields: fieldName,
},
});
}

if (value === 'details') {
should = _.concat(should, {
nested: {
path: 'details',
query: {
nested: {
path: 'details.utm',
query: {
query_string: {
query: keyword,
analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD),
fields: fieldName,
},
},
},
},
},
});
}

if (value === 'customer' || value === 'manager') {
should = _.concat(should, {
nested: {
path: 'members',
query: {
bool: {
must: [
{ match: { 'members.role': value } },
{
query_string: {
query: keyword,
analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD),
fields: fieldName,
},
},
],
},
},
},
});
}

return should;
};

/**
* Prepare search request body based on wildcard query
*
* @param {String} value the value to build request body for
* @param {String} keyword the keyword to query
* @param {Array} fieldName the fieldName
* @return {Object} search request body that can be passed to .search api call
*/
const setFilter = (value, keyword, fieldName) => {
if (keyword.indexOf('*') > -1) {
return buildEsQueryWithFilter(value, keyword, MATCH_TYPE_WILDCARD, fieldName);
}
return buildEsQueryWithFilter(value, keyword, MATCH_TYPE_EXACT_PHRASE, fieldName);
};

/**
* Parse the ES search criteria and prepare search request body
*
Expand Down Expand Up @@ -152,13 +233,40 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
}
// prepare the elasticsearch filter criteria
const boolQuery = [];
let mustQuery = [];
let fullTextQuery;
if (_.has(criteria, 'filters.id.$in')) {
boolQuery.push({
ids: {
values: criteria.filters.id.$in,
},
});
} else if (_.has(criteria, 'filters.id')) {
boolQuery.push({
term: {
id: criteria.filters.id,
},
});
}

if (_.has(criteria, 'filters.name')) {
mustQuery = _.concat(mustQuery, setFilter('name', criteria.filters.name, ['name']));
}

if (_.has(criteria, 'filters.code')) {
mustQuery = _.concat(mustQuery, setFilter('details', criteria.filters.code, ['details.utm.code']));
}

if (_.has(criteria, 'filters.customer')) {
mustQuery = _.concat(mustQuery, setFilter('customer',
criteria.filters.customer,
['members.firstName', 'members.lastName']));
}

if (_.has(criteria, 'filters.manager')) {
mustQuery = _.concat(mustQuery, setFilter('manager',
criteria.filters.manager,
['members.firstName', 'members.lastName']));
}

if (_.has(criteria, 'filters.status.$in')) {
Expand Down Expand Up @@ -222,7 +330,7 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {

if (!keyword) {
// Not a specific field search nor an exact phrase search, do a wildcard match
keyword = escapeEsKeyword(criteria.filters.keyword);
keyword = criteria.filters.keyword;
matchType = MATCH_TYPE_WILDCARD;
}

Expand All @@ -234,17 +342,22 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
filter: boolQuery,
};
}

if (mustQuery.length > 0) {
body.query.bool = _.merge(body.query.bool, {
must: mustQuery,
});
}
if (fullTextQuery) {
body.query = _.merge(body.query, fullTextQuery);
if (body.query.bool) {
body.query.bool.minimum_should_match = 1;
}
}

if (fullTextQuery || boolQuery.length > 0) {
if (fullTextQuery || boolQuery.length > 0 || mustQuery.length > 0) {
searchCriteria.body = body;
}

return searchCriteria;
};

Expand All @@ -267,8 +380,7 @@ const retrieveProjects = (req, criteria, sort, ffields) => {
fields.projects.push('id');
}

const searchCriteria = parseElasticSearchCriteria(criteria, fields, order);

const searchCriteria = parseElasticSearchCriteria(criteria, fields, order) || {};
return new Promise((accept, reject) => {
const es = util.getElasticSearchClient();
es.search(searchCriteria).then((docs) => {
Expand Down Expand Up @@ -300,7 +412,8 @@ module.exports = [
'name', 'name asc', 'name desc',
'type', 'type asc', 'type desc',
];
if (!util.isValidFilter(filters, ['id', 'status', 'type', 'memberOnly', 'keyword']) ||
if (!util.isValidFilter(filters,
['id', 'status', 'memberOnly', 'keyword', 'type', 'name', 'code', 'customer', 'manager']) ||
(sort && _.indexOf(sortableProps, sort) < 0)) {
return util.handleError('Invalid filters or sort', null, req, next);
}
Expand Down
Loading