Commit 9874b6bd authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '328845-move-urlParamsToObject-to-url-utils' into 'master'

Move to urlParamsToObject to url_utility.js and deprecate it

See merge request gitlab-org/gitlab!64386
parents 6c2f4921 724ada81
......@@ -18,7 +18,9 @@ import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create
import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { convertObjectPropsToCamelCase, urlParamsToObject } from '~/lib/utils/common_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { urlParamsToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import {
formatBoardLists,
......@@ -74,6 +76,7 @@ export default {
performSearch({ dispatch }) {
dispatch(
'setFilters',
// eslint-disable-next-line import/no-deprecated
convertObjectPropsToCamelCase(urlParamsToObject(window.location.search)),
);
......
......@@ -7,13 +7,9 @@ import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import createDefaultClient from '~/lib/graphql';
import axios from '~/lib/utils/axios_utils';
import {
urlParamsToObject,
getUrlParamsArray,
parseBoolean,
convertObjectPropsToCamelCase,
} from '~/lib/utils/common_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { mergeUrlParams, urlParamsToObject, getUrlParamsArray } from '~/lib/utils/url_utility';
import { ListType, flashAnimationDuration } from '../constants';
import eventHub from '../eventhub';
import ListAssignee from '../models/assignee';
......@@ -601,6 +597,7 @@ const boardsStore = {
getListIssues(list, emptyIssues = true) {
const data = {
// eslint-disable-next-line import/no-deprecated
...urlParamsToObject(this.filter.path),
page: list.page,
};
......
import { last } from 'lodash';
import recentSearchesStorageKeys from 'ee_else_ce/filtered_search/recent_searches_storage_keys';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { getParameterByName, getUrlParamsArray } from '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/common_utils';
import {
ENTER_KEY_CODE,
BACKSPACE_KEY_CODE,
......@@ -12,7 +12,7 @@ import {
import { __ } from '~/locale';
import createFlash from '../flash';
import { addClassIfElementExists } from '../lib/utils/dom_utils';
import { visitUrl } from '../lib/utils/url_utility';
import { visitUrl, getUrlParamsArray } from '../lib/utils/url_utility';
import FilteredSearchContainer from './container';
import DropdownUtils from './dropdown_utils';
import eventHub from './event_hub';
......
......@@ -8,13 +8,9 @@ import {
import { toNumber, omit } from 'lodash';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import {
scrollToElement,
urlParamsToObject,
historyPushState,
getParameterByName,
} from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import { scrollToElement, historyPushState, getParameterByName } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { setUrlParams, urlParamsToObject } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import initManualOrdering from '~/manual_ordering';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
......@@ -271,6 +267,7 @@ export default {
});
},
getQueryObject() {
// eslint-disable-next-line import/no-deprecated
return urlParamsToObject(window.location.search);
},
onPaginate(newPage) {
......
......@@ -162,53 +162,6 @@ export const parseUrlPathname = (url) => {
return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : `/${parsedUrl.pathname}`;
};
const splitPath = (path = '') => path.replace(/^\?/, '').split('&');
export const urlParamsToArray = (path = '') =>
splitPath(path)
.filter((param) => param.length > 0)
.map((param) => {
const split = param.split('=');
return [decodeURI(split[0]), split[1]].join('=');
});
export const getUrlParamsArray = () => urlParamsToArray(window.location.search);
/**
* Accepts encoding string which includes query params being
* sent to URL.
*
* @param {string} path Query param string
*
* @returns {object} Query params object containing key-value pairs
* with both key and values decoded into plain string.
*/
export const urlParamsToObject = (path = '') =>
splitPath(path).reduce((dataParam, filterParam) => {
if (filterParam === '') {
return dataParam;
}
const data = dataParam;
let [key, value] = filterParam.split('=');
key = /%\w+/g.test(key) ? decodeURIComponent(key) : key;
const isArray = key.includes('[]');
key = key.replace('[]', '');
value = decodeURIComponent(value.replace(/\+/g, ' '));
if (isArray) {
if (!data[key]) {
data[key] = [];
}
data[key].push(value);
} else {
data[key] = value;
}
return data;
}, {});
export const isMetaKey = (e) => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
// Identify following special clicks
......
......@@ -409,6 +409,55 @@ export function getWebSocketUrl(path) {
return `${getWebSocketProtocol()}//${joinPaths(window.location.host, path)}`;
}
const splitPath = (path = '') => path.replace(/^\?/, '').split('&');
export const urlParamsToArray = (path = '') =>
splitPath(path)
.filter((param) => param.length > 0)
.map((param) => {
const split = param.split('=');
return [decodeURI(split[0]), split[1]].join('=');
});
export const getUrlParamsArray = () => urlParamsToArray(window.location.search);
/**
* Accepts encoding string which includes query params being
* sent to URL.
*
* @param {string} path Query param string
*
* @returns {object} Query params object containing key-value pairs
* with both key and values decoded into plain string.
*
* @deprecated Please use `queryToObject(query, { gatherArrays: true });` instead. See https://gitlab.com/gitlab-org/gitlab/-/issues/328845
*/
export const urlParamsToObject = (path = '') =>
splitPath(path).reduce((dataParam, filterParam) => {
if (filterParam === '') {
return dataParam;
}
const data = dataParam;
let [key, value] = filterParam.split('=');
key = /%\w+/g.test(key) ? decodeURIComponent(key) : key;
const isArray = key.includes('[]');
key = key.replace('[]', '');
value = decodeURIComponent(value.replace(/\+/g, ' '));
if (isArray) {
if (!data[key]) {
data[key] = [];
}
data[key].push(value);
} else {
data[key] = value;
}
return data;
}, {});
/**
* Convert search query into an object
*
......
<script>
import { GlFilteredSearchToken } from '@gitlab/ui';
import { mapState } from 'vuex';
import { getParameterByName, urlParamsToObject } from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import { getParameterByName } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { setUrlParams, urlParamsToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import { SEARCH_TOKEN_TYPE, SORT_PARAM } from '~/members/constants';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
......@@ -64,6 +65,7 @@ export default {
},
},
created() {
// eslint-disable-next-line import/no-deprecated
const query = urlParamsToObject(window.location.search);
const tokens = this.tokens
......
<script>
import { GlTabs, GlTab, GlBadge } from '@gitlab/ui';
import { mapState } from 'vuex';
import { urlParamsToObject } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { urlParamsToObject } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { MEMBER_TYPES } from '../constants';
import MembersApp from './app.vue';
......@@ -55,6 +56,7 @@ export default {
},
}),
urlParams() {
// eslint-disable-next-line import/no-deprecated
return Object.keys(urlParamsToObject(window.location.search));
},
activeTabIndexCalculatedFromUrlParams() {
......
......@@ -9,8 +9,8 @@ import {
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { mapState, mapActions } from 'vuex';
import { urlParamsToObject } from '~/lib/utils/common_utils';
import { redirectTo } from '~/lib/utils/url_utility';
// eslint-disable-next-line import/no-deprecated
import { redirectTo, urlParamsToObject } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
const tooltipMessage = __('Searching by both author and message is currently not supported.');
......@@ -52,6 +52,7 @@ export default {
},
mounted() {
this.fetchAuthors();
// eslint-disable-next-line import/no-deprecated
const params = urlParamsToObject(window.location.search);
const { search: searchParam, author: authorParam } = params;
const commitsSearchInput = this.projectCommitsEl.querySelector('#commits-search');
......
import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
import { urlParamsToObject } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { urlParamsToObject } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import CodeReviewAnalyticsFilteredSearchTokenKeys from './code_review_analytics_filtered_search_token_keys';
import store from './store';
......@@ -24,6 +25,7 @@ export default class FilteredSearchCodeReviewAnalytics extends FilteredSearchMan
* Updates filters in code review analytics store
*/
updateObject = (path) => {
// eslint-disable-next-line import/no-deprecated
const filters = urlParamsToObject(path);
const { selectedLabels: selectedLabelList, selectedMilestone } = transformFilters(filters);
......
import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
import { urlParamsToObject } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { urlParamsToObject } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import ProductivityAnalyticsFilteredSearchTokenKeys from './productivity_analytics_filtered_search_token_keys';
import store from './store';
......@@ -23,6 +24,7 @@ export default class FilteredSearchProductivityAnalytics extends FilteredSearchM
* Updates filters in productivity analytics store
*/
updateObject = (path) => {
// eslint-disable-next-line import/no-deprecated
const filters = urlParamsToObject(path);
store.dispatch('filters/setFilters', filters);
};
......
import { FiltersInfo as FiltersInfoCE } from '~/boards/boards_util';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { urlParamsToObject } from '~/lib/utils/common_utils';
import { objectToQuery } from '~/lib/utils/url_utility';
// eslint-disable-next-line import/no-deprecated
import { objectToQuery, urlParamsToObject } from '~/lib/utils/url_utility';
import {
EPIC_LANE_BASE_HEIGHT,
IterationFilterType,
......@@ -93,6 +93,7 @@ export function formatEpicListsPageInfo(lists) {
export function transformBoardConfig(boardConfig) {
const updatedBoardConfig = {};
// eslint-disable-next-line import/no-deprecated
const passedFilterParams = urlParamsToObject(window.location.search);
const updateScopeObject = (key, value = '') => {
if (value === null || value === '') return;
......
import Vue from 'vue';
import EpicFilteredSearch from 'ee_component/boards/components/epic_filtered_search.vue';
import store from '~/boards/stores';
import { urlParamsToObject, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { urlParamsToObject } from '~/lib/utils/url_utility';
export default (apolloProvider) => {
const el = document.getElementById('js-board-filtered-search');
// eslint-disable-next-line import/no-deprecated
const rawFilterParams = urlParamsToObject(window.location.search);
const initialFilterParams = {
...convertObjectPropsToCamelCase(rawFilterParams, {}),
......
......@@ -15,12 +15,9 @@ import boardsStore from '~/boards/stores/boards_store';
import * as typesCE from '~/boards/stores/mutation_types';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import axios from '~/lib/utils/axios_utils';
import {
historyPushState,
convertObjectPropsToCamelCase,
urlParamsToObject,
} from '~/lib/utils/common_utils';
import { mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
import { historyPushState, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { mergeUrlParams, removeParams, urlParamsToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import {
fullEpicId,
......@@ -123,6 +120,7 @@ export default {
performSearch({ dispatch, getters }) {
dispatch(
'setFilters',
// eslint-disable-next-line import/no-deprecated
convertObjectPropsToCamelCase(urlParamsToObject(window.location.search)),
);
......
......@@ -3,11 +3,9 @@ import VueApollo from 'vue-apollo';
import { IssuableStates } from '~/issuable_list/constants';
import createDefaultClient from '~/lib/graphql';
import {
urlParamsToObject,
parseBoolean,
convertObjectPropsToCamelCase,
} from '~/lib/utils/common_utils';
import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { urlParamsToObject } from '~/lib/utils/url_utility';
import EpicsListApp from './components/epics_list_root.vue';
......@@ -43,6 +41,7 @@ export default function initEpicsList({ mountPointSelector }) {
emptyStatePath,
} = mountPointEl.dataset;
// eslint-disable-next-line import/no-deprecated
const rawFilterParams = urlParamsToObject(window.location.search);
const initialFilterParams = {
...convertObjectPropsToCamelCase(rawFilterParams, {
......
import Vue from 'vue';
import { IssuableStates } from '~/issuable_list/constants';
import { urlParamsToObject, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { urlParamsToObject } from '~/lib/utils/url_utility';
import JiraIssuesListApp from './components/jira_issues_list_root.vue';
import apolloProvider from './graphql';
......@@ -20,6 +22,7 @@ export default function initJiraIssuesList({ mountPointSelector }) {
} = mountPointEl.dataset;
const initialFilterParams = Object.assign(
// eslint-disable-next-line import/no-deprecated
convertObjectPropsToCamelCase(urlParamsToObject(window.location.search.substring(1)), {
dropKeys: ['scope', 'utf8', 'state', 'sort'], // These keys are unsupported/unnecessary
}),
......
import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys';
import { historyPushState, urlParamsToObject } from '~/lib/utils/common_utils';
import { historyPushState } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { urlParamsToObject } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import issueAnalyticsStore from './stores';
......@@ -35,6 +37,7 @@ export default class FilteredSearchIssueAnalytics extends FilteredSearchManager
updateObject = (path) => {
historyPushState(path);
// eslint-disable-next-line import/no-deprecated
const filters = urlParamsToObject(path);
issueAnalyticsStore.dispatch('issueAnalytics/setFilters', filters);
};
......
import Vue from 'vue';
import { urlParamsToObject } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { urlParamsToObject } from '~/lib/utils/url_utility';
import IssuesAnalytics from './components/issues_analytics.vue';
import store from './stores';
......@@ -18,6 +19,7 @@ export default () => {
} = el.dataset;
// Set default filters from URL
// eslint-disable-next-line import/no-deprecated
const filters = urlParamsToObject(window.location.search);
store.dispatch('issueAnalytics/setFilters', filters);
......
......@@ -3,8 +3,8 @@ import { GlPagination, GlAlert } from '@gitlab/ui';
import Api from '~/api';
import createFlash, { FLASH_TYPES } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { urlParamsToObject } from '~/lib/utils/common_utils';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
// eslint-disable-next-line import/no-deprecated
import { updateHistory, setUrlParams, urlParamsToObject } from '~/lib/utils/url_utility';
import { __, sprintf } from '~/locale';
import Tracking from '~/tracking';
......@@ -331,6 +331,7 @@ export default {
*/
updateUrl() {
const { href, search } = window.location;
// eslint-disable-next-line import/no-deprecated
const queryParams = urlParamsToObject(search);
const {
filterBy,
......
import Vue from 'vue';
import { mapActions } from 'vuex';
import {
parseBoolean,
urlParamsToObject,
convertObjectPropsToCamelCase,
} from '~/lib/utils/common_utils';
import { visitUrl, mergeUrlParams } from '~/lib/utils/url_utility';
import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { visitUrl, mergeUrlParams, urlParamsToObject } from '~/lib/utils/url_utility';
import Translate from '~/vue_shared/translate';
import EpicItem from './components/epic_item.vue';
......@@ -67,6 +64,7 @@ export default () => {
supportedPresetTypes.indexOf(dataset.presetType) > -1
? dataset.presetType
: PRESET_TYPES.MONTHS;
// eslint-disable-next-line import/no-deprecated
const rawFilterParams = urlParamsToObject(window.location.search.substring(1));
const filterParams = {
...convertObjectPropsToCamelCase(rawFilterParams, {
......
......@@ -5,8 +5,8 @@ import Api from '~/api';
import createFlash from '~/flash';
import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import axios from '~/lib/utils/axios_utils';
import { urlParamsToObject } from '~/lib/utils/common_utils';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
// eslint-disable-next-line import/no-deprecated
import { updateHistory, setUrlParams, urlParamsToObject } from '~/lib/utils/url_utility';
import { s__, __ } from '~/locale';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
......@@ -143,6 +143,7 @@ export default {
},
methods: {
updateUrl() {
// eslint-disable-next-line import/no-deprecated
const queryParams = urlParamsToObject(window.location.search);
const { authorUsername, labelName, search } = this.filterParams || {};
const { currentState, sortedBy, currentPage, prevPageCursor, nextPageCursor } = this;
......
......@@ -2,11 +2,9 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import {
urlParamsToObject,
convertObjectPropsToCamelCase,
parseBoolean,
} from '~/lib/utils/common_utils';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { urlParamsToObject } from '~/lib/utils/url_utility';
import TestCaseListApp from './components/test_case_list_root.vue';
......@@ -35,6 +33,7 @@ const initTestCaseList = ({ mountPointSelector }) => {
} = mountPointEl.dataset;
const initialFilterParams = Object.assign(
// eslint-disable-next-line import/no-deprecated
convertObjectPropsToCamelCase(urlParamsToObject(window.location.search.substring(1)), {
dropKeys: ['scope', 'utf8', 'state', 'sort'], // These keys are unsupported/unnecessary
}),
......
......@@ -26,42 +26,6 @@ describe('common_utils', () => {
});
});
describe('urlParamsToArray', () => {
it('returns empty array for empty querystring', () => {
expect(commonUtils.urlParamsToArray('')).toEqual([]);
});
it('should decode params', () => {
expect(commonUtils.urlParamsToArray('?label_name%5B%5D=test')[0]).toBe('label_name[]=test');
});
it('should remove the question mark from the search params', () => {
const paramsArray = commonUtils.urlParamsToArray('?test=thing');
expect(paramsArray[0][0]).not.toBe('?');
});
});
describe('urlParamsToObject', () => {
it('parses path for label with trailing +', () => {
expect(commonUtils.urlParamsToObject('label_name[]=label%2B', {})).toEqual({
label_name: ['label+'],
});
});
it('parses path for milestone with trailing +', () => {
expect(commonUtils.urlParamsToObject('milestone_title=A%2B', {})).toEqual({
milestone_title: 'A+',
});
});
it('parses path for search terms with spaces', () => {
expect(commonUtils.urlParamsToObject('search=two+words', {})).toEqual({
search: 'two words',
});
});
});
describe('handleLocationHash', () => {
beforeEach(() => {
jest.spyOn(window.document, 'getElementById');
......
......@@ -650,6 +650,45 @@ describe('URL utility', () => {
});
});
describe('urlParamsToArray', () => {
it('returns empty array for empty querystring', () => {
expect(urlUtils.urlParamsToArray('')).toEqual([]);
});
it('should decode params', () => {
expect(urlUtils.urlParamsToArray('?label_name%5B%5D=test')[0]).toBe('label_name[]=test');
});
it('should remove the question mark from the search params', () => {
const paramsArray = urlUtils.urlParamsToArray('?test=thing');
expect(paramsArray[0][0]).not.toBe('?');
});
});
describe('urlParamsToObject', () => {
it('parses path for label with trailing +', () => {
// eslint-disable-next-line import/no-deprecated
expect(urlUtils.urlParamsToObject('label_name[]=label%2B', {})).toEqual({
label_name: ['label+'],
});
});
it('parses path for milestone with trailing +', () => {
// eslint-disable-next-line import/no-deprecated
expect(urlUtils.urlParamsToObject('milestone_title=A%2B', {})).toEqual({
milestone_title: 'A+',
});
});
it('parses path for search terms with spaces', () => {
// eslint-disable-next-line import/no-deprecated
expect(urlUtils.urlParamsToObject('search=two+words', {})).toEqual({
search: 'two words',
});
});
});
describe('queryToObject', () => {
it.each`
case | query | options | result
......
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