diff --git a/public/app/core/utils/url.ts b/public/app/core/utils/url.ts new file mode 100644 index 00000000000..0b629768b92 --- /dev/null +++ b/public/app/core/utils/url.ts @@ -0,0 +1,52 @@ +/** + * @preserve jquery-param (c) 2015 KNOWLEDGECODE | MIT + */ + +export function toUrlParams(a) { + let s = []; + let rbracket = /\[\]$/; + + let isArray = function(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; + + let add = function(k, v) { + v = typeof v === 'function' ? v() : v === null ? '' : v === undefined ? '' : v; + s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v); + }; + + let buildParams = function(prefix, obj) { + var i, len, key; + + if (prefix) { + if (isArray(obj)) { + for (i = 0, len = obj.length; i < len; i++) { + if (rbracket.test(prefix)) { + add(prefix, obj[i]); + } else { + buildParams(prefix, obj[i]); + } + } + } else if (obj && String(obj) === '[object Object]') { + for (key in obj) { + buildParams(prefix + '[' + key + ']', obj[key]); + } + } else { + add(prefix, obj); + } + } else if (isArray(obj)) { + for (i = 0, len = obj.length; i < len; i++) { + add(obj[i].name, obj[i].value); + } + } else { + for (key in obj) { + buildParams(key, obj[key]); + } + } + return s; + }; + + return buildParams('', a) + .join('&') + .replace(/%20/g, '+'); +} diff --git a/public/app/stores/ViewStore/ViewStore.jest.ts b/public/app/stores/ViewStore/ViewStore.jest.ts new file mode 100644 index 00000000000..ddf3515532b --- /dev/null +++ b/public/app/stores/ViewStore/ViewStore.jest.ts @@ -0,0 +1,26 @@ +import { ViewStore } from './ViewStore'; +import { toJS } from 'mobx'; + +describe('ViewStore', () => { + let store; + + beforeAll(() => { + store = ViewStore.create({ + path: '', + query: {}, + }); + }); + + it('Can update path and query', () => { + store.updatePathAndQuery('/hello', { key: 1, otherParam: 'asd' }); + expect(store.path).toBe('/hello'); + expect(store.query.get('key')).toBe(1); + expect(store.currentUrl).toBe('/hello?key=1&otherParam=asd'); + }); + + it('Query can contain arrays', () => { + store.updatePathAndQuery('/hello', { values: ['A', 'B'] }); + expect(store.query.get('values').toJS()).toMatchObject(['A', 'B']); + expect(store.currentUrl).toBe('/hello?values=A&values=B'); + }); +}); diff --git a/public/app/stores/ViewStore/ViewStore.ts b/public/app/stores/ViewStore/ViewStore.ts index fb1111cf108..082e3257497 100644 --- a/public/app/stores/ViewStore/ViewStore.ts +++ b/public/app/stores/ViewStore/ViewStore.ts @@ -1,15 +1,9 @@ import { types } from 'mobx-state-tree'; +import { toJS } from 'mobx'; +import { toUrlParams } from 'app/core/utils/url'; -const QueryValueType = types.union(types.string, types.boolean, types.number); -const urlParameterize = queryObj => { - const keys = Object.keys(queryObj); - const newQuery = keys.reduce((acc: string, key: string, idx: number) => { - const preChar = idx === 0 ? '?' : '&'; - return acc + preChar + key + '=' + queryObj[key]; - }, ''); - - return newQuery; -}; +const QueryInnerValueType = types.union(types.string, types.boolean, types.number); +const QueryValueType = types.union(QueryInnerValueType, types.array(QueryInnerValueType)); export const ViewStore = types .model({ @@ -21,7 +15,7 @@ export const ViewStore = types let path = self.path; if (self.query.size) { - path += urlParameterize(self.query.toJS()); + path += '?' + toUrlParams(toJS(self.query)); } return path; }, diff --git a/yarn.lock b/yarn.lock index feba691190e..0cafa601d1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7442,7 +7442,7 @@ performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" -phantomjs-prebuilt@^2.1.15, phantomjs-prebuilt@^2.1.7: +phantomjs-prebuilt@^2.1.16, phantomjs-prebuilt@^2.1.7: version "2.1.16" resolved "https://registry.yarnpkg.com/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz#efd212a4a3966d3647684ea8ba788549be2aefef" dependencies: