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

Add canonical link tags #6466

Merged
merged 14 commits into from Oct 9, 2018
Merged

Add canonical link tags #6466

merged 14 commits into from Oct 9, 2018

Conversation

willdurand
Copy link
Member

@willdurand willdurand commented Oct 1, 2018

Fixes mozilla/addons#12123


This PR adds canonical link tags to the following pages:

  • add-on detail pages
  • homepage
  • Landing Extensions
  • Landing Themes
  • Themes / category pages
  • About page (component converted to Flow)
  • Review guide (component converted to Flow)
  • Dictionaries and Lang Packs
  • Extensions / category pages

⚠️ in order to avoid hardcoded URLs, I read the router config (pathname) from the redux state. It ensures that our canonical URLs are in sync with the router config, but that's why test cases don't have the "production" values. It also avoids issues with trailing slashes (because we can trust the middleware and we don't have to think about them).

@codecov-io
Copy link

codecov-io commented Oct 1, 2018

Codecov Report

Merging #6466 into master will increase coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    mozilla/addons-frontend#6466      +/-   ##
==========================================
+ Coverage   97.82%   97.82%   +<.01%     
==========================================
  Files         239      239              
  Lines        6475     6488      +13     
  Branches     1238     1239       +1     
==========================================
+ Hits         6334     6347      +13     
  Misses        126      126              
  Partials       15       15
Impacted Files Coverage Δ
src/amo/pages/Addon/index.js 100% <ø> (ø) ⬆️
src/amo/pages/Home/index.js 100% <ø> (ø) ⬆️
src/amo/store.js 100% <ø> (ø) ⬆️
src/amo/pages/Category/index.js 100% <ø> (ø) ⬆️
src/amo/pages/LandingPage/index.js 100% <ø> (ø) ⬆️
src/core/components/ServerHtml/index.js 100% <ø> (ø) ⬆️
src/amo/pages/StaticPages/ReviewGuide/index.js 100% <100%> (ø) ⬆️
src/amo/pages/LanguageTools/index.js 100% <100%> (ø) ⬆️
src/amo/utils.js 100% <100%> (ø) ⬆️
src/amo/pages/StaticPages/About/index.js 100% <100%> (ø) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cda25ae...24d80d9. Read the comment docs.

@willdurand willdurand force-pushed the canonical-links branch 2 times, most recently from f44f0d5 to fcae912 Compare October 4, 2018 12:12
@willdurand willdurand changed the title [WIP] Add canonical link tags Add canonical link tags Oct 4, 2018
@rebmullin rebmullin self-assigned this Oct 4, 2018
Copy link
Contributor

@rebmullin rebmullin left a comment

Choose a reason for hiding this comment

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

@willdurand, I have some minor comments/questions noted but this looks really good to me 👍

r+wc

src/core/types/router.js Outdated Show resolved Hide resolved
tests/unit/amo/helpers.js Outdated Show resolved Hide resolved
src/amo/utils.js Outdated Show resolved Hide resolved
@@ -6,4 +6,8 @@ export const apiDevHost = 'https://addons-dev.allizom.org';
export const apiProdHost = 'https://addons.mozilla.org';
export const apiStageHost = 'https://addons.allizom.org';

export const baseUrlDev = 'https://addons-dev.allizom.org';
export const baseUrlProd = 'https://addons.mozilla.org';
export const baseUrlStage = 'https://addons.allizom.org';
Copy link
Contributor

Choose a reason for hiding this comment

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

since these are the same as above, could we use those?

Copy link
Member Author

Choose a reason for hiding this comment

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

you mean: baseUrlDev = apiDevHost?

Copy link
Contributor

Choose a reason for hiding this comment

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

yeh that's what I was thinking (since it's the same)

@kumar303 kumar303 assigned kumar303 and unassigned rebmullin Oct 5, 2018
Copy link
Contributor

@kumar303 kumar303 left a comment

Choose a reason for hiding this comment

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

I have some questions and change requests. I'm not sure that it's a good idea to execute the URL calculation in every mapStateToProps so I offered an alternative. Let me know what you think.

@@ -67,14 +69,21 @@ export const LanguageToolList = ({ languageTools }: LanguageToolListProps) => {
};

type Props = {|
languageTools: Array<LanguageToolType>,
// eslint-disable-next-line react/no-unused-prop-types
Copy link
Contributor

Choose a reason for hiding this comment

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

We need this for pretty much any file that uses Flow so I'd just put it at the top.

src/core/types/router.js Outdated Show resolved Hide resolved
tests/unit/amo/helpers.js Outdated Show resolved Hide resolved
const lang = 'fr';

const _config = getFakeConfig({ baseURL });
const { state } = dispatchClientMetadata({ clientApp, lang });
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than rely on the implicit pathname set by dispatchClientMetadata, this should set an explicit pathname since that's integral to the test.

src/amo/pages/Addon/index.js Show resolved Hide resolved
const { landing, viewContext } = state;

return {
addonTypeOfResults: landing.addonType,
currentURL: getCurrentURL({ state, _config: ownProps._config }),
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than execute this in every mapStateToProps (which would happen a lot), how about just passing through the state we need? That would cause the function to only be executed when the state value changes and is more in line with how we typically do things in mapStateToProps.

Example:

diff --git a/src/amo/pages/LandingPage/index.js b/src/amo/pages/LandingPage/index.js
index 67a639446..cceeb9a94 100644
--- a/src/amo/pages/LandingPage/index.js
+++ b/src/amo/pages/LandingPage/index.js
@@ -12,7 +12,7 @@ import { setViewContext } from 'amo/actions/viewContext';
 import LandingAddonsCard from 'amo/components/LandingAddonsCard';
 import NotFound from 'amo/components/ErrorPage/NotFound';
 import Categories from 'amo/components/Categories';
-import { getCurrentURL } from 'amo/utils';
+import { getCanonicalURL } from 'amo/utils';
 import {
   ADDON_TYPE_EXTENSION,
   ADDON_TYPE_THEME,
@@ -212,11 +212,12 @@ export class LandingPageBase extends React.Component {
 
   render() {
     const {
-      currentURL,
+      _config,
       errorHandler,
       featuredAddons,
       highlyRatedAddons,
       loading,
+      locationPathname,
       trendingAddons,
       i18n,
     } = this.props;
@@ -250,7 +251,10 @@ export class LandingPageBase extends React.Component {
       >
         <Helmet>
           <title>{headingText[addonType]}</title>
-          <link rel="canonical" href={currentURL} />
+          <link
+            rel="canonical"
+            href={getCanonicalURL({ pathname: locationPathname, _config })}
+          />
         </Helmet>
 
         {errorHandler.renderErrorIfPresent()}
@@ -319,15 +323,15 @@ export class LandingPageBase extends React.Component {
 }
 
 export function mapStateToProps(state, ownProps) {
-  const { landing, viewContext } = state;
+  const { landing, router, viewContext } = state;
 
   return {
     addonTypeOfResults: landing.addonType,
-    currentURL: getCurrentURL({ state, _config: ownProps._config }),
     context: viewContext.context,
     featuredAddons: landing.featured.results,
     highlyRatedAddons: landing.highlyRated.results,
     loading: landing.loading,
+    locationPathname: router.location.pathname,
     trendingAddons: landing.trending.results,
     resultsLoaded: landing.resultsLoaded && landing.category === null,
   };
diff --git a/src/amo/utils.js b/src/amo/utils.js
index f1f96a1b3..82f0db32e 100644
--- a/src/amo/utils.js
+++ b/src/amo/utils.js
@@ -9,6 +9,7 @@ import NotFound from 'amo/components/ErrorPage/NotFound';
 import ServerError from 'amo/components/ErrorPage/ServerError';
 import { makeQueryString } from 'core/api';
 import type { AppState } from 'amo/store';
+import type { ReactRouterLocationType } from 'core/types/router';
 
 export function getErrorComponent(status: number | null) {
   switch (status) {
@@ -56,6 +57,16 @@ export const makeQueryStringWithUTM = ({
   });
 };
 
+export const getCanonicalURL = ({
+  _config = config,
+  pathname,
+}: {|
+  _config?: typeof config,
+  pathname: string,
+|}): string => {
+  return `${_config.get('baseURL')}${pathname}`;
+};
+
 export const getCurrentURL = ({
   _config = config,
   state,

Copy link
Member Author

Choose a reason for hiding this comment

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

Rather than execute this in every mapStateToProps (which would happen a lot), how about just passing through the state we need? That would cause the function to only be executed when the state value changes and is more in line with how we typically do things in mapStateToProps.

I think that does make a lot of sense. I knew something was wrong with this patch but could not see what.. Thanks for your input!

@willdurand
Copy link
Member Author

updated and rebased

@willdurand
Copy link
Member Author

re-rebased

Copy link
Contributor

@kumar303 kumar303 left a comment

Choose a reason for hiding this comment

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

r+wc

I am uneasy about copying/pasting onLocatonChange but at least your approach is simple 🤷‍♀️ I dunno, I say ship it but maybe we can revisit it later.

src/amo/store.js Outdated
import type { CreateStoreParams, CreateReducerType } from 'core/types/store';

export type AppState = {|
type AppStateWithoutRouter = {|
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we call this AMOAppState or InternalAppState? The distinction seems to be that these are all our explicitly mapped reducers whereas AppState also includes reducers added by third party libraries. The name isn't a big deal, though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, I go for InternalAppState in case there are other reducers we have to move out of this set.

hash: string, // e.g. #some-anchor
key: string,
pathname: string, // e.g. /en-US/firefox/addon/tab-mix-plus/reviews/
search: string, // e.g. ?q=search-string
state?: Object,
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe just add a comment here that sometimes location contains state but sometimes it doesn't (in my debugging, I don't see it). This comment would help dissuade someone from relying on this property. I realize that state? was always defined here (before your patch) but I only just noticed it 😬

tests/unit/amo/helpers.js Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants