Merge pull request #75 from reuters-graphics/ga4
closes #74. Adds new Analytics component for GA4 and Chartbeat.
This commit is contained in:
commit
7257791440
19 changed files with 256 additions and 136 deletions
10
package.json
10
package.json
|
|
@ -41,6 +41,7 @@
|
|||
"@storybook/theming": "6.5.9",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"@tsconfig/svelte": "^3.0.0",
|
||||
"@types/gtag.js": "^0.0.12",
|
||||
"@types/proper-url-join": "^2.1.1",
|
||||
"@types/react-syntax-highlighter": "^15.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
||||
|
|
@ -100,6 +101,10 @@
|
|||
"./actions/cssVariables": "./dist/actions/cssVariables/index.js",
|
||||
"./actions/resizeObserver": "./dist/actions/resizeObserver/index.js",
|
||||
"./components/@types/global.ts": "./dist/components/@types/global.ts",
|
||||
"./components/Analytics/Analytics.svelte": "./dist/components/Analytics/Analytics.svelte",
|
||||
"./components/Analytics/providers/chartbeat.ts": "./dist/components/Analytics/providers/chartbeat.ts",
|
||||
"./components/Analytics/providers/ga.ts": "./dist/components/Analytics/providers/ga.ts",
|
||||
"./components/Analytics/providers/index.ts": "./dist/components/Analytics/providers/index.ts",
|
||||
"./components/Article/Article.svelte": "./dist/components/Article/Article.svelte",
|
||||
"./components/BeforeAfter/BeforeAfter.svelte": "./dist/components/BeforeAfter/BeforeAfter.svelte",
|
||||
"./components/Block/Block.svelte": "./dist/components/Block/Block.svelte",
|
||||
|
|
@ -131,10 +136,6 @@
|
|||
"./components/ReutersGraphicsLogo/ReutersGraphicsLogo.svelte": "./dist/components/ReutersGraphicsLogo/ReutersGraphicsLogo.svelte",
|
||||
"./components/ReutersLogo/ReutersLogo.svelte": "./dist/components/ReutersLogo/ReutersLogo.svelte",
|
||||
"./components/SEO/SEO.svelte": "./dist/components/SEO/SEO.svelte",
|
||||
"./components/SEO/analytics/chartbeat": "./dist/components/SEO/analytics/chartbeat.js",
|
||||
"./components/SEO/analytics/ga": "./dist/components/SEO/analytics/ga.js",
|
||||
"./components/SEO/analytics": "./dist/components/SEO/analytics/index.js",
|
||||
"./components/SEO/analytics/publisherTags": "./dist/components/SEO/analytics/publisherTags.js",
|
||||
"./components/Scroller/Background.svelte": "./dist/components/Scroller/Background.svelte",
|
||||
"./components/Scroller/Embedded/Background.svelte": "./dist/components/Scroller/Embedded/Background.svelte",
|
||||
"./components/Scroller/Embedded/Foreground.svelte": "./dist/components/Scroller/Embedded/Foreground.svelte",
|
||||
|
|
@ -206,6 +207,7 @@
|
|||
"./components/Video/Controls.svelte": "./dist/components/Video/Controls.svelte",
|
||||
"./components/Video/Video.svelte": "./dist/components/Video/Video.svelte",
|
||||
"./components/Visible/Visible.svelte": "./dist/components/Visible/Visible.svelte",
|
||||
"./globals.d.ts": "./dist/globals.d.ts",
|
||||
"./scss/mixins": "./dist/scss/_mixins.scss",
|
||||
"./scss/variables": "./dist/scss/_variables.scss",
|
||||
"./scss/bootstrap/main": "./dist/scss/bootstrap/_main.scss",
|
||||
|
|
|
|||
45
src/components/Analytics/Analytics.stories.svelte
Normal file
45
src/components/Analytics/Analytics.stories.svelte
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<script>
|
||||
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
|
||||
|
||||
// Don't lose the "?raw" in markdown imports!
|
||||
// @ts-ignore
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
// @ts-ignore
|
||||
import environmentsDocs from './stories/docs/environments.md?raw';
|
||||
// @ts-ignore
|
||||
import multipageDocs from './stories/docs/multipage.md?raw';
|
||||
|
||||
import Analytics from './Analytics.svelte';
|
||||
|
||||
import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Analytics',
|
||||
component: Analytics,
|
||||
...withComponentDocs(componentDocs),
|
||||
};
|
||||
</script>
|
||||
|
||||
<Meta {...meta} />
|
||||
|
||||
<Template let:args>
|
||||
<Analytics {...args} />
|
||||
<div>Nothing to see here</div>
|
||||
</Template>
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
authors: [{ name: 'Jane Doe' }, { name: 'John Doe' }],
|
||||
}}"
|
||||
/>
|
||||
|
||||
<Story name="Environments" {...withStoryDocs(environmentsDocs)}>
|
||||
<Analytics />
|
||||
<div>Nothing to see here</div>
|
||||
</Story>
|
||||
|
||||
<Story name="Multipage apps" {...withStoryDocs(multipageDocs)}>
|
||||
<Analytics />
|
||||
<div>Nothing to see here</div>
|
||||
</Story>
|
||||
29
src/components/Analytics/Analytics.svelte
Normal file
29
src/components/Analytics/Analytics.svelte
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<script context="module">
|
||||
import { registerPageview as registerChartbeatPageview } from './providers/chartbeat';
|
||||
import { registerPageview as registerGAPageview } from './providers/ga';
|
||||
|
||||
/** Register virtual pageviews when using client-side routing in multipage applications. */
|
||||
export function registerPageview() {
|
||||
registerChartbeatPageview();
|
||||
registerGAPageview();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- @component `Analytics` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-Analytics--default) -->
|
||||
<script lang="ts">
|
||||
interface Author {
|
||||
name: string;
|
||||
}
|
||||
/**
|
||||
* Used to associate a page with its author(s) in Chartbeat.
|
||||
*/
|
||||
export let authors: Author[] = [];
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { ga, chartbeat } from './providers';
|
||||
|
||||
onMount(() => {
|
||||
ga();
|
||||
chartbeat(authors);
|
||||
});
|
||||
</script>
|
||||
41
src/components/Analytics/providers/chartbeat.ts
Normal file
41
src/components/Analytics/providers/chartbeat.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Reuters Chartbeat UID
|
||||
const UID = 52639;
|
||||
|
||||
const URL = '//static.chartbeat.com/js/chartbeat.js';
|
||||
|
||||
const attachScript = () => {
|
||||
// If script is already attached, skip
|
||||
if (document.querySelector(`script[src="${URL}"]`)) return;
|
||||
// ... else attach it.
|
||||
const e = document.createElement('script');
|
||||
const n = document.getElementsByTagName('script')[0];
|
||||
e.type = 'text/javascript';
|
||||
e.async = true;
|
||||
e.src = URL;
|
||||
n.parentNode.insertBefore(e, n);
|
||||
};
|
||||
|
||||
export default (authors: { name: string }[]) => {
|
||||
window._sf_async_config = {
|
||||
uid: UID,
|
||||
domain: 'reuters.com',
|
||||
flickerControl: false,
|
||||
useCanonical: true,
|
||||
useCanonicalDomain: true,
|
||||
sections: 'Graphics',
|
||||
authors: authors.map((a) => a?.name || '').join(','),
|
||||
...(window._sf_async_config || {}),
|
||||
};
|
||||
|
||||
try {
|
||||
attachScript();
|
||||
} catch (e) { console.warn(`Error initialising Chartbeat Analytics: ${e}`); }
|
||||
};
|
||||
|
||||
export const registerPageview = () => {
|
||||
if (typeof window === 'undefined' || !window.pSUPERFLY) return;
|
||||
window.pSUPERFLY({
|
||||
path: window.location.pathname,
|
||||
title: document?.title,
|
||||
});
|
||||
};
|
||||
43
src/components/Analytics/providers/ga.ts
Normal file
43
src/components/Analytics/providers/ga.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Reuters Google Tag ID
|
||||
const GOOGLE_TAG_ID = 'G-W3Q2X6NTNM';
|
||||
|
||||
const URL = `https://www.googletagmanager.com/gtag/js?id=${GOOGLE_TAG_ID}`;
|
||||
|
||||
const attachScript = () => {
|
||||
// If script is already attached, skip
|
||||
if (document.querySelector(`script[src="${URL}"]`)) return;
|
||||
// ... else attach it.
|
||||
const e = document.createElement('script');
|
||||
const n = document.getElementsByTagName('script')[0];
|
||||
e.type = 'text/javascript';
|
||||
e.async = true;
|
||||
e.src = URL;
|
||||
n.parentNode.insertBefore(e, n);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
try {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
if (!window.gtag) {
|
||||
attachScript();
|
||||
/** @type {Gtag.Gtag} */
|
||||
window.gtag = (...args) => {
|
||||
window.dataLayer.push(...args);
|
||||
};
|
||||
window.gtag('js', new Date());
|
||||
// config event registers a pageview by default
|
||||
window.gtag('config', GOOGLE_TAG_ID, {
|
||||
send_page_view: false,
|
||||
});
|
||||
registerPageview();
|
||||
}
|
||||
} catch (e) { console.warn(`Error initialising Google Analytics: ${e}`); }
|
||||
};
|
||||
|
||||
export const registerPageview = () => {
|
||||
if (typeof window === 'undefined' || !window.gtag) return;
|
||||
window.gtag('event', 'page_view', {
|
||||
page_location: window.location.origin + window.location.pathname,
|
||||
page_title: document?.title,
|
||||
});
|
||||
};
|
||||
2
src/components/Analytics/providers/index.ts
Normal file
2
src/components/Analytics/providers/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { default as ga } from './ga';
|
||||
export { default as chartbeat } from './chartbeat';
|
||||
11
src/components/Analytics/stories/docs/component.md
Normal file
11
src/components/Analytics/stories/docs/component.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
Add Google and Chartbeat analytics to your page.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Analytics } from '@reuters-graphics/graphics-components';
|
||||
|
||||
const authors = [{ name: 'Jane Doe' }, { name: 'John Doe' }];
|
||||
</script>
|
||||
|
||||
<Analytics authors="{authors}" />
|
||||
```
|
||||
17
src/components/Analytics/stories/docs/environments.md
Normal file
17
src/components/Analytics/stories/docs/environments.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
Generally, you only want to send page analytics in production environments.
|
||||
|
||||
In a SvelteKit context, you can use `$app` stores to restrict when you send analytics.
|
||||
|
||||
For example, the following excludes analytics from pages in development or hosted on our preview server:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Analytics } from '@reuters-graphics/graphics-components';
|
||||
import { dev } from '$app/environment';
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
{#if !dev && $page.url?.hostname !== 'graphics.thomsonreuters.com'}
|
||||
<Analytics />
|
||||
{/if}
|
||||
```
|
||||
32
src/components/Analytics/stories/docs/multipage.md
Normal file
32
src/components/Analytics/stories/docs/multipage.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
If you're using analytics to measure a multipage newsapp that uses [client-side routing](https://kit.svelte.dev/docs/glossary#routing), then you may need to trigger analytics after virtual page navigation.
|
||||
|
||||
This component also exports a function you can call to register pageviews.
|
||||
|
||||
For example, here's how you can use SvelteKit's [`afterNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-afternavigate) lifecycle to capture additional pageviews:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import {
|
||||
Analytics,
|
||||
registerPageview,
|
||||
} from '@reuters-graphics/graphics-components';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let hasMounted = false;
|
||||
|
||||
onMount(() => {
|
||||
hasMounted = true;
|
||||
});
|
||||
|
||||
afterNavigate(() => {
|
||||
// We shouldn't fire on initial page load because the Analytics component
|
||||
// already registers a reader's first pageview. After this component
|
||||
// has mounted, we can be sure that further navigation is virtual and
|
||||
// register pageviews using this function.
|
||||
if (hasMounted) registerPageview();
|
||||
});
|
||||
</script>
|
||||
|
||||
<Analytics />
|
||||
```
|
||||
|
|
@ -1,15 +1,9 @@
|
|||
<!-- @component `SEO` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-SEO--default) -->
|
||||
<script lang="ts">
|
||||
import {
|
||||
loadChartbeat,
|
||||
loadGA,
|
||||
loadPublisherTags,
|
||||
} from './analytics/index.js';
|
||||
|
||||
/**
|
||||
* Base url for the page, which in [Vite-based projects](https://vitejs.dev/guide/build.html#public-base-path)
|
||||
* is globally available as `import.meta.env.BASE_URL`.
|
||||
* @required
|
||||
* @requiredx
|
||||
* @type {string}
|
||||
*/
|
||||
export let baseUrl: string = '';
|
||||
|
|
@ -75,10 +69,6 @@
|
|||
* Array of authors for the piece. Each author object must have `name` and `url` attributes.
|
||||
*/
|
||||
export let authors: GraphicAuthor[] = [];
|
||||
/**
|
||||
* Whether to inject Google Analytics code for this page.
|
||||
*/
|
||||
export let includeAnalytics: boolean = false;
|
||||
|
||||
const getOrigin = (baseUrl) => {
|
||||
try {
|
||||
|
|
@ -94,15 +84,6 @@
|
|||
$: origin = getOrigin(baseUrl);
|
||||
$: canonicalUrl = origin + pageUrl.pathname;
|
||||
|
||||
// Only fire analytics on prod sites
|
||||
$: {
|
||||
if (typeof window !== 'undefined' && includeAnalytics) {
|
||||
loadChartbeat(authors);
|
||||
loadGA(canonicalUrl, seoTitle);
|
||||
loadPublisherTags();
|
||||
}
|
||||
}
|
||||
|
||||
const orgLdJson = {
|
||||
'@context': 'http://schema.org',
|
||||
'@type': 'NewsMediaOrganization',
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
/* eslint-disable */
|
||||
const attachScript = () => {
|
||||
// If script is already attached, skip
|
||||
if (
|
||||
document.querySelector(
|
||||
'script[src="//static.chartbeat.com/js/chartbeat.js"]'
|
||||
)
|
||||
)
|
||||
return;
|
||||
// ... else attach it.
|
||||
const e = document.createElement('script');
|
||||
const n = document.getElementsByTagName('script')[0];
|
||||
e.type = 'text/javascript';
|
||||
e.async = true;
|
||||
e.src = '//static.chartbeat.com/js/chartbeat.js';
|
||||
n.parentNode.insertBefore(e, n);
|
||||
};
|
||||
|
||||
export default (authors) => {
|
||||
// @ts-ignore
|
||||
const _sf_async_config = (window._sf_async_config =
|
||||
window._sf_async_config || {});
|
||||
_sf_async_config.uid = 52639;
|
||||
_sf_async_config.domain = 'reuters.com';
|
||||
_sf_async_config.flickerControl = false;
|
||||
_sf_async_config.useCanonical = true;
|
||||
_sf_async_config.useCanonicalDomain = true;
|
||||
_sf_async_config.sections = 'Graphics';
|
||||
_sf_async_config.authors = authors.map((a) => a.name).join(',');
|
||||
try {
|
||||
attachScript();
|
||||
} catch (e) {}
|
||||
};
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/* eslint-disable */
|
||||
const attachScript = function (i, s, o, g, r, a, m) {
|
||||
i.GoogleAnalyticsObject = r;
|
||||
(i[r] =
|
||||
i[r] ||
|
||||
function () {
|
||||
(i[r].q = i[r].q || []).push(arguments);
|
||||
}),
|
||||
(i[r].l = Date.now());
|
||||
(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m);
|
||||
};
|
||||
/* eslint-enable */
|
||||
|
||||
export default (page, title) => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
if (!window.ga) {
|
||||
attachScript(
|
||||
window,
|
||||
document,
|
||||
'script',
|
||||
'https://www.google-analytics.com/analytics.js',
|
||||
'ga'
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
window.ga('create', 'UA-41619329-3', { cookieDomain: 'auto' });
|
||||
// @ts-ignore
|
||||
window.ga('require', 'linkid', 'linkid.js');
|
||||
}
|
||||
// @ts-ignore
|
||||
window.ga('send', 'pageview', {
|
||||
page,
|
||||
title,
|
||||
});
|
||||
} catch (e) {}
|
||||
};
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export { default as loadGA } from './ga.js';
|
||||
export { default as loadChartbeat } from './chartbeat.js';
|
||||
export { default as loadPublisherTags } from './publisherTags.js';
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
export default () => {
|
||||
try {
|
||||
const { protocol } = document.location;
|
||||
const gptScript = document.querySelector(
|
||||
`script[src="${protocol}//www.googletagservices.com/tag/js/gpt.js"]`
|
||||
);
|
||||
// Only do this once.
|
||||
if (gptScript) return;
|
||||
const googletag = window.googletag || {};
|
||||
googletag.cmd = googletag.cmd || [];
|
||||
(function () {
|
||||
const gads = document.createElement('script');
|
||||
gads.async = true;
|
||||
gads.type = 'text/javascript';
|
||||
const useSSL = document.location.protocol === 'https:';
|
||||
gads.src =
|
||||
(useSSL ? 'https:' : 'http:') +
|
||||
'//www.googletagservices.com/tag/js/gpt.js';
|
||||
const node = document.getElementsByTagName('script')[0];
|
||||
node.parentNode.insertBefore(gads, node);
|
||||
})();
|
||||
googletag.cmd.push(function () {
|
||||
googletag
|
||||
.defineSlot(
|
||||
'/4735792/reuters_investigates',
|
||||
[[300, 250]],
|
||||
'div-gpt-ad-1441822201033-0'
|
||||
)
|
||||
.addService(googletag.pubads());
|
||||
googletag.pubads().enableSingleRequest();
|
||||
googletag.enableServices();
|
||||
});
|
||||
} catch (e) {}
|
||||
};
|
||||
|
|
@ -22,6 +22,5 @@ The `SEO` component adds essential metadata to published pages.
|
|||
{ name: 'Jane Doe', url: 'https://twitter.com/JaneDoe' },
|
||||
{ name: 'John Doe', url: 'https://twitter.com/JohnDoe' },
|
||||
]}"
|
||||
includeAnalytics="{true}"
|
||||
/>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -25,6 +25,5 @@ Most of the fields are filled in by other sources, like your Google Doc and pack
|
|||
publishTime="{pkg?.reuters?.graphic?.published}"
|
||||
updateTime="{pkg?.reuters?.graphic?.updated}"
|
||||
authors="{pkg?.reuters?.graphic?.authors}"
|
||||
includeAnalytics="{$page.url.hostname === 'graphics.reuters.com'}"
|
||||
/>
|
||||
```
|
||||
|
|
|
|||
23
src/globals.d.ts
vendored
Normal file
23
src/globals.d.ts
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
interface ChartbeatConfig {
|
||||
uid?: number;
|
||||
domain?: string;
|
||||
flickerControl?: boolean;
|
||||
useCanonical?: boolean;
|
||||
useCanonicalDomain?: boolean;
|
||||
sections?: string;
|
||||
authors?: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
interface Window {
|
||||
/** Google analytics dataLayer */
|
||||
dataLayer: Record<string, any>,
|
||||
/** Chartbeat config */
|
||||
_sf_async_config: ChartbeatConfig,
|
||||
/** Chartbeat method */
|
||||
pSUPERFLY: (config: { path: string, title: string }) => void,
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
// Components
|
||||
export { default as Analytics, registerPageview } from './components/Analytics/Analytics.svelte';
|
||||
export { default as Article } from './components/Article/Article.svelte';
|
||||
export { default as BeforeAfter } from './components/BeforeAfter/BeforeAfter.svelte';
|
||||
export { default as Block } from './components/Block/Block.svelte';
|
||||
|
|
|
|||
|
|
@ -2729,6 +2729,11 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/gtag.js@^0.0.12":
|
||||
version "0.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/gtag.js/-/gtag.js-0.0.12.tgz#095122edca896689bdfcdd73b057e23064d23572"
|
||||
integrity sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==
|
||||
|
||||
"@types/hast@^2.0.0":
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc"
|
||||
|
|
|
|||
Loading…
Reference in a new issue