Skip to content

feat: add google analytics gtag.js plugin #1702

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

Merged
merged 19 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,26 @@ Install the plugin and configure the track id.

```html
<script>
// Single ID
window.$docsify = {
ga: 'UA-XXXXX-Y',
};

// Multiple IDs
window.$docsify = {
ga: [
'G-XXXXXXXX', // Google Analytics 4 (GA4)
'UA-XXXXXXXX', // Google Universal Analytics (GA3)
'AW-XXXXXXXX', // Google Ads
'DC-XXXXXXXX', // Floodlight
],
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/ga.min.js"></script>
```

Configure by `data-ga`.
Configure by `data-ga`, only support single gtag.

<!-- prettier-ignore -->
```html
Expand Down
52 changes: 41 additions & 11 deletions src/plugins/ga.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,61 @@
/* eslint-disable no-console */
// From https://github.com/egoist/vue-ga/blob/master/src/index.js
function appendScript() {

function appendScript(id) {
const script = document.createElement('script');
script.async = true;
script.src = 'https://www.google-analytics.com/analytics.js';
script.src = 'https://www.googletagmanager.com/gtag/js?id=' + id;
document.body.appendChild(script);
}

function init(id) {
appendScript();
window.ga =
window.ga ||
// global site tag instance initialized
function initGlobalSiteTag(id) {
appendScript(id);

window.dataLayer = window.dataLayer || [];
Copy link
Member

Choose a reason for hiding this comment

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

Let's move the window.dataLayer and window.gtag setup outside of the initGlobalSiteTag function. This will allow users to call gtag() so long as it is done after the plugin's <script> tag:

Plugin:

window.dataLayer = window.dataLayer || [];
window.gtag =
  window.gtag ||
  function () {
    window.dataLayer.push(arguments);
  };

$docsify.plugins = [].concat(install, $docsify.plugins);

HTML:

<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/plugins/gtag.min.js"></script>
<script>
// Advanced users can call `gtag()` on their own
window.gtag('event', 'conversion', {
  allow_custom_scripts: true,
  send_to: 'DC-ZZZZZZ/actions/locat304+standard',
});
</script>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not work, I think we can ignore this function for the time being, and then pass the custom collector in through parameters when we need it later.

window.gtag =
window.gtag ||
function () {
(window.ga.q = window.ga.q || []).push(arguments);
window.dataLayer.push(arguments);
};

window.ga.l = Number(new Date());
window.ga('create', id, 'auto');
window.gtag('js', new Date());
window.gtag('config', id);
}

// add additional products to your tag
// https://developers.google.com/tag-platform/gtagjs/install
function initAdditionalTag(id) {
window.gtag('config', id);
}

function init(ids) {
if (Array.isArray(ids)) {
// set the first id to be a global site tag
initGlobalSiteTag(ids[0]);

// the rest ids
ids.forEach((id, index) => {
if (index > 0) {
initAdditionalTag(id);
}
});
} else {
initGlobalSiteTag(ids);
}
}

function collect() {
if (!window.ga) {
init($docsify.ga);
}

window.ga('set', 'page', location.hash);
window.ga('send', 'pageview');
// usage: https://developers.google.com/analytics/devguides/collection/gtagjs/pages
window.gtag('event', 'page_view', {
page_title: document.title,
Copy link
Member

Choose a reason for hiding this comment

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

Please disable eslint's "camelcase" rules for under_score properties to prevent lint warnings:

window.gtag('event', 'page_view', {
  /* eslint-disable camelcase */
  page_title: document.title,
  page_location: location.href,
  page_path: location.pathname,
  /* eslint-enable camelcase */
});

You can verify the fix by running npm run lint.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi, there is some questions:

  1. Do we still need to keep ga.js ?
  2. docs/plugins.md is necessary to keep the instructions for ga and add the instructions for gtag ?

Copy link
Member

Choose a reason for hiding this comment

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

I believe we need to keep ga.js (unmodified) because people are actively using this plugin. Your work would then be a new plugin, gtag.js.

We should update the documentation and specify that the

We should update the documentation to specify that the ga.js plugin is for "Google Universal Analytics". We can also provide a slightly modified warning that align with the one Google shows when you log into https://analytics.google.com/ and view a UA property:

CleanShot 2022-08-01 at 17 08 30@2x

Perhaps our message under the ga.js heading can read:

Google's Universal Analytics service will no longer process new data in standard properties beginning July 1, 2023. Prepare now by setting up and switching over to a Google Analytics 4 property and docsify's gtag.js plugin.

How does that sound?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great~

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just pushed the code, there is an exception in ci/codesandbox. I'm not sure what the reason is, please check it for me.

Copy link
Member

Choose a reason for hiding this comment

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

I took a look and tried a few things in #1851 but I was unable to find the issue. This happened once before with Codesandbox it is it kinda of a PITA to troublehoot because the failures don't happen locally or in CI tests.

As much as I'd love to wrap this up, I don't have the spare cycles to devote to it right now. I will circle back as soon as I can devote some time to it.

In the meantime, thanks for being patient with this, @ekoz. Very much appreciated. 😄

page_location: location.href,
page_path: location.pathname,
});
}

const install = function (hook) {
Expand Down
97 changes: 97 additions & 0 deletions test/e2e/ga.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Modules, constants, and variables
// npm run test:e2e ga.test.js
// -----------------------------------------------------------------------------
const docsifyInit = require('../helpers/docsify-init');
const { test, expect } = require('./fixtures/docsify-init-fixture');

const gaTagList = [
'AW-YYYYYY', // Google Ads
'DC-ZZZZZZ', // Floodlight
'G-XXXXXX', // Google Analytics 4 (GA4)
'UA-XXXXXX', // Google Universal Analytics (GA3)
];

// Suite
// -----------------------------------------------------------------------------
test.describe('GA Plugin Tests', () => {
// page request listened, print collect url
function pageRequestListened(page) {
// page.on('request', request => {
// if (request.url().indexOf('www.google-analytics.com') !== -1) {
// console.log(request.url());
// }
// });

page.on('response', response => {
const request = response.request();
// googleads.g.doubleclick.net
// www.google-analytics.com
// www.googletagmanager.com
const reg =
/googleads\.g\.doubleclick\.net|www\.google-analytics\.com|www\.googletagmanager\.com/g;
if (request.url().match(reg)) {
console.log(request.url(), response.status());
Copy link
Member

Choose a reason for hiding this comment

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

Let's remove or comment out the console.log tests so we don't see them in every test run.

}
});
}

// Tests
// ---------------------------------------------------------------------------
test('single gtag', async ({ page }) => {
pageRequestListened(page);

const docsifyInitConfig = {
config: {
ga: gaTagList[0],
},
scriptURLs: ['/lib/plugins/ga.min.js'],
styleURLs: ['/lib/themes/vue.css'],
};

await docsifyInit({
...docsifyInitConfig,
});

const $docsify = await page.evaluate(() => window.$docsify);

// Verify config options
expect(typeof $docsify).toEqual('object');

console.log($docsify.ga, $docsify.ga === '');

// Tests
expect($docsify.ga).not.toEqual('');
});

test('multi gtag', async ({ page }) => {
pageRequestListened(page);

const docsifyInitConfig = {
config: {
ga: gaTagList,
},
scriptURLs: ['/lib/plugins/ga.min.js'],
styleURLs: ['/lib/themes/vue.css'],
};

await docsifyInit({
...docsifyInitConfig,
});

const $docsify = await page.evaluate(() => window.$docsify);

// Verify config options
expect(typeof $docsify).toEqual('object');

console.log($docsify.ga, $docsify.ga === '');

// Tests
expect($docsify.ga).not.toEqual('');
});

test('data-ga attribute', async ({ page }) => {
pageRequestListened(page);

// TODO
});
});