From 671521093a67092f53b4808dad4bf48aac69d767 Mon Sep 17 00:00:00 2001 From: charles-ramos Date: Fri, 17 Jul 2026 15:11:25 -0300 Subject: [PATCH] GTM event - deep_activation --- .../Data/ApiConsole/GraphQLConsole.react.js | 7 ++++++ .../Data/ApiConsole/RestConsole.react.js | 3 +++ src/dashboard/Data/Browser/Browser.react.js | 9 +++++++- .../Data/CloudCode/B4ACloudCode.react.js | 4 +++- .../Data/Playground/Playground.react.js | 3 +++ src/lib/gtm.js | 22 +++++++++++++++++++ 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js b/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js index 21f63ee0d2..96d16ffe26 100644 --- a/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js +++ b/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js @@ -11,6 +11,7 @@ import EmptyState from 'components/EmptyState/EmptyState.react'; import Toolbar from 'components/Toolbar/Toolbar.react'; import styles from 'dashboard/Data/ApiConsole/ApiConsole.scss'; import { CurrentApp } from 'context/currentApp'; +import { fireDeepActivation } from 'lib/gtm.js'; export default class GraphQLConsole extends Component { static contextType = CurrentApp; @@ -51,6 +52,12 @@ export default class GraphQLConsole extends Component { }, body: JSON.stringify(graphQLParams), }); + // Deep activation: user sent a query from the GraphQL console. + // GraphiQL auto-fires an IntrospectionQuery on load to build the + // schema/docs, so ignore it and only count real user queries. + if (graphQLParams.operationName !== 'IntrospectionQuery') { + fireDeepActivation(); + } return data.json().catch(() => data.text()); }} /> diff --git a/src/dashboard/Data/ApiConsole/RestConsole.react.js b/src/dashboard/Data/ApiConsole/RestConsole.react.js index 15cc7eef06..3981a18d4a 100644 --- a/src/dashboard/Data/ApiConsole/RestConsole.react.js +++ b/src/dashboard/Data/ApiConsole/RestConsole.react.js @@ -19,6 +19,7 @@ import Option from 'components/Dropdown/Option.react'; import Parse from 'parse'; import React, { Component } from 'react'; import request from 'dashboard/Data/ApiConsole/request'; +import { fireDeepActivation } from 'lib/gtm.js'; import styles from 'dashboard/Data/ApiConsole/RestConsole.scss'; import TextInput from 'components/TextInput/TextInput.react'; import B4aToggle from 'components/Toggle/B4aToggle.react'; @@ -124,6 +125,8 @@ export default class RestConsole extends Component { request(this.context, this.state.method, endpoint, payload, options).then(response => { this.setState({ response }); document.body.scrollTop = 540; + // Deep activation: user sent a query from the REST console. + fireDeepActivation(); }); } diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index ab0cdd1000..f6bca77d09 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -60,7 +60,7 @@ import generatePath from 'lib/generatePath'; import { withRouter } from 'lib/withRouter'; import Icon from 'components/Icon/Icon.react'; import { amplitudeLogEvent } from 'lib/amplitudeEvents'; -import { pushGTMEvent } from 'lib/gtm.js' +import { pushGTMEvent, fireDeepActivation } from 'lib/gtm.js' import { get } from 'jquery'; const BROWSER_LAST_LOCATION = 'b4a_brower_last_location'; @@ -902,6 +902,8 @@ class Browser extends DashboardView { }).then(() => { // Send track event // back4AppNavigation && back4AppNavigation.createClassClickEvent() + // Deep activation: user touched the DB structure by creating a class. + fireDeepActivation(); }).catch(error => { let errorDeletingNote = 'Internal server error' if (error.code === 403) {errorDeletingNote = error.message;} @@ -996,6 +998,8 @@ class Browser extends DashboardView { return this.props.schema .dispatch(ActionTypes.ADD_COLUMN, payload) .then(() => { + // Deep activation: user touched the DB structure by creating a column. + fireDeepActivation(); if (required) { const requiredCols = [...this.state.requiredColumnFields, name]; this.setState({ @@ -1114,6 +1118,9 @@ class Browser extends DashboardView { const msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' created'; this.showNote(msg, false); + // Deep activation: user added data by creating a row. + fireDeepActivation(); + const state = { data: this.state.data }; const relation = this.state.relation; if (relation) { diff --git a/src/dashboard/Data/CloudCode/B4ACloudCode.react.js b/src/dashboard/Data/CloudCode/B4ACloudCode.react.js index 7c7d26a711..c8942e068d 100644 --- a/src/dashboard/Data/CloudCode/B4ACloudCode.react.js +++ b/src/dashboard/Data/CloudCode/B4ACloudCode.react.js @@ -20,7 +20,7 @@ import Icon from 'components/Icon/Icon.react'; import B4aModal from 'components/B4aModal/B4aModal.react'; import { withRouter } from 'lib/withRouter'; import CloudCodeChanges from 'lib/CloudCodeChanges'; -import { pushGTMEvent } from 'lib/gtm.js'; +import { pushGTMEvent, fireDeepActivation } from 'lib/gtm.js'; @withRouter class B4ACloudCode extends CloudCode { @@ -209,6 +209,8 @@ class B4ACloudCode extends CloudCode { }) // eslint-disable-next-line no-undef // back4AppNavigation && back4AppNavigation.deployCloudCodeEvent() + // Deep activation: user deployed cloud code. + fireDeepActivation(); await this.fetchSource(); // force jstree component to upload await updateTreeContent(this.state.files); diff --git a/src/dashboard/Data/Playground/Playground.react.js b/src/dashboard/Data/Playground/Playground.react.js index d88ab73936..9a9fc561ab 100644 --- a/src/dashboard/Data/Playground/Playground.react.js +++ b/src/dashboard/Data/Playground/Playground.react.js @@ -9,6 +9,7 @@ import SaveButton from 'components/SaveButton/SaveButton.react'; import Swal from 'sweetalert2'; import Toolbar from 'components/Toolbar/Toolbar.react'; import { CurrentApp } from 'context/currentApp'; +import { fireDeepActivation } from 'lib/gtm.js'; import styles from './Playground.scss'; @@ -93,6 +94,8 @@ export default class Playground extends Component { this.setState({ running: true, results: [] }); await new Function('Parse', finalCode)(Parse); + // Deep activation: user ran a query from the JavaScript console. + fireDeepActivation(); } catch (e) { console.error(e); } finally { diff --git a/src/lib/gtm.js b/src/lib/gtm.js index ebf36f2849..20499cffb4 100644 --- a/src/lib/gtm.js +++ b/src/lib/gtm.js @@ -1,4 +1,6 @@ import {SHA256} from 'crypto-js'; +import AccountManager from 'lib/AccountManager'; +import { post } from 'lib/AJAX'; export const pushGTMEvent = (eventName, includeEmail = false) => { const email = includeEmail ? localStorage.getItem('username') || '' : '' @@ -7,3 +9,23 @@ export const pushGTMEvent = (eventName, includeEmail = false) => { window.dataLayer.push({ event: eventName, email: hashedEmail}) } } + +// Fires the `deep_activation` GTM event once per user, ever. It is triggered by +// "deep activation" actions (touching the DB structure/data, deploying cloud +// code, or sending a REST query). The `deepActivationFired` flag is persisted on +// the account so it only ever fires a single time, mirroring the once-per-user +// server flags (see `parseDashboardMobileAlertShown` in DashboardView). +export const fireDeepActivation = () => { + const user = AccountManager.currentUser(); + if (!user || user.deepActivationFired) { + return; + } + if (window && window.dataLayer) { + window.dataLayer.push({ event: 'deep_activation' }); + } + // Optimistically flip the flag locally so it does not fire again this session, + // then persist it on the account so it never fires again for this user. + user.deepActivationFired = true; + AccountManager.setCurrentUser({ user }); + post('/b4aUser/parseDashboardDeepActivation').catch(() => {}); +}