Skip to content

Commit

Permalink
Merge pull request #1819 from 4dn-dcic/ga_migration
Browse files Browse the repository at this point in the history
UA to GA4 Migration
  • Loading branch information
utku-ozturk authored Jul 10, 2023
2 parents a1553de + 43e12ae commit 6cecddf
Show file tree
Hide file tree
Showing 24 changed files with 1,330 additions and 1,578 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ fourfront
Change Log
----------

5.3.16
======

`Google Analytics - UA to GA4 migration <https://github.com/4dn-dcic/fourfront/pull/1819>`_

* UA (Universal Analytics) sunsetted on July 1st, 2023
* UA property and all hit-based functions including enhanced e-commerce plugins are migrated to GA4 property
* Supports Google Tag Manager (GTM)


5.3.15
======

Expand Down
2,448 changes: 1,077 additions & 1,371 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.4",
"@hms-dbmi-bgm/react-workflow-viz": "0.1.7",
"@hms-dbmi-bgm/shared-portal-components": "git+https:github.com/4dn-dcic/shared-portal-components#0.1.64",
"@hms-dbmi-bgm/shared-portal-components": "git+https:github.com/4dn-dcic/shared-portal-components#0.1.65",
"auth0-lock": "^11.33.1",
"d3": "^7.5.0",
"date-fns": "^2.28.0",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tool.poetry]
# Note: Various modules refer to this system as "encoded", not "fourfront".
name = "encoded"
version = "5.3.15"
version = "5.3.16"
description = "4DN-DCIC Fourfront"
authors = ["4DN-DCIC Team <[email protected]>"]
license = "MIT"
Expand Down
9 changes: 8 additions & 1 deletion src/encoded/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from dcicutils.ff_utils import get_health_page
from dcicutils.log_utils import set_logging
from dcicutils.misc_utils import VirtualApp
from dcicutils.secrets_utils import assumed_identity, SecretsTable
from dcicutils.secrets_utils import assume_identity, assumed_identity, SecretsTable
from pyramid.config import Configurator
from pyramid_localroles import LocalRolesAuthorizationPolicy
from pyramid.settings import asbool
Expand Down Expand Up @@ -191,6 +191,13 @@ def main(global_config, **local_config):
'github', 'google-oauth2'
]
}
# ga4 api secret
if 'IDENTITY' in os.environ:
identity = assume_identity()
if 'ga4.secret' not in settings:
settings['ga4.secret'] = identity.get('GA4_API_SECRET', os.environ.get('GA4Secret'))
else:
settings['ga4.secret'] = settings.get('ga4.secret', os.environ.get('GA4Secret'))
# set google reCAPTCHA keys
# TODO propagate from GAC
settings['g.recaptcha.key'] = os.environ.get('reCaptchaKey')
Expand Down
19 changes: 12 additions & 7 deletions src/encoded/static/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,11 @@ export default class App extends React.PureComponent {
'handleClick', 'handleSubmit', 'handlePopState', 'handleBeforeUnload'
);

const { context } = props;
const { context, href } = props;

Alerts.setStore(store);

const analyticsID = getGoogleAnalyticsTrackingID(href);
/**
* Whether HistoryAPI is supported in current browser.
* Assigned / determined in constructor.
Expand Down Expand Up @@ -175,6 +176,7 @@ export default class App extends React.PureComponent {
*/
this.state = {
session,
analyticsID,
'schemas' : context.schemas || null,
'isSubmitting' : false,
'mounted' : false
Expand Down Expand Up @@ -202,7 +204,7 @@ export default class App extends React.PureComponent {
*/
componentDidMount() {
const { href, context } = this.props;
// const { session } = this.state;
const { analyticsID } = this.state;

ajax.AJAXSettings.addSessionExpiredCallback(this.updateAppSessionState);
// The href prop we have was from serverside. It would not have a hash in it, and might be shortened.
Expand All @@ -214,7 +216,6 @@ export default class App extends React.PureComponent {

// Load up analytics
// Load up analytics & perform initial pageview track
const analyticsID = getGoogleAnalyticsTrackingID(href);
if (analyticsID){
analytics.initializeGoogleAnalytics(
analyticsID,
Expand Down Expand Up @@ -1145,6 +1146,9 @@ export default class App extends React.PureComponent {
this.historyEnabled = false;
}

const { analyticsID } = this.state;
const gtag4Script = analyticsID && ("https://www.googletagmanager.com/gtag/js?id=" + analyticsID);

const isLoading = contextRequest && contextRequest.xhr && contextRequest.xhr.readyState < 4;
const baseDomain = (hrefParts.protocol || '') + '//' + hrefParts.host;
const bodyElementProps = _.extend({}, this.state, this.props, { // Complete set of own props, own state, + extras.
Expand All @@ -1170,11 +1174,11 @@ export default class App extends React.PureComponent {
"img-src 'self' https://* data: www.google-analytics.com abs.twimg.com https://pbs.twimg.com ton.twimg.com platform.twitter.com https://syndication.twitter.com",
"child-src blob:",
"frame-src https://twitter.com platform.twitter.com syndication.twitter.com www.google.com/recaptcha/",
"script-src 'self' www.google-analytics.com https://cdn.auth0.com https://hms-dbmi.auth0.com https://secure.gravatar.com https://cdn.syndication.twimg.com platform.twitter.com https://www.gstatic.com/recaptcha/ https://www.google.com/recaptcha/ 'unsafe-eval'", // + (typeof BUILDTYPE === "string" && BUILDTYPE === "quick" ? " 'unsafe-eval'" : ""),
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://unpkg.com https://ton.twimg.com platform.twitter.com",
"script-src 'self' www.google-analytics.com www.googletagmanager.com https://cdn.auth0.com https://hms-dbmi.auth0.com https://secure.gravatar.com https://cdn.syndication.twimg.com platform.twitter.com https://www.gstatic.com/recaptcha/ https://www.google.com/recaptcha/ 'unsafe-eval'", // + (typeof BUILDTYPE === "string" && BUILDTYPE === "quick" ? " 'unsafe-eval'" : ""),
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://unpkg.com https://ton.twimg.com platform.twitter.com https://www.googletagmanager.com",
"font-src 'self' https://fonts.gstatic.com",
"worker-src 'self' blob:",
"connect-src 'self' * blob: https://raw.githubusercontent.com https://higlass.4dnucleome.org https://*.s3.amazonaws.com https://s3.amazonaws.com/4dn-dcic-public/ https://www.encodeproject.org https://rest.ensembl.org https://www.google-analytics.com https://o427308.ingest.sentry.io https://www.gstatic.com/recaptcha/ https://www.google.com/recaptcha/ 'unsafe-inline' 'unsafe-eval'"
"connect-src 'self' * blob: https://raw.githubusercontent.com https://higlass.4dnucleome.org https://*.s3.amazonaws.com https://s3.amazonaws.com/4dn-dcic-public/ https://www.encodeproject.org https://rest.ensembl.org https://www.google-analytics.com https://www.googletagmanager.com https://o427308.ingest.sentry.io https://www.gstatic.com/recaptcha/ https://www.google.com/recaptcha/ 'unsafe-inline' 'unsafe-eval'"
].join("; ");

// `lastCSSBuildTime` is used for both CSS and JS because is most likely they change at the same time on production from recompiling
Expand All @@ -1196,10 +1200,11 @@ export default class App extends React.PureComponent {
<link rel="preconnect" href="https://unpkg.com" />
<link rel="preconnect" href="https://fonts.googleapis.com/" />
<link rel="preconnect" href="//www.google-analytics.com" />
<link rel="preconnect" href="//www.googletagmanager.com" />
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/rc-tabs.min.css" type="text/css" />
<SEO.CurrentContext {...{ context, hrefParts, baseDomain }} />
<link href="https://fonts.googleapis.com/css?family=Mada:200,300,400,500,600,700,900|Yrsa|Source+Code+Pro:300,400,500,600" rel="stylesheet" type="text/css"/>
<script defer type="application/javascript" src="//www.google-analytics.com/analytics.js" />
{gtag4Script && <script async type="application/javascript" src={gtag4Script} />}
<script defer type="application/javascript" src={"/static/build/bundle.js?build=" + (lastCSSBuildTime || 0)} charSet="utf-8" />
<link rel="canonical" href={canonical} />
{/* <script data-prop-name="inline" type="application/javascript" charSet="utf-8" dangerouslySetInnerHTML={{__html: this.props.inline}}/> <-- SAVED FOR REFERENCE */}
Expand Down
4 changes: 2 additions & 2 deletions src/encoded/static/components/browse/PublicationSearchView.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ class PublicationSearchResultTitle extends React.PureComponent {
evt.preventDefault();
evt.stopPropagation();
analytics.productClick(result, {
'list' : analytics.hrefToListName(href),
'position' : rowNumber + 1
'list_name' : analytics.hrefToListName(href),
'index' : rowNumber + 1
}, function(){
navigate(object.itemUtil.atId(result));
});
Expand Down
10 changes: 5 additions & 5 deletions src/encoded/static/components/browse/components/FacetCharts.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,11 @@ export class FacetCharts extends React.PureComponent {

// Register 'Set Filter' event for each field:term pair (node) of selected Bar Section.
_.forEach(cursorProps.path, function(node){
analytics.event('BarPlot', 'Set Filter', {
'eventLabel' : analytics.eventLabelFromChartNode(node, false), // 'New' filter logged here.
'field' : node.field,
'term' : node.term,
'currentFilters' : analytics.getStringifiedCurrentFilters(currExpSetFilters), // 'Existing' filters, or filters at time of action, go here.
analytics.event('cursor_detail', 'BarPlot', 'Set Filter', null, {
'name' : analytics.eventLabelFromChartNode(node, false), // 'New' filter logged here.
'field_key' : node.field,
'term_key' : node.term,
'filters' : analytics.getStringifiedCurrentFilters(currExpSetFilters), // 'Existing' filters, or filters at time of action, go here.
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,20 @@ export class SelectedFilesController extends React.PureComponent {
});
if (existingFileList.length > 0) {
setTimeout(function(){
const extData = { list: analytics.hrefToListName(window && window.location.href) };
analytics.productsAddToCart(existingFileList, extData);
//analytics
const extData = { item_list_name: analytics.hrefToListName(window && window.location.href) };
const products = analytics.transformItemsToProducts(existingFileList, extData);
const productsLength = Array.isArray(products) ? products.length : existingFileList.length;
analytics.event(
"add_to_cart",
"SelectedFilesController",
"Select Files",
function() { console.info(`Adding ${productsLength} items to cart.`); },
{
eventLabel: extData.list,
eventValue: existingFileList.length,
currentFilters: analytics.getStringifiedCurrentFilters((context && context.filters) || null)
items: Array.isArray(products) ? products : null,
list_name: extData.item_list_name,
value: productsLength,
filters: analytics.getStringifiedCurrentFilters((context && context.filters) || null)
}
);
}, 250);
Expand All @@ -155,9 +160,13 @@ export class SelectedFilesController extends React.PureComponent {
logger.error("Supplied accessionTriple is not a string or array of strings/arrays:", accessionTriple);
throw new Error("Supplied accessionTriple is not a string or array of strings/arrays:", accessionTriple);
}
const newlyAddedFileItems = [];
//it is initialized inside of setState, since setState is called twice
//in strict mode and newlyAddedFileItems would have duplicate items
//more info: https://github.com/facebook/react/issues/12856#issuecomment-390206425
let newlyAddedFileItems = null;
this.setState(({ selectedFiles })=>{
var newSelectedFiles = _.extend({}, selectedFiles);
newlyAddedFileItems = [];
const newSelectedFiles = _.extend({}, selectedFiles);

function add(id, fileItemCurr = null){
if (typeof newSelectedFiles[id] !== 'undefined'){
Expand Down Expand Up @@ -187,15 +196,20 @@ export class SelectedFilesController extends React.PureComponent {
if (!analyticsAddFilesToCart){
return;
}
const extData = { list: analytics.hrefToListName(window && window.location.href) };
analytics.productsAddToCart(newlyAddedFileItems, extData);
//analytics
const extData = { item_list_name: analytics.hrefToListName(window && window.location.href) };
const products = analytics.transformItemsToProducts(newlyAddedFileItems, extData);
const productsLength = Array.isArray(products) ? products.length : accessionTriple.length;
analytics.event(
"add_to_cart",
"SelectedFilesController",
"Select Files",
function () { console.info(`Adding ${productsLength} items to cart.`); },
{
eventLabel: extData.list,
eventValue: newlyAddedFileItems.length,
currentFilters: analytics.getStringifiedCurrentFilters((context && context.filters) || null)
items: Array.isArray(products) ? products : null,
list_name: extData.item_list_name,
value: productsLength,
filters: analytics.getStringifiedCurrentFilters((context && context.filters) || null)
}
);
});
Expand Down Expand Up @@ -240,15 +254,20 @@ export class SelectedFilesController extends React.PureComponent {
if (!analyticsAddFilesToCart){
return;
}
const extData = { list: analytics.hrefToListName(window && window.location.href) };
analytics.productsRemoveFromCart(newlyRemovedFileItems, extData);
//analytics
const extData = { item_list_name: analytics.hrefToListName(window && window.location.href) };
const products = analytics.transformItemsToProducts(newlyRemovedFileItems, extData);
const productsLength = Array.isArray(products) ? products.length : newlyRemovedFileItems.length;
analytics.event(
"remove_from_cart",
"SelectedFilesController",
"Unselect Files",
function () { console.info(`Removing ${productsLength} items from cart.`); },
{
eventLabel: extData.list,
eventValue: newlyRemovedFileItems.length,
currentFilters: analytics.getStringifiedCurrentFilters((context && context.filters) || null)
items: Array.isArray(products) ? products : null,
list_name: extData.item_list_name,
value: productsLength,
filters: analytics.getStringifiedCurrentFilters((context && context.filters) || null)
}
);
});
Expand All @@ -266,15 +285,20 @@ export class SelectedFilesController extends React.PureComponent {
if (!analyticsAddFilesToCart || existingFileList.length === 0){
return;
}
const extData = { list: analytics.hrefToListName(window && window.location.href) };
analytics.productsRemoveFromCart(existingFileList, extData);
//analytics
const extData = { item_list_name: analytics.hrefToListName(window && window.location.href) };
const products = analytics.transformItemsToProducts(existingFileList, extData);
const productsLength = Array.isArray(products) ? products.length : existingFileList.length;
analytics.event(
"remove_from_cart",
"SelectedFilesController",
"Unselect All Files",
function () { console.info(`Removing ${productsLength} items from cart.`); },
{
eventLabel: extData.list,
eventValue: existingFileList.length,
currentFilters: analytics.getStringifiedCurrentFilters((context && context.filters) || null)
items: Array.isArray(products) ? products : null,
list_name: extData.item_list_name,
value: productsLength,
filters: analytics.getStringifiedCurrentFilters((context && context.filters) || null)
}
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class SelectAllFilesButton extends React.PureComponent {
}

this.setState({ 'selecting' : true }, () => {
const extData = { list: analytics.hrefToListName(window && window.location.href) };
const extData = { item_list_name: analytics.hrefToListName(window && window.location.href) };

if (!this.isAllSelected()){
const currentHrefParts = memoizedUrlParse(href);
Expand All @@ -97,7 +97,7 @@ export class SelectAllFilesButton extends React.PureComponent {
ajax.load(reqHref, (resp)=>{
let allExtendedFiles;
let filesToSelect;
if (extData.list === 'browse') {
if (extData.item_list_name === 'browse') {

allExtendedFiles = _.reduce(resp['@graph'] || [], (m, v) => m.concat(allFilesFromExperimentSet(v, true)), []);
filesToSelect = _.zip(filesToAccessionTriples(allExtendedFiles, true, true), allExtendedFiles);
Expand All @@ -108,13 +108,19 @@ export class SelectAllFilesButton extends React.PureComponent {
selectFile(filesToSelect);
this.setState({ 'selecting' : false });

//analytics
const products = analytics.transformItemsToProducts(allExtendedFiles, extData);
const productsLength = Array.isArray(products) ? products.length : allExtendedFiles.length;
analytics.event(
"add_to_cart",
"SelectAllFilesButton",
"Select All",
function () { console.info(`Adding ${productsLength} items from cart.`); },
{
eventLabel: extData.list,
eventValue: totalFilesCount,
currentFilters: analytics.getStringifiedCurrentFilters((context && context.filters) || null)
items: Array.isArray(products) ? products : null,
list_name: extData.item_list_name,
value: productsLength,
filters: analytics.getStringifiedCurrentFilters((context && context.filters) || null)
}
);
});
Expand Down
Loading

0 comments on commit 6cecddf

Please sign in to comment.