UNPKG

fabric8-planner

Version:
657 lines 27.3 kB
import { Injectable } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Subject, throwError } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { HttpClientService } from '../shared/http-module/http.service'; import { AND, ENCLOSURE, EQUAL, IN, NOT_EQUAL, NOT_IN, OR, P_END, P_START, SUB_STR } from './query-keys'; var FilterService = /** @class */ (function () { function FilterService(httpClientService, route) { this.httpClientService = httpClientService; this.route = route; this.filters = []; this.activeFilters = []; this.filterChange = new Subject(); this.filterObservable = new Subject(); this.and_notation = AND; this.or_notation = OR; this.equal_notation = EQUAL; this.not_equal_notation = NOT_EQUAL; this.in_notation = IN; this.not_in_notation = NOT_IN; this.sub_str_notation = SUB_STR; this.str_enclouser = ENCLOSURE; this.special_keys = { 'null': null, 'true': true, 'false': false, '': null }; this.compare_notations = [ EQUAL, NOT_EQUAL, IN, NOT_IN, SUB_STR ]; this.join_notations = [ AND, OR ]; this.filtertoWorkItemMap = { 'assignee': ['relationships', 'assignees', 'data', ['id']], 'creator': ['relationships', 'creator', 'data', 'id'], 'area': ['relationships', 'area', 'data', 'id'], 'workitemtype': ['relationships', 'baseType', 'data', 'id'], 'iteration': ['relationships', 'iteration', 'data', 'id'], 'state': ['attributes', 'system.state'] }; } FilterService.prototype.setFilterValues = function (id, value) { var index = this.activeFilters.findIndex(function (f) { return f.id === id; }); if (index > -1 && value !== undefined) { this.activeFilters[index].paramKey = 'filter[' + id + ']'; this.activeFilters[index].value = value; } else { this.activeFilters.push({ id: id, paramKey: 'filter[' + id + ']', value: value }); } //Emit filter update event this.filterObservable.next(); }; FilterService.prototype.getFilterValue = function (id) { var filter = this.activeFilters.find(function (f) { return f.id === id; }); return filter ? filter.value : null; }; FilterService.prototype.applyFilter = function () { console.log('[FilterService::applyFilter] - Applying filters', this.activeFilters); this.filterChange.next(this.activeFilters); }; FilterService.prototype.getAppliedFilters = function (includeSidePanel) { if (includeSidePanel === void 0) { includeSidePanel = false; } if (includeSidePanel) { var arr = this.getFiltersFromUrl(); arr = arr.concat(this.activeFilters); //remove duplicates arr = arr .filter(function (thing, index, self) { return self.findIndex(function (t) { return t.id === thing.id; }) === index; }); return arr; } else { return this.activeFilters; } }; FilterService.prototype.getFiltersFromUrl = function () { // TODO // This code needs to be looked at // to support in query from the expression as well var refCurrentFilter = []; if (this.route.snapshot.queryParams['q']) { var urlString = this.route.snapshot.queryParams['q'] .replace(' ' + AND + ' ', ' ') .replace(' ' + OR + ' ', ' ') .replace(P_START, '') .replace(P_END, ''); var temp_arr = urlString.split(' '); for (var i = 0; i < temp_arr.length; i++) { var arr = temp_arr[i].split(':'); if (arr[1] !== undefined) { refCurrentFilter.push({ id: arr[0], paramKey: 'filter[' + arr[0] + ']', value: arr[1] }); } } } //active filter will have the transient filters //witgroup and space are permanent filters return refCurrentFilter.filter(function (f) { return f.id === 'typegroup.name' || f.id === 'space' || f.id === 'iteration'; }); }; FilterService.prototype.clearFilters = function (keys) { if (keys === void 0) { keys = []; } if (keys.length) { this.activeFilters = this.activeFilters.filter(function (f) { return keys.indexOf(f.id) > -1; }); } else { this.activeFilters = []; } }; /** * getFilters - Fetches all the available filters * @param apiUrl - The url to get list of all filters * @return Observable of FilterModel[] - Array of filters */ FilterService.prototype.getFilters = function (apiUrl) { return this.httpClientService .get(apiUrl) .pipe(map(function (response) { return response.data; }), catchError(function (error) { console.log('API returned error: ', error.message); return throwError('Error - [FilterService - getFilters]' + error.message); })); }; FilterService.prototype.returnFilters = function () { return this.filters; }; /** * Usage: to check if the workitem matches with current applied filter or not. * TODO: Make this function better and smarter * NOTE: To add a new filter you have to do nothing here, just update the filtertoWorkItemMap * @param WorkItem - workItem * @returns boolean */ FilterService.prototype.doesMatchCurrentFilter = function (workItem) { var _this = this; var refCurrentFilter = this.getFiltersFromUrl(); //concat both arrays refCurrentFilter = refCurrentFilter.concat(this.activeFilters); //remove duplicates refCurrentFilter = refCurrentFilter .filter(function (thing, index, self) { return self.findIndex(function (t) { return t.id === thing.id; }) === index; }); return refCurrentFilter.every(function (filter) { if (filter.id && Object.keys(_this.filtertoWorkItemMap).indexOf(filter.id) > -1) { var currentAttr_1 = workItem; return _this.filtertoWorkItemMap[filter.id].every(function (attr, map_index) { if (Array.isArray(attr)) { if (Array.isArray(currentAttr_1)) { var innerAttr = currentAttr_1; return currentAttr_1.some(function (item) { return item[attr[0]] == filter.value; }); } else { return false; } } else if (currentAttr_1[attr]) { currentAttr_1 = currentAttr_1[attr]; if (map_index === _this.filtertoWorkItemMap[filter.id].length - 1 && currentAttr_1 != filter.value) { return false; } else { return true; } } else { return false; } }); } return true; }); }; /** * This function encloses the query value within quotes * only if the string contains any space * value with spaces should never be without enclouser * for ease of coding and understanding * @param query */ FilterService.prototype.encloseValue = function (query) { // the value could be // null, true, false all special values if (typeof query !== 'string') { return query; } // If there is a space in between // and there no enclouser already // then enclosed the string and return it if (query.split(' ').length > 1 && !(query[0] === ENCLOSURE && query[query.length - 1] === ENCLOSURE)) { return ENCLOSURE + query + ENCLOSURE; } return query; }; /** * This function clears the quote from the value * @param query */ FilterService.prototype.clearEnclosedValue = function (query) { if (query[0] === ENCLOSURE && query[query.length - 1] === ENCLOSURE) { return query.substr(1, query.length - 2); } return query; }; /** * Take the existing query and simply AND it with provided options * @param existingQuery * @param options */ FilterService.prototype.constructQueryURL = function (existingQuery, options) { var _this = this; var processedObject = ''; // If onptions has any length enclose processedObject with () if (Object.keys(options).length > 1) { processedObject = P_START + Object.keys(options).map(function (key) { return typeof (options[key]) !== 'string' ? key + ':' + options[key] : options[key].split(',').map(function (val) { return key + ':' + _this.encloseValue(val.trim()); }).join(' ' + AND + ' '); }).join(' ' + AND + ' ') + P_END; } else if (Object.keys(options).length === 1) { processedObject = Object.keys(options).map(function (key) { return typeof (options[key]) !== 'string' ? key + ':' + options[key] : options[key].split(',').map(function (val) { return key + ':' + _this.encloseValue(val.trim()); }).join(' ' + AND + ' '); }).join(' ' + AND + ' '); } else { return decodeURIComponent(existingQuery); } // Check if the existing query is empty // Then return processedObject if (existingQuery === '') { return processedObject; } else { // Decode existing URL var decodedURL = decodeURIComponent(existingQuery); // Check if there is any composite query in existing one if (decodedURL.indexOf(AND) > -1 || decodedURL.indexOf(OR) > -1) { // Check if existing query is a group i.e. enclosed if (decodedURL[0] !== P_START || decodedURL[decodedURL.length - 1] !== P_END) { // enclose it with () decodedURL = P_START + decodedURL + P_END; } } // Add the query from option with AND operation return P_START + decodedURL + ' ' + AND + ' ' + processedObject + P_END; } }; /** * * @param key The value is the object key like 'workitem_type', 'iteration' etc * @param compare The values are * FilterService::EQUAL', * FilterService::not_EQUAL', * FilterService::not_EQUAL', * FilterService::in_notation', * FilterService::not_in_notation' * @param value string or array of string of values (in case of IN or NOT IN) */ FilterService.prototype.queryBuilder = function (key, compare, value) { if (this.compare_notations.indexOf(compare.trim()) == -1) { throw new Error('Not a valid compare notation'); } var op = {}; op[key.trim()] = {}; if (Array.isArray(value)) { op[key.trim()][compare.trim()] = value.map(function (v) { return v.trim(); }); } else { op[key.trim()][compare.trim()] = value === null ? null : value.trim(); } return op; }; /** * * @param existingQueryObject * @param join The values are * FilterService::AND, * FilterService::OR * @param newQueryObject */ FilterService.prototype.queryJoiner = function (existingQueryObject, join, newQueryObject) { if (this.join_notations.indexOf(join.trim()) == -1) { throw new Error('Not a valid compare notation'); } // existingQueryObject is empty if (!Object.keys(existingQueryObject).length) { if (Object.keys(newQueryObject).length) { if (this.join_notations.indexOf(Object.keys(newQueryObject)[0]) > -1) { return newQueryObject; } else { var op = {}; op[OR] = [newQueryObject]; return op; } } else { return {}; } } else { // If existingObject is not empty var existingJoiner = Object.keys(existingQueryObject)[0]; // If existing joiner is not valid if (this.join_notations.indexOf(existingJoiner) == -1) { throw new Error('Existing query object is invalid without a joiner in root'); } // If new object is empty then return existingQueryObject if (!Object.keys(newQueryObject).length) { return existingQueryObject; } var newJoiner = Object.keys(newQueryObject)[0]; // If new object has join_notation as root if (this.join_notations.indexOf(newJoiner) > -1) { // If new joiner existing joiner and given joiner is same // put all of them under one joiner if (join === newJoiner && join === existingJoiner) { var op = {}; op[join] = existingQueryObject[join].concat(newQueryObject[join]); return op; } else if (existingQueryObject[existingJoiner].length === 1 && newQueryObject[newJoiner].length === 1) { var op = {}; op[join] = existingQueryObject[existingJoiner].concat(newQueryObject[newJoiner]); return op; } else if (existingQueryObject[existingJoiner].length === 1) { var op = {}; if (newJoiner === join) { op[join] = existingQueryObject[existingJoiner].concat(newQueryObject[newJoiner]); } else { op[join] = existingQueryObject[existingJoiner].concat([ newQueryObject ]); } return op; } else if (newQueryObject[newJoiner].length === 1) { var op = {}; if (existingJoiner === join) { op[join] = existingQueryObject[existingJoiner].concat(newQueryObject[newJoiner]); } else { op[join] = [ existingQueryObject ].concat(newQueryObject[newJoiner]); } return op; } else { var op = {}; op[join] = [ existingQueryObject, newQueryObject ]; return op; } } else { if (join === existingJoiner) { existingQueryObject[join].push(newQueryObject); return existingQueryObject; } else { var op = {}; // If existingQueryObject has only one item in the array if (existingQueryObject[existingJoiner].length === 1) { op[join] = existingQueryObject[existingJoiner].concat([ newQueryObject ]); } else { op[join] = [ existingQueryObject, newQueryObject ]; } return op; } } } }; /** * Query string to JSON conversion */ FilterService.prototype.queryToJson = function (query, first_level) { var _this = this; if (first_level === void 0) { first_level = true; } var temp = [], p_count = 0, p_start = -1, new_str = '', output = {}; // counting on parenthesis // Replacing highest level of enclosed querries with __temp__ // for example - // (a:b $AND c:d) $OR e:f becomes // __temp__ $OR e:f // tmp queue has a:b $AND c:d for (var i = 0; i < query.length; i++) { if (query[i] === P_START) { if (p_start < 0) { p_start = i; } p_count += 1; } if (p_start === -1) { new_str += query[i]; } if (query[i] === P_END) { p_count -= 1; } if (p_start >= 0 && p_count === 0) { temp.push(query.substring(p_start + 1, i)); new_str += '__temp__'; p_start = -1; } } // In order to treat it as a queue we reverse the temp array temp.reverse(); // First split with $OR var arr = new_str.split(OR); if (arr.length > 1) { // Each element after the split will be either // a singular query i.e. some_key: some_value // or some query with or without __temp__ in it // We replace the __temp__ with the actual string // Pass it through the same function output[OR] = arr.map(function (item) { item = item.trim(); if (item == '__temp__') { item = temp.pop(); } while (item.indexOf('__temp__') > -1) { item = item.replace('__temp__', temp.pop()); } return _this.queryToJson(item, false); }); } else { // Next split the item by $AND arr = new_str.split(AND); if (arr.length > 1) { // Do the same as earlier output[AND] = arr.map(function (item) { if (item.trim() == '__temp__') { item = temp.pop(); } return _this.queryToJson(item, false); }); } else { var dObj = {}; while (new_str.indexOf('__temp__') > -1) { new_str = new_str.replace('__temp__', temp.pop()); } if (new_str.indexOf(AND) > -1 || new_str.indexOf(OR) > -1) { return this.queryToJson(new_str, false); } var keyIndex = -1; var splitter = ''; for (var i = 0; i < new_str.length; i++) { if (new_str[i] === ':' || new_str[i] === '!') { splitter = new_str[i]; keyIndex = i; break; } } var key = new_str.substring(0, keyIndex).trim(); var value = new_str.substring(keyIndex + 1).trim(); var val_arr_1 = value.split(',').map(function (i) { return i.trim(); }); dObj[key] = {}; if (splitter === '!') { if (val_arr_1.length > 1) { dObj[key][NOT_IN] = val_arr_1; } else { if (Object.keys(this.special_keys).findIndex(function (k) { return k === val_arr_1[0]; }) > -1) { dObj[key][NOT_EQUAL] = this.special_keys[val_arr_1[0]]; } else if (key === 'title') { dObj[key][SUB_STR] = this.clearEnclosedValue(val_arr_1[0]); } else { dObj[key][NOT_EQUAL] = this.clearEnclosedValue(val_arr_1[0]); } } } else if (splitter === ':') { if (val_arr_1.length > 1) { dObj[key][IN] = val_arr_1; } else { if (Object.keys(this.special_keys).findIndex(function (k) { return k === val_arr_1[0]; }) > -1) { dObj[key][EQUAL] = this.special_keys[val_arr_1[0]]; } else if (key === 'title') { dObj[key][SUB_STR] = this.clearEnclosedValue(val_arr_1[0]); } else { dObj[key][EQUAL] = this.clearEnclosedValue(val_arr_1[0]); } } } if (first_level) { output[OR] = [dObj]; } else { return dObj; } } } return output; }; FilterService.prototype.jsonToQuery = function (obj) { var _this = this; var key = Object.keys(obj)[0]; // key will be AND or OR var value = obj[key]; return P_START + value.map(function (item) { if (Object.keys(item)[0] == AND || Object.keys(item)[0] == OR) { return _this.jsonToQuery(item); } else { var data_key = Object.keys(item)[0]; var data = item[data_key]; var conditional_operator = Object.keys(data)[0]; var splitter = ''; switch (conditional_operator) { case EQUAL: splitter = ':'; return data_key + splitter + _this.encloseValue(data[conditional_operator]); case NOT_EQUAL: splitter = '!'; return data_key + splitter + _this.encloseValue(data[conditional_operator]); case IN: splitter = ':'; return data_key + splitter + data[conditional_operator].join(); case NOT_IN: splitter = '!'; return data_key + splitter + data[conditional_operator].join(); case SUB_STR: splitter = ':'; return data_key + splitter + _this.encloseValue(data[conditional_operator]); } } }) .join(' ' + key + ' ') + P_END; }; /** * This decodes a key query term value from a given query string. It is used to * shortcut the parsing of the query string to get context info from it. Currently, * it is used when getting the context info from an existing query to give context * to a following UX flow. This only supports a very narrow usecase currently, but * may be extended later. * * @param queryString search/filter query string. * @param key key of the term for which we look for the value. */ FilterService.prototype.getConditionFromQuery = function (queryString, key) { if (queryString) { var decodedQuery = this.queryToJson(queryString); // we ignore non-AND queries for now, might want to extend that later. if (!decodedQuery[AND] && !(decodedQuery[OR] && decodedQuery[OR].length === 1)) { console.log('The current query is not supported by getConditionFromQuery() (non-AND query): ' + queryString); return undefined; } else { var terms = decodedQuery[AND] ? decodedQuery[AND] : decodedQuery[OR]; if (terms || !Array.isArray(terms)) { for (var i = 0; i < terms.length; i++) { var thisTerm = terms[i]; if (thisTerm && thisTerm[key]) { // format of value: {$EQ: "value"}, if not found, value remains undefined return thisTerm[key]['$EQ']; } } console.log('Condition key not found in query: ' + key + ', query= ' + queryString); return undefined; } else { console.log('The current query is not supported by getConditionFromQuery() (bad format): ' + queryString); // use standard non-context create dialog return undefined; } } } }; // Temporary function to deal with single level $AND operator FilterService.prototype.queryToFlat = function (query) { return query.replace(/^\((.+)\)$/, '$1') .split(AND).map(function (item, index) { // regex to match field:value pattern. // for item=title:A:D, field -> title and value -> A:D var filterValue = /(^[^:]+):(.*)$/.exec(item); return { field: filterValue[1].trim(), index: index, value: filterValue[2].trim() }; }); }; FilterService.prototype.flatToQuery = function (arr) { var _this = this; var query = {}; arr.forEach(function (item) { var newQuery = _this.queryBuilder(item.field, EQUAL, item.value); query = _this.queryJoiner(query, AND, newQuery); }); return query; }; /** * This function takes only a query string * and returns the parent number if it's only a child Query * @param query * @returns string */ FilterService.prototype.isOnlyChildQuery = function (query) { try { var jsonQuery = this.queryToJson(query); if (jsonQuery[OR] && jsonQuery[OR].length === 1 && jsonQuery[OR][0]['parent.number'] && jsonQuery[OR][0]['parent.number'][EQUAL]) { return jsonQuery[OR][0]['parent.number'][EQUAL]; } } catch (_a) { } return null; }; FilterService.decorators = [ { type: Injectable }, ]; /** @nocollapse */ FilterService.ctorParameters = function () { return [ { type: HttpClientService, }, { type: ActivatedRoute, }, ]; }; return FilterService; }()); export { FilterService }; //# sourceMappingURL=filter.service.js.map