Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encapsulate query strings #3687

Closed
wants to merge 11 commits into from
5 changes: 3 additions & 2 deletions client/app/components/parameters.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { extend } from 'lodash';
import template from './parameters.html';
import qs from '@/services/query-string';
import EditParameterSettingsDialog from './EditParameterSettingsDialog';

function ParametersDirective($location) {
Expand All @@ -23,11 +24,11 @@ function ParametersDirective($location) {
if (scope.changed) {
scope.changed({});
}
const params = extend({}, $location.search());
const params = qs.fromUrl();
scope.parameters.forEach((param) => {
extend(params, param.toUrlParams());
});
$location.search(params);
$location.search(qs.toString(params));
},
true,
);
Expand Down
2 changes: 1 addition & 1 deletion client/app/components/queries/visualization-embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function init(ngModule) {
return session($http, $route, Auth).then(() => {
const queryId = $route.current.params.queryId;
const query = $http.get(`api/queries/${queryId}`).then(response => response.data);
const queryResult = $http.post(`api/queries/${queryId}/results`, { parameters: queryStringParameters() }).then(response => response.data);
const queryResult = $http.post(`api/queries/${queryId}/results`, { parameters: queryStringParameters.fromUrl() }).then(response => response.data);
return $q.all([query, queryResult]);
});
}
Expand Down
26 changes: 22 additions & 4 deletions client/app/services/query-string.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import qs from 'qs';

const parse = queryString => qs.parse(queryString, { allowDots: true, ignoreQueryPrefix: true });
const onlyParameters = ([k]) => k.startsWith('p_');
const removePrefix = ([k, v]) => [k.slice(2), v];
const prefix = 'p_';
const options = { allowDots: true, ignoreQueryPrefix: true };

const parse = queryString => qs.parse(queryString, options);
const onlyParameters = ([k]) => k.startsWith(prefix);
const removePrefix = ([k, v]) => [k.slice(prefix.length), v];
const addPrefix = ([k, v]) => [`${prefix}${k}`, v];
const toObject = (obj, [k, v]) => Object.assign(obj, { [k]: v });

export default () => Object.entries(parse(location.search))
const fromString = queryString => Object.entries(parse(queryString))
.filter(onlyParameters)
.map(removePrefix)
.reduce(toObject, {});

const fromUrl = () => fromString(location.search);

const toString = obj => qs.stringify(
Object.entries(obj)
.map(addPrefix)
.reduce(toObject, {}), options,
);

export default {
fromString,
fromUrl,
toString,
};
39 changes: 39 additions & 0 deletions client/app/services/query-string.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import queryString from '@/services/query-string';

describe('fromString', () => {
it('removes prefixes', () => {
expect(queryString.fromString('p_a=b'))
.toHaveProperty('a');
});

it('ignores non-parameters', () => {
expect(queryString.fromString('p_a=b&utm=123'))
.not.toHaveProperty('utm');
});

it('handles date ranges', () => {
expect(queryString.fromString('p_created_at.start=2019-01-01&p_created_at.end=2019-02-01')).toEqual({
created_at: {
start: '2019-01-01',
end: '2019-02-01',
},
});
});
});

describe('toString', () => {
it('adds prefixes', () => {
expect(queryString.toString({
a: 'b',
})).toEqual('p_a=b');
});

it('handles date ranges', () => {
expect(queryString.toString({
created_at: {
start: '2019-01-01',
end: '2019-02-01',
},
})).toEqual('p_created_at.start=2019-01-01&p_created_at.end=2019-02-01');
});
});
35 changes: 5 additions & 30 deletions client/app/services/query.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import moment from 'moment';
import debug from 'debug';
import qs from '@/services/query-string';
import Mustache from 'mustache';
import {
zipObject, isEmpty, map, filter, includes, union, uniq, has,
isNull, isUndefined, isArray, isObject, identity, extend, each,
isNull, isUndefined, isArray, isObject, identity, each,
} from 'lodash';

Mustache.escape = identity; // do not html-escape values
Expand Down Expand Up @@ -164,19 +165,7 @@ export class Parameter {
}

toUrlParams() {
if (this.isEmpty) {
return {};
}
const prefix = this.urlPrefix;
if (isDateRangeParameter(this.type)) {
return {
[`${prefix}${this.name}.start`]: this.value.start,
[`${prefix}${this.name}.end`]: this.value.end,
};
}
return {
[`${prefix}${this.name}`]: this.value,
};
return this.isEmpty ? {} : { [this.name]: this.value };
}

fromUrlParams(query) {
Expand Down Expand Up @@ -296,15 +285,7 @@ class Parameters {
}

toUrlParams() {
if (this.get().length === 0) {
return '';
}

const params = Object.assign(...this.get().map(p => p.toUrlParams()));
return Object
.keys(params)
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
.join('&');
return qs.toString(this.getValues());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to use param.toUrlParams() instead of mapping name => value directly - it provides some layer of abstraction for a case when param will need some different serialization logic in future.

Copy link
Contributor Author

@rauchy rauchy Apr 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I'm trying to achieve with this refactoring is moving out the serialization logic from Parameter/Parameters and into the query string module. The approach here is that a query string is itself the serialization "view", thus the logic resides there and the model simply reveals its data.

}
}

Expand Down Expand Up @@ -546,13 +527,7 @@ function QueryResource(
url += '/source';
}

let params = {};
if (this.getParameters().isRequired()) {
this.getParametersDefs().forEach((param) => {
extend(params, param.toUrlParams());
});
}
params = map(params, (value, name) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`).join('&');
const params = this.getParameters().toUrlParams();

if (params !== '') {
url += `?${params}`;
Expand Down