Skip to content

Commit 80ab3f2

Browse files
authored
Merge pull request #256 from topcoder-platform/feature/new-project-filters-2
Add new filters for projects list (without ID)
2 parents 7ac6135 + 281af4a commit 80ab3f2

File tree

5 files changed

+412
-13
lines changed

5 files changed

+412
-13
lines changed

postman.json

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"info": {
3-
"_postman_id": "97085cd7-b298-4f1c-9629-24af14ff5f13",
3+
"_postman_id": "4fc2b7cf-404a-4fd7-b6d2-4828a3994859",
44
"name": "tc-project-service",
55
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
66
},
@@ -1452,7 +1452,7 @@
14521452
"response": []
14531453
},
14541454
{
1455-
"name": "List projects with filters applied",
1455+
"name": "List projects with filters - type (exact)",
14561456
"request": {
14571457
"method": "GET",
14581458
"header": [
@@ -1466,7 +1466,7 @@
14661466
"raw": ""
14671467
},
14681468
"url": {
1469-
"raw": "{{api-url}}/v4/projects?filter=type%3Dgeneric",
1469+
"raw": "{{api-url}}/v4/projects?filter=type%3Dapp",
14701470
"host": [
14711471
"{{api-url}}"
14721472
],
@@ -1477,7 +1477,109 @@
14771477
"query": [
14781478
{
14791479
"key": "filter",
1480-
"value": "type%3Dgeneric"
1480+
"value": "type%3Dapp"
1481+
}
1482+
]
1483+
},
1484+
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
1485+
},
1486+
"response": []
1487+
},
1488+
{
1489+
"name": "List projects with filters - id (exact)",
1490+
"request": {
1491+
"method": "GET",
1492+
"header": [
1493+
{
1494+
"key": "Authorization",
1495+
"value": "Bearer {{jwt-token}}"
1496+
}
1497+
],
1498+
"body": {
1499+
"mode": "raw",
1500+
"raw": ""
1501+
},
1502+
"url": {
1503+
"raw": "{{api-url}}/v4/projects?filter=id%3D1",
1504+
"host": [
1505+
"{{api-url}}"
1506+
],
1507+
"path": [
1508+
"v4",
1509+
"projects"
1510+
],
1511+
"query": [
1512+
{
1513+
"key": "filter",
1514+
"value": "id%3D1"
1515+
}
1516+
]
1517+
},
1518+
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
1519+
},
1520+
"response": []
1521+
},
1522+
{
1523+
"name": "List projects with filters - name, code, customer, manager",
1524+
"request": {
1525+
"method": "GET",
1526+
"header": [
1527+
{
1528+
"key": "Authorization",
1529+
"value": "Bearer {{jwt-token}}"
1530+
}
1531+
],
1532+
"body": {
1533+
"mode": "raw",
1534+
"raw": ""
1535+
},
1536+
"url": {
1537+
"raw": "{{api-url}}/v4/projects?filter=id%3D1*%26name%3Dtes*%26code=test*%26customer%3DDiya*%26manager=first*",
1538+
"host": [
1539+
"{{api-url}}"
1540+
],
1541+
"path": [
1542+
"v4",
1543+
"projects"
1544+
],
1545+
"query": [
1546+
{
1547+
"key": "filter",
1548+
"value": "id%3D1*%26name%3Dtes*%26code=test*%26customer%3DDiya*%26manager=first*"
1549+
}
1550+
]
1551+
},
1552+
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
1553+
},
1554+
"response": []
1555+
},
1556+
{
1557+
"name": "List projects with filters - code",
1558+
"request": {
1559+
"method": "GET",
1560+
"header": [
1561+
{
1562+
"key": "Authorization",
1563+
"value": "Bearer {{jwt-token}}"
1564+
}
1565+
],
1566+
"body": {
1567+
"mode": "raw",
1568+
"raw": ""
1569+
},
1570+
"url": {
1571+
"raw": "{{api-url}}/v4/projects?filter=code%3Dtest*",
1572+
"host": [
1573+
"{{api-url}}"
1574+
],
1575+
"path": [
1576+
"v4",
1577+
"projects"
1578+
],
1579+
"query": [
1580+
{
1581+
"key": "filter",
1582+
"value": "code%3Dtest*"
14811583
}
14821584
]
14831585
},

src/routes/projects/list.js

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,87 @@ const buildEsFullTextQuery = (keyword, matchType, singleFieldName) => {
102102
};
103103
};
104104

105+
/**
106+
* Build ES query search request body based on value, keyword, matchType and fieldName
107+
*
108+
* @param {String} value the value to build request body for
109+
* @param {String} keyword the keyword to query
110+
* @param {String} matchType wildcard match or exact match
111+
* @param {Array} fieldName the fieldName
112+
* @return {Object} search request body that can be passed to .search api call
113+
*/
114+
const buildEsQueryWithFilter = (value, keyword, matchType, fieldName) => {
115+
let should = [];
116+
if (value !== 'details' && value !== 'customer' && value !== 'manager') {
117+
should = _.concat(should, {
118+
query_string: {
119+
query: keyword,
120+
analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD),
121+
fields: fieldName,
122+
},
123+
});
124+
}
125+
126+
if (value === 'details') {
127+
should = _.concat(should, {
128+
nested: {
129+
path: 'details',
130+
query: {
131+
nested: {
132+
path: 'details.utm',
133+
query: {
134+
query_string: {
135+
query: keyword,
136+
analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD),
137+
fields: fieldName,
138+
},
139+
},
140+
},
141+
},
142+
},
143+
});
144+
}
145+
146+
if (value === 'customer' || value === 'manager') {
147+
should = _.concat(should, {
148+
nested: {
149+
path: 'members',
150+
query: {
151+
bool: {
152+
must: [
153+
{ match: { 'members.role': value } },
154+
{
155+
query_string: {
156+
query: keyword,
157+
analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD),
158+
fields: fieldName,
159+
},
160+
},
161+
],
162+
},
163+
},
164+
},
165+
});
166+
}
167+
168+
return should;
169+
};
170+
171+
/**
172+
* Prepare search request body based on wildcard query
173+
*
174+
* @param {String} value the value to build request body for
175+
* @param {String} keyword the keyword to query
176+
* @param {Array} fieldName the fieldName
177+
* @return {Object} search request body that can be passed to .search api call
178+
*/
179+
const setFilter = (value, keyword, fieldName) => {
180+
if (keyword.indexOf('*') > -1) {
181+
return buildEsQueryWithFilter(value, keyword, MATCH_TYPE_WILDCARD, fieldName);
182+
}
183+
return buildEsQueryWithFilter(value, keyword, MATCH_TYPE_EXACT_PHRASE, fieldName);
184+
};
185+
105186
/**
106187
* Parse the ES search criteria and prepare search request body
107188
*
@@ -152,13 +233,40 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
152233
}
153234
// prepare the elasticsearch filter criteria
154235
const boolQuery = [];
236+
let mustQuery = [];
155237
let fullTextQuery;
156238
if (_.has(criteria, 'filters.id.$in')) {
157239
boolQuery.push({
158240
ids: {
159241
values: criteria.filters.id.$in,
160242
},
161243
});
244+
} else if (_.has(criteria, 'filters.id')) {
245+
boolQuery.push({
246+
term: {
247+
id: criteria.filters.id,
248+
},
249+
});
250+
}
251+
252+
if (_.has(criteria, 'filters.name')) {
253+
mustQuery = _.concat(mustQuery, setFilter('name', criteria.filters.name, ['name']));
254+
}
255+
256+
if (_.has(criteria, 'filters.code')) {
257+
mustQuery = _.concat(mustQuery, setFilter('details', criteria.filters.code, ['details.utm.code']));
258+
}
259+
260+
if (_.has(criteria, 'filters.customer')) {
261+
mustQuery = _.concat(mustQuery, setFilter('customer',
262+
criteria.filters.customer,
263+
['members.firstName', 'members.lastName']));
264+
}
265+
266+
if (_.has(criteria, 'filters.manager')) {
267+
mustQuery = _.concat(mustQuery, setFilter('manager',
268+
criteria.filters.manager,
269+
['members.firstName', 'members.lastName']));
162270
}
163271

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

223331
if (!keyword) {
224332
// Not a specific field search nor an exact phrase search, do a wildcard match
225-
keyword = escapeEsKeyword(criteria.filters.keyword);
333+
keyword = criteria.filters.keyword;
226334
matchType = MATCH_TYPE_WILDCARD;
227335
}
228336

@@ -234,17 +342,22 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
234342
filter: boolQuery,
235343
};
236344
}
345+
346+
if (mustQuery.length > 0) {
347+
body.query.bool = _.merge(body.query.bool, {
348+
must: mustQuery,
349+
});
350+
}
237351
if (fullTextQuery) {
238352
body.query = _.merge(body.query, fullTextQuery);
239353
if (body.query.bool) {
240354
body.query.bool.minimum_should_match = 1;
241355
}
242356
}
243357

244-
if (fullTextQuery || boolQuery.length > 0) {
358+
if (fullTextQuery || boolQuery.length > 0 || mustQuery.length > 0) {
245359
searchCriteria.body = body;
246360
}
247-
248361
return searchCriteria;
249362
};
250363

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

270-
const searchCriteria = parseElasticSearchCriteria(criteria, fields, order);
271-
383+
const searchCriteria = parseElasticSearchCriteria(criteria, fields, order) || {};
272384
return new Promise((accept, reject) => {
273385
const es = util.getElasticSearchClient();
274386
es.search(searchCriteria).then((docs) => {
@@ -300,7 +412,8 @@ module.exports = [
300412
'name', 'name asc', 'name desc',
301413
'type', 'type asc', 'type desc',
302414
];
303-
if (!util.isValidFilter(filters, ['id', 'status', 'type', 'memberOnly', 'keyword']) ||
415+
if (!util.isValidFilter(filters,
416+
['id', 'status', 'memberOnly', 'keyword', 'type', 'name', 'code', 'customer', 'manager']) ||
304417
(sort && _.indexOf(sortableProps, sort) < 0)) {
305418
return util.handleError('Invalid filters or sort', null, req, next);
306419
}

0 commit comments

Comments
 (0)