diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 3782f6e740..d903776765 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -249,7 +249,7 @@ var tower = angular.module('Tower', [ }, // decoding // from "_search=operator:key:compator=value& ... " - // to "_search=operator:key:compator=value& ... " + // to {operator__key1__comparator=value, ... } decode: function(item) { return QuerySet.$get().decodeArr(item); }, diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index 1296a1394b..a45f8af74f 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -51,10 +51,19 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear encodeQueryset(params) { let queryset; queryset = _.reduce(params, (result, value, key) => { - return result + `${key}=${value}&`; + return result + encodeTerm(value, key); }, ''); queryset = queryset.substring(0, queryset.length - 1); return angular.isObject(params) ? `?${queryset}` : ''; + + function encodeTerm(value, key){ + if (Array.isArray(value)){ + return _.map(value, (item) => `${key}=${item}`).join('&') + '&'; + } + else { + return `${key}=${value}&`; + } + } }, // encodes a ui smart-search param to a django-friendly param // operand:key:comparator:value => {operand__key__comparator: value} @@ -62,19 +71,42 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear let split = param.split(':'); return {[split.slice(0,split.length -1).join('__')] : split[split.length-1]}; }, - // decodes a django queryset param into ui smart-search param - decodeParam(key, value){ - return `${key.split('__').join(':')}:${value}`; + // decodes a django queryset param into a ui smart-search tag or set of tags + decodeParam(value, key){ + if (Array.isArray(value)){ + return _.map(value, (item) => { + return `${key.split('__').join(':')}:${item}`; + }); + } + else { + return `${key.split('__').join(':')}:${value}`; + } }, // encodes a django queryset for ui-router's URLMatcherFactory - // {operand__key__comparator: value, } => 'operand:key:comparator:value,...' + // {operand__key__comparator: value, } => 'operand:key:comparator:value;...' + // value.isArray expands to: + // {operand__key__comparator: [value1, value2], } => 'operand:key:comparator:value1;operand:key:comparator:value1...' encodeArr(params) { let url; url = _.reduce(params, (result, value, key) => { - return result.concat(`${key}:${value}`); + return result.concat(encodeUrlString(value, key)); }, []); + return url.join(';'); + + // {key:'value'} => 'key:value' + // {key: [value1, value2, ...]} => ['key:value1', 'key:value2'] + function encodeUrlString(value, key){ + if (Array.isArray(value)){ + return _.map(value, (item) => { + return `${key}:${item}`; + }); + } + else { + return `${key}:${value}`; + } + } }, // decodes a django queryset for ui-router's URLMatcherFactory @@ -84,7 +116,15 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear _.forEach(arr.split(';'), (item) => { let key = item.split(':')[0], value = item.split(':')[1]; - params[key] = value; + if(!params[key]){ + params[key] = value; + } + else if (Array.isArray(params[key])){ + params[key].push(value); + } + else { + params[key] = [params[key], value]; + } }); return params; }, diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index fd2fafe1aa..4fa3517547 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -22,10 +22,11 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' // Removes state definition defaults and pagination terms function stripDefaultParams(params) { - return _.pick(params, (value, key) => { + let stripped =_.pick(params, (value, key) => { // setting the default value of a term to null in a state definition is a very explicit way to ensure it will NEVER generate a search tag, even with a non-default value return defaults[key] !== value && key !== 'page' && key !== 'page_size' && defaults[key] !== null; }); + return _(stripped).map(qs.decodeParam).flatten().value(); } // searchable relationships @@ -53,8 +54,16 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' }; // remove tag, merge new queryset, $state.go - $scope.remove = function(key) { - delete queryset[key]; + $scope.remove = function(index) { + let removed = qs.encodeParam($scope.searchTags.splice(index, 1)[0]); + _.each(removed, (value, key) => { + if (Array.isArray(queryset[key])){ + _.remove(queryset[key], (item) => item === value); + } + else { + delete queryset[key]; + } + }); $state.go('.', { [$scope.iterator + '_search']: queryset }); qs.search(path, queryset).then((res) => { @@ -91,7 +100,16 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' } params.page = '1'; - queryset = _.merge(queryset, params); + queryset = _.merge(queryset, params, (objectValue, sourceValue, key, object) => { + if (object[key] && object[key] !== sourceValue){ + return [object[key], sourceValue]; + } + else { + // // https://lodash.com/docs/3.10.1#merge + // If customizer fn returns undefined merging is handled by default _.merge algorithm + return undefined; + } + }); // https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic // This transition will not reload controllers/resolves/views // but will register new $stateParams[$scope.iterator + '_search'] terms @@ -105,9 +123,5 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' $scope.searchTerm = null; $scope.searchTags = stripDefaultParams(queryset); }; - - $scope.decodeParam = function(key, value) { - return qs.decodeParam(key, value); - }; } ]; diff --git a/awx/ui/client/src/shared/smart-search/smart-search.partial.html b/awx/ui/client/src/shared/smart-search/smart-search.partial.html index a3850d0de0..3c0061fa33 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.partial.html +++ b/awx/ui/client/src/shared/smart-search/smart-search.partial.html @@ -19,12 +19,12 @@