closes #74. Adds new Analytics component for GA4 and Chartbeat.

This commit is contained in:
Jon McClure 2023-05-28 17:46:54 +01:00
parent fbbf500cbf
commit d3d917b74f
18 changed files with 236 additions and 133 deletions

View file

@ -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",
@ -237,4 +238,4 @@
".": "./dist/index.js"
},
"svelte": "./dist/index.js"
}
}

View 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>

View 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>

View file

@ -0,0 +1,46 @@
// Reuters Chartbeat UID
const UID = 52639;
/* 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);
};
/* eslint-enable */
export default (authors: { name: string }[]) => {
// @ts-ignore
const config = window._sf_async_config = (window._sf_async_config || {});
config.uid = UID;
config.domain = 'reuters.com';
config.flickerControl = false;
config.useCanonical = true;
config.useCanonicalDomain = true;
config.sections = 'Graphics';
config.authors = authors.map((a) => a.name).join(',');
try {
attachScript();
} catch (e) { console.warn(`Error initialising Chartbeat Analytics: ${e}`); }
};
export const registerPageview = () => {
// @ts-ignore
if (!window.pSUPERFLY) return;
// @ts-ignore
window.pSUPERFLY({
path: window.location.pathname,
title: document.title,
});
};

View file

@ -0,0 +1,45 @@
// Reuters Google Tag ID
const GOOGLE_TAG_ID = 'G-W3Q2X6NTNM';
/* eslint-disable */
const attachScript = () => {
// If script is already attached, skip
if (
document.querySelector(
`script[src="https://www.googletagmanager.com/gtag/js?id=${GOOGLE_TAG_ID}"]`
)
)
return;
// ... else attach it.
const e = document.createElement('script');
const n = document.getElementsByTagName('script')[0];
e.type = 'text/javascript';
e.async = true;
e.src = `https://www.googletagmanager.com/gtag/js?id=${GOOGLE_TAG_ID}`;
n.parentNode.insertBefore(e, n);
};
/* eslint-enable */
export default () => {
try {
// @ts-ignore
window.dataLayer = window.dataLayer || [];
if (!window.gtag) {
attachScript();
/** @type {Gtag.Gtag} */
window.gtag = (...args) => {
// @ts-ignore
window.dataLayer.push(...args);
};
window.gtag('js', new Date());
window.gtag('config', GOOGLE_TAG_ID);
}
} catch (e) { console.warn(`Error initialising Google Analytics: ${e}`); }
};
export const registerPageview = () => {
if (!window.gtag) return;
window.gtag('event', 'page_view', {
page_location: window.location.origin + window.location.pathname,
});
};

View file

@ -0,0 +1,2 @@
export { default as ga } from './ga';
export { default as chartbeat } from './chartbeat';

View 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}" />
```

View 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}
```

View 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 />
```

View file

@ -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',

View file

@ -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) {}
};

View file

@ -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) {}
};

View file

@ -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';

View file

@ -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) {}
};

View file

@ -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}"
/>
```

View file

@ -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'}"
/>
```

View file

@ -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';

View file

@ -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"