From 5942d11cb6295724fb85fa3e4804ec311ca00e6e Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Thu, 26 Oct 2017 11:41:48 +0300 Subject: [PATCH] Use the canonical url for analytics page views --- src/Routes.js | 7 +++-- src/util/routes.js | 29 ++++++++++++++++++ src/util/routes.test.js | 65 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/Routes.js b/src/Routes.js index 83963a9a..fb64788a 100644 --- a/src/Routes.js +++ b/src/Routes.js @@ -8,6 +8,8 @@ import { NamedRedirect } from './components'; import { locationChanged } from './ducks/Routing.duck'; import * as propTypes from './util/propTypes'; import * as log from './util/log'; +import { canonicalRouteUrl } from './util/routes'; +import routeConfiguration from './routeConfiguration'; const { arrayOf, bool, object, func, shape, string } = PropTypes; @@ -36,9 +38,8 @@ const callLoadData = props => { }; const handleLocationChanged = (dispatch, location) => { - const { pathname, search, hash } = location; - const canonicalUrl = `${pathname}${search}${hash}`; - dispatch(locationChanged(location, canonicalUrl)); + const url = canonicalRouteUrl(routeConfiguration(), location); + dispatch(locationChanged(location, url)); }; class RouteComponentRenderer extends Component { diff --git a/src/util/routes.js b/src/util/routes.js index 8149b887..a94bc4de 100644 --- a/src/util/routes.js +++ b/src/util/routes.js @@ -88,3 +88,32 @@ export const findRouteByRouteName = (nameToFind, routes) => { } return route; }; + +/** + * Get the canonical URL from the given location + * + * @param {Array<{ route }>} routes - Route configuration as flat array + * @param {Object} location - location object from React Router + * + * @return {String} Canonical URL of the given location + * + */ +export const canonicalRouteUrl = (routes, location) => { + const { pathname, search, hash } = location; + + const matches = matchPathname(pathname, routes); + const isListingRoute = matches.length === 1 && matches[0].route.name === 'ListingPage'; + + if (isListingRoute) { + // Remove the dynamic slug from the listing page canonical URL + + const parts = pathname.split('/'); + if (parts.length !== 4) { + throw new Error('Expecting ListingPage pathname to have 4 parts'); + } + const canonicalListingPathname = `/${parts[1]}/${parts[3]}`; + return `${canonicalListingPathname}${search}${hash}`; + } + + return `${pathname}${search}${hash}`; +}; diff --git a/src/util/routes.test.js b/src/util/routes.test.js index 58a46d95..037ed2d2 100644 --- a/src/util/routes.test.js +++ b/src/util/routes.test.js @@ -4,7 +4,7 @@ import { RoutesProvider } from '../components'; import routeConfiguration from '../routeConfiguration'; import { renderDeep, renderShallow } from './test-helpers'; import * as propTypes from './propTypes'; -import { createResourceLocatorString, findRouteByRouteName } from './routes'; +import { createResourceLocatorString, findRouteByRouteName, canonicalRouteUrl } from './routes'; const { arrayOf } = PropTypes; @@ -68,4 +68,67 @@ describe('util/routes.js', () => { ); }); }); + + describe('canonicalRouteUrl', () => { + it('handles non-listing route', () => { + const routes = routeConfiguration(); + const location = { + pathname: '/', + search: '?some=value', + hash: '#and-some-hash', + }; + expect(canonicalRouteUrl(routes, location)).toEqual('/?some=value#and-some-hash'); + }); + it('handles ListingPage', () => { + const routes = routeConfiguration(); + const location = { + pathname: '/l/some-slug-here/00000000-0000-0000-0000-000000000000', + search: '', + hash: '', + }; + expect(canonicalRouteUrl(routes, location)).toEqual( + '/l/00000000-0000-0000-0000-000000000000' + ); + }); + it('handles ListingBasePage', () => { + const routes = routeConfiguration(); + const location = { + pathname: '/l', + search: '', + hash: '', + }; + expect(canonicalRouteUrl(routes, location)).toEqual('/l'); + }); + it('handles CheckoutPage', () => { + const routes = routeConfiguration(); + const location = { + pathname: '/l/some-slug-here/00000000-0000-0000-0000-000000000000/checkout', + search: '', + hash: '', + }; + expect(canonicalRouteUrl(routes, location)).toEqual( + '/l/some-slug-here/00000000-0000-0000-0000-000000000000/checkout' + ); + }); + it('handles NewListingPage', () => { + const routes = routeConfiguration(); + const location = { + pathname: '/l/new', + search: '', + hash: '', + }; + expect(canonicalRouteUrl(routes, location)).toEqual('/l/new'); + }); + it('handles ListingPageCanonical', () => { + const routes = routeConfiguration(); + const location = { + pathname: '/l/00000000-0000-0000-0000-000000000000', + search: '', + hash: '', + }; + expect(canonicalRouteUrl(routes, location)).toEqual( + '/l/00000000-0000-0000-0000-000000000000' + ); + }); + }); });