Skip to content

Commit

Permalink
Merge pull request googleanalytics#181 from googleanalytics/site-search
Browse files Browse the repository at this point in the history
Add a queryParamsWhitelist option
  • Loading branch information
philipwalton authored Jun 2, 2017
2 parents bdbc90a + 55c6d0e commit 74308ac
Show file tree
Hide file tree
Showing 5 changed files with 459 additions and 158 deletions.
27 changes: 27 additions & 0 deletions docs/plugins/clean-url-tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ The following table outlines all possible configuration options for the `cleanUr
<strong>Default:</strong> <code>false</code>
</td>
</tr>
<tr valign="top">
<td><code>queryParamsWhitelist</code></td>
<td><code>Array</code></td>
<td>
An array of query params not to strip. This is most commonly used in conjunction with site search, as shown in the <a href=""><code>queryParamsWhitelist</code> example</a> below.
</td>
</tr>
<tr valign="top">
<td><code>queryDimensionIndex</code></td>
<td><code>number</code></td>
Expand Down Expand Up @@ -154,6 +161,26 @@ And given those four URLs, the following fields would be sent to Google Analytic
}
```

### Using the `queryParamsWhitelist` option

Unlike campaign (e.g. `utm` params) and adwords (e.g. `glclid`) data, [Site Search](https://support.google.com/analytics/answer/1012264) data is not inferred by Google Analytics from the `location` field when the `page` field is present, so any site search query params *must not* be stripped from the `page` field.

You can preserve individual query params via the `queryParamsWhitelist` option:

```js
ga('require', 'cleanUrlTracker', {
stripQuery: true,
queryParamsWhitelist: ['q'],
});
```

Note that *not* stripping site search params from your URLs means those params will still show up in your page reports. If you don't want this to happen you can update your view's [Site Search setup](https://support.google.com/analytics/answer/1012264) as follows:

1. Specify the same parameter(s) you set in the `queryParamsWhitelist` option.
2. Check the "Strip query parameters out of URL" box.

These options combined will allow you to keep all unwanted query params out of your page reports and still use site search.

### Using the `urlFieldsFilter` option

If the available configuration options are not sufficient for your needs, you can use the `urlFieldsFilter` option to arbirarily modify the URL fields sent to Google Analytics.
Expand Down
1 change: 1 addition & 0 deletions lib/externs/clean-url-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Public options for the CleanUrlTracker.
* @typedef {{
* stripQuery: (boolean|undefined),
* queryParamsWhitelist: (Array|undefined),
* queryDimensionIndex: (number|undefined),
* indexFilename: (string|undefined),
* trailingSlash: (string|undefined),
Expand Down
35 changes: 32 additions & 3 deletions lib/plugins/clean-url-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class CleanUrlTracker {
/** @type {CleanUrlTrackerOpts} */
const defaultOpts = {
// stripQuery: undefined,
// queryParamsWhitelist: undefined,
// queryDimensionIndex: undefined,
// indexFilename: undefined,
// trailingSlash: undefined,
Expand Down Expand Up @@ -140,7 +141,8 @@ class CleanUrlTracker {

/** @type {!FieldsObj} */
const cleanedFieldsObj = {
page: pathname + (!this.opts.stripQuery ? url.search : ''),
page: pathname + (this.opts.stripQuery ?
this.stripNonWhitelistedQueryParams(url.search) : url.search),
};
if (fieldsObj.location) {
cleanedFieldsObj.location = fieldsObj.location;
Expand All @@ -157,16 +159,43 @@ class CleanUrlTracker {
this.opts.urlFieldsFilter(cleanedFieldsObj, parseUrl);

// Ensure only the URL fields are returned.
return {
const returnValue = {
page: userCleanedFieldsObj.page,
location: userCleanedFieldsObj.location,
[this.queryDimension]: userCleanedFieldsObj[this.queryDimension],
};
if (this.queryDimension) {
returnValue[this.queryDimension] =
userCleanedFieldsObj[this.queryDimension];
}
return returnValue;
} else {
return cleanedFieldsObj;
}
}

/**
* Accpets a raw URL search string and returns a new search string containing
* only the site search params (if they exist).
* @param {string} searchString The URL search string (starting with '?').
* @return {string} The query string
*/
stripNonWhitelistedQueryParams(searchString) {
if (Array.isArray(this.opts.queryParamsWhitelist)) {
const foundParams = [];
searchString.slice(1).split('&').forEach((kv) => {
const [key, value] = kv.split('=');
if (this.opts.queryParamsWhitelist.indexOf(key) > -1 && value) {
foundParams.push([key, value]);
}
});

return foundParams.length ?
'?' + foundParams.map((kv) => kv.join('=')).join('&') : '';
} else {
return '';
}
}

/**
* Restores all overridden tasks and methods.
*/
Expand Down
157 changes: 2 additions & 155 deletions test/e2e/clean-url-tracker-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,68 +66,6 @@ describe('cleanUrlTracker', function() {
assert.strictEqual(hits[0].dp, '/foo/bar?q=qux&b=baz');
});

it('supports removing the query string from the URL path', () => {
const url = 'https://example.com/foo/bar?q=qux&b=baz#hash';
browser.execute(ga.run, 'set', 'location', url);
browser.execute(ga.run, 'require', 'cleanUrlTracker', {
stripQuery: true,
});
browser.execute(ga.run, 'send', 'pageview');
browser.waitUntil(log.hitCountEquals(1));

const hits = log.getHits();
assert.strictEqual(hits[0].dl, url);
assert.strictEqual(hits[0].dp, '/foo/bar');
});

it('optionally adds the query string as a custom dimension', () => {
const url = 'https://example.com/foo/bar?q=qux&b=baz#hash';
browser.execute(ga.run, 'set', 'location', url);
browser.execute(ga.run, 'require', 'cleanUrlTracker', {
stripQuery: true,
queryDimensionIndex: 1,
});
browser.execute(ga.run, 'send', 'pageview');
browser.waitUntil(log.hitCountEquals(1));

const hits = log.getHits();
assert.strictEqual(hits[0].dl, url);
assert.strictEqual(hits[0].dp, '/foo/bar');
assert.strictEqual(hits[0].cd1, 'q=qux&b=baz');
});

it('adds the null dimensions when no query string is found', () => {
const url = 'https://example.com/foo/bar';
browser.execute(ga.run, 'set', 'location', url);
browser.execute(ga.run, 'require', 'cleanUrlTracker', {
stripQuery: true,
queryDimensionIndex: 1,
});
browser.execute(ga.run, 'send', 'pageview');
browser.waitUntil(log.hitCountEquals(1));

const hits = log.getHits();
assert.strictEqual(hits[0].dl, url);
assert.strictEqual(hits[0].dp, '/foo/bar');
assert.strictEqual(hits[0].cd1, constants.NULL_DIMENSION);
});

it('does not set a dimension if strip query is false', () => {
const url = 'https://example.com/foo/bar?q=qux&b=baz#hash';
browser.execute(ga.run, 'set', 'location', url);
browser.execute(ga.run, 'require', 'cleanUrlTracker', {
stripQuery: false,
queryDimensionIndex: 1,
});
browser.execute(ga.run, 'send', 'pageview');
browser.waitUntil(log.hitCountEquals(1));

const hits = log.getHits();
assert.strictEqual(hits[0].dl, url);
assert.strictEqual(hits[0].dp, '/foo/bar?q=qux&b=baz');
assert.strictEqual(hits[0].cd1, undefined);
});

it('cleans URLs in all hits, not just the initial pageview', () => {
const url = 'https://example.com/foo/bar?q=qux&b=baz#hash';
browser.execute(ga.run, 'set', 'location', url);
Expand Down Expand Up @@ -176,76 +114,6 @@ describe('cleanUrlTracker', function() {
assert.strictEqual(hits[1].cd1, 'query=new');
});

it('supports removing index filenames', () => {
const url = 'https://example.com/foo/bar/index.html?q=qux&b=baz#hash';
browser.execute(ga.run, 'set', 'location', url);
browser.execute(ga.run, 'require', 'cleanUrlTracker', {
indexFilename: 'index.html',
});
browser.execute(ga.run, 'send', 'pageview');
browser.waitUntil(log.hitCountEquals(1));

const hits = log.getHits();
assert.strictEqual(hits[0].dp, '/foo/bar/?q=qux&b=baz');
});

it('only removes index filenames at the end of the URL after a slash', () => {
const url = 'https://example.com/noindex.html';
browser.execute(ga.run, 'set', 'location', url);
browser.execute(ga.run, 'require', 'cleanUrlTracker', {
indexFilename: 'index.html',
});
browser.execute(ga.run, 'send', 'pageview');
browser.waitUntil(log.hitCountEquals(1));

const hits = log.getHits();
assert.strictEqual(hits[0].dp, '/noindex.html');
});

it('supports stripping trailing slashes', () => {
const url = 'https://example.com/foo/bar/';
browser.execute(ga.run, 'set', 'location', url);
browser.execute(ga.run, 'require', 'cleanUrlTracker', {
trailingSlash: 'remove',
});
browser.execute(ga.run, 'send', 'pageview');
browser.waitUntil(log.hitCountEquals(1));

const hits = log.getHits();
assert.strictEqual(hits[0].dp, '/foo/bar');
});

it('supports adding trailing slashes to non-filename URLs', () => {
const url = 'https://example.com/foo/bar?q=qux&b=baz#hash';
browser.execute(ga.run, 'set', 'location', url);
browser.execute(ga.run, 'require', 'cleanUrlTracker', {
stripQuery: true,
queryDimensionIndex: 1,
trailingSlash: 'add',
});
browser.execute(ga.run, 'send', 'pageview');
browser.execute(ga.run, 'set', 'page', '/foo/bar.html');
browser.execute(ga.run, 'send', 'pageview');
browser.waitUntil(log.hitCountEquals(2));

const hits = log.getHits();
assert.strictEqual(hits[0].dp, '/foo/bar/');
assert.strictEqual(hits[1].dp, '/foo/bar.html');
});

it('supports generically filtering all URL fields', () => {
const url = 'https://example.com/foo/bar?q=qux&b=baz#hash';
browser.execute(ga.run, 'set', 'location', url);
browser.execute(requireCleanUrlTracker_urlFieldsFilter);
browser.execute(ga.run, 'send', 'pageview');
browser.waitUntil(log.hitCountEquals(1));

const hits = log.getHits();
assert.strictEqual(hits[0].dl,
'https://example.io/foo/bar?q=qux&b=baz#hash');
assert.strictEqual(hits[0].dp, '/foo/bar');
});

it('works with many options in conjunction with each other', () => {
const url = 'https://example.com/path/to/index.html?q=qux&b=baz#hash';
browser.execute(ga.run, 'set', 'location', url);
Expand All @@ -256,7 +124,7 @@ describe('cleanUrlTracker', function() {
const hits = log.getHits();
assert.strictEqual(hits[0].dl,
'https://example.io/path/to/index.html?q=qux&b=baz#hash');
assert.strictEqual(hits[0].dp, '/path/to');
assert.strictEqual(hits[0].dp, '/path/to?q=qux');
assert.strictEqual(hits[0].cd1, 'q=qux&b=baz');
});

Expand Down Expand Up @@ -300,28 +168,6 @@ describe('cleanUrlTracker', function() {
});


/**
* Since function objects can't be passed via parameters from server to
* client, this one-off function must be used to set the value for
* `urlFieldsFilter`.
*/
function requireCleanUrlTracker_urlFieldsFilter() {
ga('require', 'cleanUrlTracker', {
urlFieldsFilter: (fieldsObj, parseUrl) => {
fieldsObj.page = parseUrl(fieldsObj.location).pathname;

const url = parseUrl(fieldsObj.location);
if (url.hostname == 'example.com') {
fieldsObj.location =
`${url.protocol}//example.io` +
`${url.pathname}${url.search}${url.hash}`;
}
return fieldsObj;
},
});
}


/**
* Since function objects can't be passed via parameters from server to
* client, this one-off function must be used to set the value for
Expand All @@ -330,6 +176,7 @@ function requireCleanUrlTracker_urlFieldsFilter() {
function requireCleanUrlTracker_multipleOpts() {
ga('require', 'cleanUrlTracker', {
stripQuery: true,
queryParamsWhitelist: ['q', 's'],
queryDimensionIndex: 1,
indexFilename: 'index.html',
trailingSlash: 'remove',
Expand Down
Loading

0 comments on commit 74308ac

Please sign in to comment.