fabric8-planner
Version:
A planner front-end for Fabric8.
657 lines • 27.3 kB
JavaScript
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