Gatsbyjs google analytics gdpr - javascript

I want to use google analytics in my website, but be gdpr compliant, so only fire it, when the user consents.
I am using gatsby and followed this tutorial: https://www.improvebadcode.com/gatsby-gdpr-cookie-consent/, which makes total sense in my unterstanding.
So I'am using gatsby-plugin-gdpr-cookies and react-cookie-consent.
My gatsby-config looks like this:
plugins: [
{
resolve: `gatsby-plugin-gdpr-cookies`,
options: {
googleAnalytics: {
trackingId: '---', // leave empty if you want to disable the tracker
cookieName: 'gatsby-gdpr-google-analytics', // default
anonymize: true, // default
},
// defines the environments where the tracking should be available - default is ["production"]
environments: ['production', 'development'],
},
},
and my cookie consent in my App.js file like this:
<CookieConsent
enableDeclineButton
flipButtons
location="bottom"
buttonText="Zustimmen"
declineButtonStyle={{ background: '#5f7063', border: 'solid grey 1px', color: 'grey' }}
style={{ background: '#5f7063' }}
declineButtonText="Ablehnen"
buttonStyle={{
backgroundColor: '#fff',
color: '#000',
fontSize: '13px',
}}
cookieName="gatsby-gdpr-google-analytics"
>
Diese Website speichert Cookies auf Ihrem Computer. ...
</CookieConsent>
After gatsby build my cookie banner ist showing perfectly fine, but I don't receive any data on my google analytics.
I first thought the problem was that I was using the GA4 version of GA, but I generated an "old" Universal Analytics tag and it is still not working.
Can anybody tell me, what I'm doing wrong?
This ist the output on my website when I look for google analytics on my website:
var options = (0, _merge.default)(_defaultOptions.default, pluginOptions);
if (isEnvironmentValid(options.environments)) {
// google analytics
initGoogleAnalytics(options); // facebook pixel
initFacebookPixel(options);
}
}; // initializing helpers
exports.onClientEntry = onClientEntry;
var initGoogleAnalytics = function initGoogleAnalytics(options) {
if (cookies.get(options.googleAnalytics.cookieName) === "true" && (0, _validTrackingId.validGATrackingId)(options)) {
_reactGa.default.initialize(options.googleAnalytics.trackingId);
window.GoogleAnalyticsIntialized = true;
}
};
var initFacebookPixel = function initFacebookPixel(options) {
if (cookies.get(options.facebookPixel.cookieName) === "true" && (0, _validTrackingId.validFbPixelId)(options) && typeof window.fbq === "function") {
window.fbq("init", options.facebookPixel.pixelId);
window.FacebookPixelInitialized = true;
}
};
var checkIfGoogleAnalyticsIsInitilized = function checkIfGoogleAnalyticsIsInitilized() {
return !!window.GoogleAnalyticsIntialized;
};
var checkIfFacebookPixelIsInitilized = function checkIfFacebookPixelIsInitilized() {
return !!window.FacebookPixelInitialized;
}; // track
var onRouteUpdate = function onRouteUpdate(_ref, pluginOptions) {
var location = _ref.location;
if (pluginOptions === void 0) {
pluginOptions = {};
}
var options = (0, _merge.default)(_defaultOptions.default, pluginOptions);
if (isEnvironmentValid(options.environments)) {
// google analytics
if (!checkIfGoogleAnalyticsIsInitilized()) initGoogleAnalytics(options);
if (cookies.get(options.googleAnalytics.cookieName) === "true" && (0, _validTrackingId.validGATrackingId)(options) && _reactGa.default.ga) {
var gaAnonymize = options.googleAnalytics.anonymize;
var gaAllowAdFeatures = options.googleAnalytics.allowAdFeatures;
gaAnonymize = gaAnonymize !== undefined ? gaAnonymize : true;
gaAllowAdFeatures = gaAllowAdFeatures !== undefined ? gaAllowAdFeatures : true;
_reactGa.default.set({
page: location.pathname,
anonymizeIp: gaAnonymize,
allowAdFeatures: gaAllowAdFeatures
});
_reactGa.default.pageview(location.pathname);
} // google tag manager
if (cookies.get(options.googleTagManager.cookieName) === "true" && (0, _validTrackingId.validGTMTrackingId)(options)) {
setTimeout(function () {
var data = options.googleTagManager.dataLayerName ? window[options.googleTagManager.dataLayerName] : window.dataLayer;
if (typeof data === "object") {
var eventName = options.googleTagManager.routeChangeEvent || "gatsbyRouteChange";
data.push({
event: eventName
});
}
}, 50);
} // facebook pixel
if (!checkIfFacebookPixelIsInitilized()) initFacebookPixel(options);
if (cookies.get(options.facebookPixel.cookieName) === "true" && (0, _validTrackingId.validFbPixelId)(options) && typeof window.fbq === "function") {
window.fbq("track", "PageView");
}
}
};
exports.onRouteUpdate = onRouteUpdate;

I've faced recently the same issue, some Google Analytics plugins (gatsby-plugin-gdpr-cookies and gatsby-plugin-google-analytics). It seems that both are actually using an old version of the tracker. The script was perfectly inserted in the page but it doesn't show any results in Google's dashboard.
Reading some official documentation I've ended using gatsby-plugin-google-gtag (recommended by Gatsby) and it worked, maybe it works for you too:
// In your gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-google-gtag`,
options: {
// You can add multiple tracking ids and a pageview event will be fired for all of them.
trackingIds: [
"GA-TRACKING_ID", // Google Analytics / GA
"AW-CONVERSION_ID", // Google Ads / Adwords / AW
"DC-FLOODIGHT_ID", // Marketing Platform advertising products (Display & Video 360, Search Ads 360, and Campaign Manager)
],
// This object gets passed directly to the gtag config command
// This config will be shared across all trackingIds
gtagConfig: {
optimize_id: "OPT_CONTAINER_ID",
anonymize_ip: true,
cookie_expires: 0,
},
// This object is used for configuration specific to this plugin
pluginConfig: {
// Puts tracking script in the head instead of the body
head: false,
// Setting this parameter is also optional
respectDNT: true,
// Avoids sending pageview hits from custom paths
exclude: ["/preview/**", "/do-not-track/me/too/"],
},
},
},
],
}
You can omit/remove all the optional parameters and replace the GA-TRACKING_ID by yours.

Related

what features of Google Analytics can be used in Electron-Vue application

I have created an education application with electron-vue js and now I have decided to implement Google Analytics in this desktop application. I have googled for some packages but could not find what exactly I can get from Google Analytics i.e., what features of google analytics I should use to improve my study-based desktop application( electron-vue js platform).
Here is a little bit description about it:
a) the application is totally offline.
b) it includes study stuff like audios,videos,etc.,.
c) it also provides features like printing study material.
Even a single idea can help me figuring out what to do with Google analytics and can be a good head start.
Thanking you in advance!
Google analytics will consider Electron a website.
I use this plugin https://github.com/MatteoGabriele/vue-analytics
And set it up like this in your main entry for Vue in your renderer
import VueAnalytics, { set } from 'vue-analytics'
Vue.use(VueAnalytics, {
id: 'UA-idnumber',
router,
// debug: {
// enabled: true,
// trace: true // help you find problems
// },
fields: {
cookieDomain: 'none' // no domain
},
autoTracking: {
pageviewTemplate (route) {
// allow custom page titles in the router meta
let title = route.meta.title
if (!title) {
title = route.name
}
return {
page: route.name,
title: title,
location: route.path
}
}
}
})
set('allowAdFeatures', false) // no ads
set('checkProtocolTask', null) // ignore electron protocols
set('checkStorageTask', null) // ignore electrons cache solution, assume it works
Then I have directives like this
import { event } from 'vue-analytics'
Vue.directive('gaClick',
{
inserted: (el, binding, vnode) => {
let routeName = vnode.context.$route.meta.title
if (!routeName) {
routeName = vnode.context.$route.name
}
el.addEventListener('click', async e => {
const category = binding.value && binding.value.category ? binding.value.category : 'button Click'
const action = binding.value && binding.value.action ? binding.value.action : 'Click'
const label = binding.value && binding.value.label ? binding.value.label : `${e.target.innerText} (${routeName})`
const value = binding.value && binding.value.value ? binding.value.value : 0
event(category, action, label, value)
})
}
})
To be used on buttons and links like this
<router-link
:to="{name:'status-page'}}"
v-ga-click="{label:'Status Page'}"
>
Status Page
</router-link>
This will give you nearly all the features google analytics has. Unless they decide to change things again and break it. Like they did in their push to firebase analytics for "apps"

Cannot Authenticate Google Analytics Reporting API with Magic Mirror Module

I am working on a Magic Mirror module. This is my first time using node and i think i did some things wrong. But i cannot figure it out anymore with google and trying things out.
I have used the Hello Analytics template, and it worked when i started. But my code was just pasted below the node code and i knew this wasn't how it supposed to be. So i rewrote the code, but now it is not working anymore. I might have made a few mistakes, but i don't get any errors on my code in the javascript console.
I'll sum up what i did:
i created a project, and an API key for the module in the credentials app from google. i set everything up as asked by the hello analytics tutorial.
I added the ClientID to the meta tag in the head. I placed this directly in the index.html file. I also added the other required meta tag.
I created a view for my Analytics account and copied the view variable listed below. (the comment behind it is the Table id. as i didn't knew which one i needed.)
If the function queryReports is outside of the node framework, it works as intended and outputs data. But whenever i try to incorporate it in the node code it just throws an 401. Which means it is missing credentials to verify my user account. I have signed in with the OAuth2 button and it says i am singed in.
Can anyone tell me what i am doing wrong?
Here is the code for the module:
/* global Module */
/* Magic Mirror
* Module: MMM-ga GoogleAnalytics module
*
* By Skippy Skefnietof http://www.skippyweb.nl
* MIT Licensed.
*/
Module.register("MMM-ga",{
// Default module config.
defaults: {
text: 'test text',
startDate: '2017-02-12',
endDate: 'today',
viewID: '37419435'//'ga:37419435'
},
//getScripts: function() {
//return ['https://apis.google.com/js/client:platform.js'];
//},
getStyles: function() {
return ["MMM-ga.css"];
},
// Override dom generator.
getDom: function() {
var MMMga = document.createElement("div");
MMMga.className = "MMM-ga" + this.config.text;
var ga = document.createElement("p");
ga.setAttribute('data-onsuccess','this.queryReports');
ga.className = "g-signin2";
MMMga.appendChild(ga);
var gascript = document.createElement("script");
gascript.src = "https://apis.google.com/js/client:platform.js";
MMMga.appendChild(gascript);
var debugtxt = document.createElement("textarea");
debugtxt.id = "query-output";
MMMga.appendChild(debugtxt);
return MMMga;
//var queryReports = this.queryReports();
},
notificationReceived: function(notification, payload, sender) {
if (notification === "DOM_OBJECTS_CREATED") {
this.queryReports();
}
},
queryReports: function() {
console.log('!!!!!!!!LOOK AT ME I AM MR MEESEEKS! LOOK AT ME!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
gapi.client.request({
path: '/v4/reports:batchGet',
root: 'https://analyticsreporting.googleapis.com/',
method: 'POST',
body: {
reportRequests: [
{
viewId: this.config.viewID,
dateRanges: [
{
startDate: '7daysAgo',
endDate: 'today'
}
],
metrics: [
{
expression: 'ga:sessions'
}
]
}
]
}
}).then(this.displayResults, console.error.bind(console));
},
displayResults: function(response) {
var formattedJson = JSON.stringify(response.result, null, 2);
document.getElementById('query-output').value = formattedJson;
}
});

Cordova cordova-plugin-qrscanner: Opaque camera view

I am working through quasar-framework and I do the wrap with cordova for android platform.
The scanner works fine but blindly.
When QRScanner.show() starts I am getting full opaque view. I try to do all html elements transparent, hide and even remove some of them after and before QRScanner.show() call but always I see the opaque view. Someone knows how to fix this?
<script>
export default {
/*
Fuentes:
camera
https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-camera/index.html#takePicture
qrscanner
https://github.com/bitpay/cordova-plugin-qrscanner#prepare
*/
mounted () {
this.prepDevice()
},
data () {
return {
imageURI: '',
authorized: false,
selection: 'standard',
selectOptions: [
{
label: 'Camera-thumbnail',
value: 'camera-thmb'
},
{
label: 'Standard',
value: 'standard'
}
],
enableVisibility: 'hidden',
backColor: 'transparent'
}
},
methods: {
prepDevice () {
QRScanner.prepare(this.onDone)
},
onDone: function (err, status) {
if(err) {
alert("preparing: error code = " + err.code)
}
if(status.authorized) {
this.authorized = true
} else if (status.denied || !status.authorized) {
this.openSettings()
} else {
//No se obtuvo permiso
}
},
goScan: function () {
//--->>> Funciona pero el escaneo es a ciegas (vista en negro) <<<---
this.authorized = false
QRScanner.show()
/*
var html = document.getElementsByTagName("*")
for (var i=0; i<html.length; i++) {
html[i].style.backgroundColor = "transparent"
}
*/
//QRScanner.enableLight()
QRScanner.scan(this.displayContents)
},
displayContents: function (err, text) {
if(err){
alert("scanning: error code = " + err.code)
if(err.name === 'SCAN_CANCELED') {
alert("The scan was canceled before a QR code was found.")
}
} else {
alert(text)
}
//QRScanner.hide()
//QRScanner.disableLight()
QRScanner.destroy() // hide, cancelScan...
this.authorized = true
},
cancelScan() {
QRScanner.cancelScan()
this.authorized = true
},
openSettings() {
if(status.canOpenSettings){
if(confirm("Would you like to enable QR code scanning? You can allow camera access in your settings.")){
QRScanner.openSettings();
}
}
}
}
}
And the html where I call the goScan function:
<button v-if="authorized" class="secondary push" #click="goScan()">Go Scan</button>
Resource: https://github.com/bitpay/cordova-plugin-qrscanner
As I said the scan works fine but blindly with the full opaque camera view.
Thanks.
If scanning is already working, you're nearly there. Ensuring the video preview is visible basically requires stepping through the layers of your application and confirming that the layer isn't obscuring the preview.
Start by inspecting the DOM of your app while it's running on the device. Try setting a background of none transparent on each element covering the view, including the body and html elements. In almost all cases, you'll find a rogue container with a white background somewhere in the layers of your app.
If you're entirely convinced the entire web view is transparent (note: this is very unusual), you'll need to inspect the native layers of your app to determine if another plugin or configuration setting is interfering with visibility. Instructions for this step would be very platform specific, so it's best to consult documentation/forums for the platform in question.

eslint: howto lint only touched files

I have recently added eslint, as webpack loader, in a codebase that was never parsed with a linter before.
Obviously the amount of errors triggered are endless: there is any chance to configure eslint to parse only the touched files? I would like the linter to parse every file in which developers make changes and those only.
This is the loader I am using so far (in case can be of interest), very standard configuration:
{test: /\.(jsx|js)$/, loader: "eslint-loader?{cache: true}", exclude: /node_modules/}
Thank you
I accomplished it by using a watcher; this is the solution in the details:
dependencies for the Webpack configuration:
var logger = require('reliable-logger');
var watch = require('watch');
var CLIEngine = require('eslint').CLIEngine
watcher and linter configuration and start; I am pasting it with all the todos, as it is:
var configureLinterAndWatchFiles = function() {
var changedFiles = [];
var formatter;
var report;
var SEPARATOR = "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////";
// TODO I got the feeling that one of those settings is breaking the
// linter (probably the path resolving?)
var linter = new CLIEngine({
// TODO do I need this? Looks like I don't...
// envs: ["node"],
// TODO what is the default?
useEslintrc: true,
// TODO I find weird that I get no error with this: configFile: "../.eslintrc1111"
// make sure that the configuration file is correctly picked up
configFile: ".eslintrc",
// TODO useless if your root is src
// ignorePath: "node_modules"
// TODO probably both useless... the first I still don't get it,
// the second you are enforcing the filtering yourself by checks
// cache: false,
// extensions: [".js", ".jsx"]
});
var fileUpdatedFn = function(f) {
// TODO I would prefer much more to get the list of changed files from
// git status (how to?). Here I am building my own
// resetting the array only for debug purpose
// changedFiles = [];
if(/.js$/.test(f) || /.jsx$/.test(f)) {
changedFiles.push(f);
logger.info(SEPARATOR);
report = linter.executeOnFiles(changedFiles);
logger.info(formatter(report.results));
}
};
// get the default formatter
formatter = linter.getFormatter();
watch.watchTree('src', function(f, curr, prev) {
if (typeof f == "object" && prev === null && curr === null) {
// Finished walking the tree
} else if (prev === null) {
// f is a new file
} else if (curr.nlink === 0) {
// f was removed
} else {
// f was changed
fileUpdatedFn(f);
}
});
};
in module.exports, as last line:
module.exports = function(callback, options){
// ... more code ...
configureLinterAndWatchFiles();
}
That should be it. As I pointed out in a comment:
I wonder, though, if the cache flag (eslint.org/docs/developer-guide/nodejs-api#cliengine) was the best to be used for the problem. From here (github.com/adametry/gulp-eslint/issues/…): "--cache flag will skip over any files that had no problems in the previous run unless they have been modified": not sure if that is my case but is of interest.
Definitively I'm a little late for the party, but I faced the very same issue today & it seems like there is still no common solution for that.
I ended up monkey patching webpack's devServer with this:
const { exec } = require('child_process');
// ...
devServer: {
hot: false,
inline: false,
publicPath: '/',
historyApiFallback: true,
disableHostCheck: true,
after: (app, server, compiler) => {
compiler.hooks.watchRun.tap(
'EsLint-upon-save',
() => {
// This should only work in dev environment
if (process.env.NODE_ENV !== 'development') {
return;
}
// Credits to:
// https://stackoverflow.com/a/43149576/9430588
const filesChanged = Object.keys(compiler.watchFileSystem.watcher.mtimes);
// Might be empty
if (!filesChanged.length) {
return;
}
filesChanged.forEach((changedFileAbsolutePath) => {
const extension = changedFileAbsolutePath.split('.').pop();
if (extension === 'js' || extension === 'jsx') {
exec(`npx eslint --fix --fix-type suggestion,layout ${changedFileAbsolutePath}`);
}
});
}
);
}
},
It's surely quite quick & dirty type of solution, however it seems to work fine with eslint#7.7.0.

Do I need any server adjustments in order to use fully HTML5 History API?

I am using HTML5 History API (on Chrome), at the following link:
http://jsbin.com/zuqijofole/1
You can see a simple application which shows/hides div (Views).
Script works fine using browser backward and forward buttons but if I type directly in the browser the following address (in order to see View 2)
http://jsbin.com/zuqijofole/2
the document is not found. I need instead the second View to be shown.
I would like to know:
Should I implement on server side some logic which map URL? Which
coul be a solution using latest PHP?
Or am I missing some implementation in my JS?
Notes: solution should work in a SPA application, so all data is rendered by JS app.
window.app = {
data: {
views: [
{ id: 0, isActive: false },
{ id: 1, isActive: false },
{ id: 2, isActive: false },
]
},
start: function () {
this.listeners();
// default entry
var activeView = this.wm.activeView;
history.replaceState({ activeView: activeView }, document.title, document.location.href);
window.app.wm.hideViews();
window.app.wm.showView();
},
listeners: function () {
window.addEventListener('popstate', function (event) {
// fires when backing/forwarding in history
console.log(event);
console.log(window.history.state);
this.wm.showHideBl(event.state);
}.bind(this));
var elm = document.getElementById('btn-prev');
elm.addEventListener('click', function () {
window.app.wm.snowPrevView();
});
elm = document.getElementById('btn-next');
elm.addEventListener('click', function () {
window.app.wm.snowNextView();
});
},
wm: {
activeView: 0, // default
showView: function () {
var elm = document.getElementById('view-' + this.activeView);
elm.style.display = '';
},
showHideBl: function (data) {
this.hideView();
this.activeView = data.activeView;
this.showView();
},
snowNextView: function () {
// bl
if (this.activeView < window.app.data.views.length - 1) {
this.hideView();
this.activeView++;
this.showView();
history.pushState({ activeView: this.activeView }, '', this.activeView);
}
},
snowPrevView: function () {
// bl
if (this.activeView > 0) {
this.hideView();
this.activeView--;
this.showView();
history.pushState({ activeView: this.activeView }, '', this.activeView);
}
},
hideView: function () {
var elm = document.getElementById('view-' + this.activeView);
elm.style.display = 'none';
},
hideViews: function () {
window.app.data.views.forEach(function (item, index, array) {
var elm = document.getElementById('view-' + item.id);
elm.style.display = 'none';
}.bind(this));
}
}
};
Yes, for a completely seamless experience, you want that all URLs created by the History API to be mapped to actual URLs that the server can use.
For example, if you have a table that you can sort client side, you can use the history API to save the sorting state into the URL. The server should be able to read that URL and serve a table already sorted when the page is refreshed.
The best way to make sure everything works as intended is to disable JavaScript and make sure you can still navigate and use the site correctly (even though the page refreshes all the time).

Categories

Resources