Commit de53e7d5 authored by Michael Lunøe's avatar Michael Lunøe

Merge branch 'mrincon-support-plus-as-spaces' into 'master'

Update queryToObject to replace plus with spaces

See merge request gitlab-org/gitlab!63753
parents 68746a8e c13a6d22
...@@ -31,7 +31,7 @@ function organizeQuery(obj, isFallbackKey = false) { ...@@ -31,7 +31,7 @@ function organizeQuery(obj, isFallbackKey = false) {
} }
function format(searchTerm, isFallbackKey = false) { function format(searchTerm, isFallbackKey = false) {
const queryObject = queryToObject(searchTerm); const queryObject = queryToObject(searchTerm, { legacySpacesDecode: true });
const organizeQueryObject = organizeQuery(queryObject, isFallbackKey); const organizeQueryObject = organizeQuery(queryObject, isFallbackKey);
const formattedQuery = objectToQuery(organizeQueryObject); const formattedQuery = objectToQuery(organizeQueryObject);
......
...@@ -414,29 +414,35 @@ export function getWebSocketUrl(path) { ...@@ -414,29 +414,35 @@ export function getWebSocketUrl(path) {
* *
* @param {String} query from "document.location.search" * @param {String} query from "document.location.search"
* @param {Object} options * @param {Object} options
* @param {Boolean} options.gatherArrays - gather array values into an Array * @param {Boolean?} options.gatherArrays - gather array values into an Array
* @param {Boolean?} options.legacySpacesDecode - (deprecated) plus symbols (+) are not replaced with spaces, false by default
* @returns {Object} * @returns {Object}
* *
* ex: "?one=1&two=2" into {one: 1, two: 2} * ex: "?one=1&two=2" into {one: 1, two: 2}
*/ */
export function queryToObject(query, options = {}) { export function queryToObject(query, { gatherArrays = false, legacySpacesDecode = false } = {}) {
const { gatherArrays = false } = options;
const removeQuestionMarkFromQuery = String(query).startsWith('?') ? query.slice(1) : query; const removeQuestionMarkFromQuery = String(query).startsWith('?') ? query.slice(1) : query;
return removeQuestionMarkFromQuery.split('&').reduce((accumulator, curr) => { return removeQuestionMarkFromQuery.split('&').reduce((accumulator, curr) => {
const [key, value] = curr.split('='); const [key, value] = curr.split('=');
if (value === undefined) { if (value === undefined) {
return accumulator; return accumulator;
} }
const decodedValue = decodeURIComponent(value);
const decodedValue = legacySpacesDecode ? decodeURIComponent(value) : decodeUrlParameter(value);
if (gatherArrays && key.endsWith('[]')) { if (gatherArrays && key.endsWith('[]')) {
const decodedKey = decodeURIComponent(key.slice(0, -2)); const decodedKey = legacySpacesDecode
? decodeURIComponent(key.slice(0, -2))
: decodeUrlParameter(key.slice(0, -2));
if (!Array.isArray(accumulator[decodedKey])) { if (!Array.isArray(accumulator[decodedKey])) {
accumulator[decodedKey] = []; accumulator[decodedKey] = [];
} }
accumulator[decodedKey].push(decodedValue); accumulator[decodedKey].push(decodedValue);
} else { } else {
accumulator[decodeURIComponent(key)] = decodedValue; const decodedKey = legacySpacesDecode ? decodeURIComponent(key) : decodeUrlParameter(key);
accumulator[decodedKey] = decodedValue;
} }
return accumulator; return accumulator;
......
...@@ -175,7 +175,7 @@ export const graphDataValidatorForAnomalyValues = (graphData) => { ...@@ -175,7 +175,7 @@ export const graphDataValidatorForAnomalyValues = (graphData) => {
* Returns `null` if no parameters form a time range. * Returns `null` if no parameters form a time range.
*/ */
export const timeRangeFromUrl = (search = window.location.search) => { export const timeRangeFromUrl = (search = window.location.search) => {
const params = queryToObject(search); const params = queryToObject(search, { legacySpacesDecode: true });
return timeRangeFromParams(params); return timeRangeFromParams(params);
}; };
...@@ -228,7 +228,7 @@ export const convertVariablesForURL = (variables) => ...@@ -228,7 +228,7 @@ export const convertVariablesForURL = (variables) =>
* @returns {Object} The custom variables defined by the user in the URL * @returns {Object} The custom variables defined by the user in the URL
*/ */
export const templatingVariablesFromUrl = (search = window.location.search) => { export const templatingVariablesFromUrl = (search = window.location.search) => {
const params = queryToObject(search); const params = queryToObject(search, { legacySpacesDecode: true });
// pick the params with variable prefix // pick the params with variable prefix
const paramsWithVars = pickBy(params, (val, key) => key.startsWith(VARIABLE_PREFIX)); const paramsWithVars = pickBy(params, (val, key) => key.startsWith(VARIABLE_PREFIX));
// remove the prefix before storing in the Vuex store // remove the prefix before storing in the Vuex store
...@@ -289,7 +289,7 @@ export const timeRangeToUrl = (timeRange, url = window.location.href) => { ...@@ -289,7 +289,7 @@ export const timeRangeToUrl = (timeRange, url = window.location.href) => {
* @throws Will throw an error if Panel cannot be located. * @throws Will throw an error if Panel cannot be located.
*/ */
export const expandedPanelPayloadFromUrl = (dashboard, search = window.location.search) => { export const expandedPanelPayloadFromUrl = (dashboard, search = window.location.search) => {
const params = queryToObject(search); const params = queryToObject(search, { legacySpacesDecode: true });
// Search for the panel if any of the search params is identified // Search for the panel if any of the search params is identified
if (params.group || params.title || params.y_label) { if (params.group || params.title || params.y_label) {
......
import { queryToObject } from '~/lib/utils/url_utility'; import { queryToObject } from '~/lib/utils/url_utility';
import { FILTERED_SEARCH_TERM } from './constants'; import { FILTERED_SEARCH_TERM } from './constants';
export const getQueryParams = (query) => queryToObject(query, { gatherArrays: true }); export const getQueryParams = (query) =>
queryToObject(query, { gatherArrays: true, legacySpacesDecode: true });
export const keyValueToFilterToken = (type, data) => ({ type, value: { data } }); export const keyValueToFilterToken = (type, data) => ({ type, value: { data } });
......
...@@ -8,10 +8,7 @@ import createStore from './store'; ...@@ -8,10 +8,7 @@ import createStore from './store';
import { initTopbar } from './topbar'; import { initTopbar } from './topbar';
export const initSearchApp = () => { export const initSearchApp = () => {
// Similar to url_utility.decodeUrlParameter const query = queryToObject(window.location.search);
// Our query treats + as %20. This replaces the query + symbols with %20.
const sanitizedSearch = window.location.search.replace(/\+/g, '%20');
const query = queryToObject(sanitizedSearch);
const store = createStore({ query }); const store = createStore({ query });
......
...@@ -142,11 +142,11 @@ function extractNameAndOperator(filterName) { ...@@ -142,11 +142,11 @@ function extractNameAndOperator(filterName) {
* '?myFilterName=foo' * '?myFilterName=foo'
* gets translated into: * gets translated into:
* { myFilterName: { value: 'foo', operator: '=' } } * { myFilterName: { value: 'foo', operator: '=' } }
* @param {String} query URL quert string, e.g. from `window.location.search` * @param {String} query URL query string, e.g. from `window.location.search`
* @return {Object} filter object with filter names and their values * @return {Object} filter object with filter names and their values
*/ */
export function urlQueryToFilter(query = '') { export function urlQueryToFilter(query = '') {
const filters = queryToObject(query, { gatherArrays: true }); const filters = queryToObject(query, { gatherArrays: true, legacySpacesDecode: true });
return Object.keys(filters).reduce((memo, key) => { return Object.keys(filters).reduce((memo, key) => {
const value = filters[key]; const value = filters[key];
if (!value) { if (!value) {
......
...@@ -5,7 +5,7 @@ import * as types from './mutation_types'; ...@@ -5,7 +5,7 @@ import * as types from './mutation_types';
export const initializeAuditEvents = ({ commit }) => { export const initializeAuditEvents = ({ commit }) => {
commit( commit(
types.INITIALIZE_AUDIT_EVENTS, types.INITIALIZE_AUDIT_EVENTS,
parseAuditEventSearchQuery(queryToObject(window.location.search)), parseAuditEventSearchQuery(queryToObject(window.location.search, { legacySpacesDecode: true })),
); );
}; };
......
...@@ -227,7 +227,7 @@ export default { ...@@ -227,7 +227,7 @@ export default {
}, },
}, },
created() { created() {
const params = queryToObject(window.location.search); const params = queryToObject(window.location.search, { legacySpacesDecode: true });
this.selectedSiteProfileId = params.site_profile_id this.selectedSiteProfileId = params.site_profile_id
? convertToGraphQLId(TYPE_SITE_PROFILE, params.site_profile_id) ? convertToGraphQLId(TYPE_SITE_PROFILE, params.site_profile_id)
......
...@@ -650,45 +650,24 @@ describe('URL utility', () => { ...@@ -650,45 +650,24 @@ describe('URL utility', () => {
}); });
describe('queryToObject', () => { describe('queryToObject', () => {
it('converts search query into an object', () => { it.each`
const searchQuery = '?one=1&two=2'; case | query | options | result
${'converts query'} | ${'?one=1&two=2'} | ${undefined} | ${{ one: '1', two: '2' }}
expect(urlUtils.queryToObject(searchQuery)).toEqual({ one: '1', two: '2' }); ${'converts query without ?'} | ${'one=1&two=2'} | ${undefined} | ${{ one: '1', two: '2' }}
}); ${'removes undefined values'} | ${'?one=1&two=2&three'} | ${undefined} | ${{ one: '1', two: '2' }}
${'overwrites values with same key and does not change key'} | ${'?one[]=1&one[]=2&two=2&two=3'} | ${undefined} | ${{ 'one[]': '2', two: '3' }}
it('removes undefined values from the search query', () => { ${'gathers values with the same array-key, strips `[]` from key'} | ${'?one[]=1&one[]=2&two=2&two=3'} | ${{ gatherArrays: true }} | ${{ one: ['1', '2'], two: '3' }}
const searchQuery = '?one=1&two=2&three'; ${'overwrites values with the same array-key name'} | ${'?one=1&one[]=2&two=2&two=3'} | ${{ gatherArrays: true }} | ${{ one: ['2'], two: '3' }}
${'overwrites values with the same key name'} | ${'?one[]=1&one=2&two=2&two=3'} | ${{ gatherArrays: true }} | ${{ one: '2', two: '3' }}
expect(urlUtils.queryToObject(searchQuery)).toEqual({ one: '1', two: '2' }); ${'ignores plus symbols'} | ${'?search=a+b'} | ${{ legacySpacesDecode: true }} | ${{ search: 'a+b' }}
}); ${'ignores plus symbols in keys'} | ${'?search+term=a'} | ${{ legacySpacesDecode: true }} | ${{ 'search+term': 'a' }}
${'ignores plus symbols when gathering arrays'} | ${'?search[]=a+b'} | ${{ gatherArrays: true, legacySpacesDecode: true }} | ${{ search: ['a+b'] }}
describe('with gatherArrays=false', () => { ${'replaces plus symbols with spaces'} | ${'?search=a+b'} | ${undefined} | ${{ search: 'a b' }}
it('overwrites values with the same array-key and does not change the key', () => { ${'replaces plus symbols in keys with spaces'} | ${'?search+term=a'} | ${undefined} | ${{ 'search term': 'a' }}
const searchQuery = '?one[]=1&one[]=2&two=2&two=3'; ${'replaces plus symbols when gathering arrays'} | ${'?search[]=a+b'} | ${{ gatherArrays: true }} | ${{ search: ['a b'] }}
${'replaces plus symbols when gathering arrays for values with same key'} | ${'?search[]=a+b&search[]=c+d'} | ${{ gatherArrays: true }} | ${{ search: ['a b', 'c d'] }}
expect(urlUtils.queryToObject(searchQuery)).toEqual({ 'one[]': '2', two: '3' }); `('$case', ({ query, options, result }) => {
}); expect(urlUtils.queryToObject(query, options)).toEqual(result);
});
describe('with gatherArrays=true', () => {
const options = { gatherArrays: true };
it('gathers only values with the same array-key and strips `[]` from the key', () => {
const searchQuery = '?one[]=1&one[]=2&two=2&two=3';
expect(urlUtils.queryToObject(searchQuery, options)).toEqual({ one: ['1', '2'], two: '3' });
});
it('overwrites values with the same array-key name', () => {
const searchQuery = '?one=1&one[]=2&two=2&two=3';
expect(urlUtils.queryToObject(searchQuery, options)).toEqual({ one: ['2'], two: '3' });
});
it('overwrites values with the same key name', () => {
const searchQuery = '?one[]=1&one=2&two=2&two=3';
expect(urlUtils.queryToObject(searchQuery, options)).toEqual({ one: '2', two: '3' });
});
}); });
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment