From 508535be6662300dd6cf8d26e3e00acdd6dafa5e Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Thu, 19 Sep 2019 11:42:00 -0700 Subject: [PATCH] rework removeParams --- awx/ui_next/src/util/qs.js | 191 +++++++++++++++----------------- awx/ui_next/src/util/qs.test.js | 99 +++++++++++++---- 2 files changed, 164 insertions(+), 126 deletions(-) diff --git a/awx/ui_next/src/util/qs.js b/awx/ui_next/src/util/qs.js index 75df652120..60d68127c2 100644 --- a/awx/ui_next/src/util/qs.js +++ b/awx/ui_next/src/util/qs.js @@ -36,6 +36,53 @@ export function parseQueryString(config, queryString) { return addDefaultsToObject(config, params); } +function stringToObject(config, qs) { + const params = {}; + qs.replace(/^\?/, '') + .split('&') + .map(s => s.split('=')) + .forEach(([nsKey, rawValue]) => { + if (!nsKey || !namespaceMatches(config.namespace, nsKey)) { + return; + } + const key = config.namespace + ? decodeURIComponent(nsKey.substr(config.namespace.length + 1)) + : decodeURIComponent(nsKey); + const value = parseValue(config, key, rawValue); + params[key] = mergeParam(params[key], value); + }); + return params; +}; +export { stringToObject as _stringToObject }; + +/** + * helper function to check the namespace of a param is what you expec + * @param {string} namespace to append to params + * @param {object} params object to append namespace to + * @return {object} params object with namespaced keys + */ +const namespaceMatches = (namespace, fieldname) => { + if (!namespace) return !fieldname.includes('.'); + + return fieldname.startsWith(`${namespace}.`); +}; + +function parseValue(config, key, rawValue) { + if (config.integerFields && config.integerFields.some(v => v === key)) { + return parseInt(rawValue, 10); + } + // TODO: parse dateFields into date format? + return decodeURIComponent(rawValue); +} + +function addDefaultsToObject(config, params) { + return { + ...config.defaultParams, + ...params, + }; +} +export { addDefaultsToObject as _addDefaultsToObject }; + /** * Convert query param object to url query string * Used to encode params for interacting with the api @@ -97,100 +144,13 @@ export const encodeNonDefaultQueryString = (config, params) => { .join('&'); }; -/** - * Removes params from the search string and returns the updated list of params - * @param {object} qs config object (used for getting defaults, current query params etc.) - * @param {object} object with params from existing search - * @param {object} object with new params to remove - * @return {object} query param object - */ -export function removeParams(config, oldParams, paramsToRemove) { - const paramsEntries = []; - Object.entries(oldParams).forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach(val => { - paramsEntries.push([key, val]); - }); - } else { - paramsEntries.push([key, value]); - } - }); - const paramsToRemoveEntries = Object.entries(paramsToRemove); - const remainingEntries = paramsEntries.filter( - ([key, value]) => - paramsToRemoveEntries.filter( - ([newKey, newValue]) => key === newKey && value === newValue - ).length === 0 - ); - const remainingObject = arrayToObject(remainingEntries); - const defaultEntriesLeftover = Object.entries(config.defaultParams).filter( - ([key]) => !remainingObject[key] - ); - const finalParamsEntries = remainingEntries; - defaultEntriesLeftover.forEach(value => { - finalParamsEntries.push(value); - }); - return arrayToObject(finalParamsEntries); -} - -const stringToObject = (config, qs) => { - const params = {}; - qs.replace(/^\?/, '') - .split('&') - .map(s => s.split('=')) - .forEach(([nsKey, rawValue]) => { - if (!nsKey || !namespaceMatches(config.namespace, nsKey)) { - return; - } - const key = decodeURIComponent(nsKey.substr(config.namespace.length + 1)); - const value = parseValue(config, key, rawValue); - params[key] = mergeParam(params[key], value); - }); - return params; -}; -export { stringToObject as _stringToObject }; - -function parseValue(config, key, rawValue) { - if (config.integerFields && config.integerFields.some(v => v === key)) { - return parseInt(rawValue, 10); - } - // TODO: parse dateFields into date format? - return decodeURIComponent(rawValue); -} - -function addDefaultsToObject(config, params) { - return { - ...config.defaultParams, - ...params, - }; -} -export { addDefaultsToObject as _addDefaultsToObject }; - -/** - * helper function used to convert from - * Object.entries format ([ [ key, value ], ... ]) to object - * @param {array} array in the format [ [ key, value ], ...] - * @return {object} object in the forms { key: value, ... } - */ -const arrayToObject = entriesArr => - entriesArr.reduce((acc, [key, value]) => { - if (acc[key] && Array.isArray(acc[key])) { - acc[key].push(value); - } else if (acc[key]) { - acc[key] = [acc[key], value]; - } else { - acc[key] = value; - } - return acc; - }, {}); - /** * helper function to namespace params object * @param {string} namespace to append to params * @param {object} params object to append namespace to * @return {object} params object with namespaced keys */ -const namespaceParams = (namespace, params = {}) => { +const namespaceParams = (namespace, params) => { if (!namespace) return params; const namespaced = {}; @@ -198,19 +158,7 @@ const namespaceParams = (namespace, params = {}) => { namespaced[`${namespace}.${key}`] = params[key]; }); - return namespaced || {}; -}; - -/** - * helper function to check the namespace of a param is what you expec - * @param {string} namespace to append to params - * @param {object} params object to append namespace to - * @return {object} params object with namespaced keys - */ -const namespaceMatches = (namespace, fieldname) => { - if (!namespace) return !fieldname.includes('.'); - - return fieldname.startsWith(`${namespace}.`); + return namespaced; }; /** @@ -234,6 +182,45 @@ const paramValueIsEqual = (one, two) => { return isEqual; }; +/** + * Removes params from the search string and returns the updated list of params + * @param {object} qs config object (used for getting defaults, current query params etc.) + * @param {object} object with params from existing search + * @param {object} object with new params to remove + * @return {object} query param object + */ +export function removeParams(config, oldParams, paramsToRemove) { + const updated = { + ...config.defaultParams, + }; + Object.keys(oldParams).forEach(key => { + const value = removeParam(oldParams[key], paramsToRemove[key]); + if (value) { + updated[key] = value; + } + }); + return updated; +} + +function removeParam(oldVal, deleteVal) { + if (oldVal === deleteVal) { + return null; + } + if (Array.isArray(deleteVal)) { + return deleteVal.reduce(removeParam, oldVal); + } + if (Array.isArray(oldVal)) { + const index = oldVal.indexOf(deleteVal); + if (index > -1) { + oldVal.splice(index, 1); + } + if (oldVal.length === 1) { + return oldVal[0]; + } + } + return oldVal; +} + /** * Merge old and new params together, joining values into arrays where necessary * @param {object} namespaced params object of old params @@ -264,7 +251,7 @@ function mergeParam(oldVal, newVal) { if (Array.isArray(oldVal)) { merged = oldVal.concat(newVal); } else { - merged = ([oldVal]).concat(newVal); + merged = [oldVal].concat(newVal); } return dedupeArray(merged); } diff --git a/awx/ui_next/src/util/qs.test.js b/awx/ui_next/src/util/qs.test.js index c0acb8fbd1..6ae8e48ba0 100644 --- a/awx/ui_next/src/util/qs.test.js +++ b/awx/ui_next/src/util/qs.test.js @@ -3,7 +3,6 @@ import { encodeNonDefaultQueryString, parseQueryString, getQSConfig, - addParams, removeParams, _stringToObject, _addDefaultsToObject, @@ -286,6 +285,19 @@ describe('qs (qs.js)', () => { baz: ['one', 'two', 'three'], }); }); + + test('should handle non-namespaced params', () => { + const config = { + namespace: null, + defaultParams: { page: 1, page_size: 15 }, + integerFields: ['page', 'page_size'], + }; + const query = '?item.baz=bar&page=3'; + expect(parseQueryString(config, query)).toEqual({ + page: 3, + page_size: 15, + }); + }); }); describe('removeParams', () => { @@ -296,8 +308,8 @@ describe('qs (qs.js)', () => { integerFields: ['page', 'page_size'], }; const oldParams = { baz: 'bar', page: 3, bag: 'boom', page_size: 15 }; - const newParams = { bag: 'boom' }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { bag: 'boom' }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ baz: 'bar', page: 3, page_size: 15, @@ -311,8 +323,8 @@ describe('qs (qs.js)', () => { integerFields: ['page', 'page_size'], }; const oldParams = { baz: ['bar', 'bang'], page: 3, page_size: 15 }; - const newParams = { baz: 'bar' }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { baz: 'bar' }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ baz: 'bang', page: 3, page_size: 15, @@ -330,14 +342,33 @@ describe('qs (qs.js)', () => { page: 3, page_size: 15, }; - const newParams = { baz: 'bar' }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { baz: 'bar' }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ baz: ['bang', 'bust'], page: 3, page_size: 15, }); }); + test('should remove multiple values from query params (array -> smaller array)', () => { + const config = { + namespace: null, + defaultParams: { page: 1, page_size: 15 }, + integerFields: ['page', 'page_size'], + }; + const oldParams = { + baz: ['bar', 'bang', 'bust'], + page: 3, + page_size: 15, + }; + const toRemove = { baz: ['bang', 'bar'] }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ + baz: 'bust', + page: 3, + page_size: 15, + }); + }); + test('should reset query params that have default keys back to default values', () => { const config = { namespace: null, @@ -345,8 +376,8 @@ describe('qs (qs.js)', () => { integerFields: ['page', 'page_size'], }; const oldParams = { baz: ['bar', 'bang'], page: 3, page_size: 15 }; - const newParams = { page: 3 }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { page: 3 }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ baz: ['bar', 'bang'], page: 1, page_size: 15, @@ -365,8 +396,8 @@ describe('qs (qs.js)', () => { page: 3, page_size: 15, }; - const newParams = { baz: 'bust', pat: 'pal' }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { baz: 'bust', pat: 'pal' }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ baz: ['bar', 'bang'], page: 3, page_size: 15, @@ -380,8 +411,8 @@ describe('qs (qs.js)', () => { integerFields: ['page', 'page_size'], }; const oldParams = { baz: 'bar', page: 3, page_size: 15 }; - const newParams = { baz: 'bar' }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { baz: 'bar' }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ page: 3, page_size: 15, }); @@ -394,8 +425,8 @@ describe('qs (qs.js)', () => { integerFields: ['page', 'page_size'], }; const oldParams = { baz: 'bar', page: 1, page_size: 15 }; - const newParams = { baz: 'bar' }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { baz: 'bar' }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ page: 1, page_size: 15, }); @@ -408,8 +439,8 @@ describe('qs (qs.js)', () => { integerFields: ['page', 'page_size'], }; const oldParams = { baz: ['bar', 'bang'], page: 3, page_size: 15 }; - const newParams = { baz: 'bar' }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { baz: 'bar' }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ baz: 'bang', page: 3, page_size: 15, @@ -427,8 +458,8 @@ describe('qs (qs.js)', () => { page: 3, page_size: 15, }; - const newParams = { baz: 'bar' }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { baz: 'bar' }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ baz: ['bang', 'bust'], page: 3, page_size: 15, @@ -442,14 +473,34 @@ describe('qs (qs.js)', () => { integerFields: ['page', 'page_size'], }; const oldParams = { baz: ['bar', 'bang'], page: 3, page_size: 15 }; - const newParams = { page: 3 }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { page: 3 }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ baz: ['bar', 'bang'], page: 1, page_size: 15, }); }); + test('should retain long array values', () => { + const config = { + namespace: 'item', + defaultParams: { page: 1, page_size: 15 }, + integerFields: ['page', 'page_size'], + }; + const oldParams = { + baz: ['one', 'two', 'three'], + page: 3, + bag: 'boom', + page_size: 15, + }; + const toRemove = { bag: 'boom' }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ + baz: ['one', 'two', 'three'], + page: 3, + page_size: 15, + }); + }); + test('should remove multiple namespaced params', () => { const config = { namespace: 'item', @@ -462,8 +513,8 @@ describe('qs (qs.js)', () => { page: 3, page_size: 15, }; - const newParams = { baz: 'bust', pat: 'pal' }; - expect(removeParams(config, oldParams, newParams)).toEqual({ + const toRemove = { baz: 'bust', pat: 'pal' }; + expect(removeParams(config, oldParams, toRemove)).toEqual({ baz: ['bar', 'bang'], page: 3, page_size: 15, @@ -645,7 +696,7 @@ describe('qs (qs.js)', () => { page: 3, page_size: 15, }); - }) + }); }); describe('replaceParams', () => {