diff --git a/packages/client/src/helpers/gtag.js b/packages/client/src/helpers/gtag.js
index 0225ba6eb..08e22d15e 100644
--- a/packages/client/src/helpers/gtag.js
+++ b/packages/client/src/helpers/gtag.js
@@ -2,6 +2,17 @@
// Note that the 'set' command does not appear to do anything (despite Google documentation to the contrary) so all
// runtime reconfiguration is being performed via 'config'.
+function gtag(...args) {
+ if (typeof window.gtag === 'function' && window.APP_CONFIG?.GOOGLE_TAG_ID) {
+ window.gtag(...args);
+ if (window.APP_CONFIG?.GOOGLE_ANALYTICS_DEBUG) {
+ console.log(`[Google Analytics] Emitted ${args[0]} with:`, ...args.slice(1));
+ }
+ } else if (window.APP_CONFIG?.GOOGLE_ANALYTICS_DEBUG) {
+ console.log(`[Google Analytics][DISABLED] Would have emitted ${args[0]} with:`, ...args.slice(1));
+ }
+}
+
/**
* If Google Analytics are enabled, calls the config command on the appropriate Google tag.
* If called repeatedly, appears to update the config by performing a deep merge of the existing config with the new.
@@ -13,12 +24,10 @@
*/
export function gtagConfig(config) {
// See index.html for the definition of the gtag function.
- if (typeof window.gtag === 'function' && window.APP_CONFIG?.GOOGLE_TAG_ID) {
- window.gtag('config', window.APP_CONFIG.GOOGLE_TAG_ID, {
- ...(window.APP_CONFIG?.GOOGLE_ANALYTICS_DEBUG ? { debug_mode: true } : {}),
- ...config,
- });
- }
+ gtag('config', window.APP_CONFIG.GOOGLE_TAG_ID, {
+ ...(window.APP_CONFIG?.GOOGLE_ANALYTICS_DEBUG ? { debug_mode: true } : {}),
+ ...config,
+ });
}
export function setUserForGoogleAnalytics(user) {
@@ -30,3 +39,14 @@ export function setUserForGoogleAnalytics(user) {
},
});
}
+
+/**
+ * Emits a Google Analytics GA4 custom event. Google also defines some reserved param names to be aware of,
+ * such as `event_callback` to execute code once the event has been logged: https://developers.google.com/tag-platform/gtagjs/reference/parameters
+ *
+ * @param {string} eventName Canonical name of the event to emit
+ * @param {object} eventParams Metadata to associate with this event
+ */
+export function gtagEvent(eventName, eventParams) {
+ gtag('event', eventName, eventParams);
+}
diff --git a/packages/client/src/views/GrantDetails.vue b/packages/client/src/views/GrantDetails.vue
index d1ca7bfdc..136e9d443 100644
--- a/packages/client/src/views/GrantDetails.vue
+++ b/packages/client/src/views/GrantDetails.vue
@@ -49,6 +49,7 @@
target="_blank"
rel="noopener noreferrer"
data-dd-action-name="view on grants.gov"
+ @click="onOpenGrantsGov"
>
Apply on Grants.gov
@@ -57,6 +58,7 @@
@@ -65,6 +67,7 @@
@@ -164,6 +167,7 @@ import { debounce } from 'lodash';
import { newTerminologyEnabled } from '@/helpers/featureFlags';
import { formatCurrency } from '@/helpers/currency';
import { titleize } from '@/helpers/form-helpers';
+import { gtagEvent } from '@/helpers/gtag';
import { DateTime } from 'luxon';
import UserAvatar from '@/components/UserAvatar.vue';
@@ -380,7 +384,12 @@ export default {
agencyId: this.selectedAgencyId,
interestedCode: this.selectedInterestedCode,
});
- datadogRum.addAction('submit team status for grant', { team: { id: this.selectedAgencyId }, status: this.selectedInterestedCode, grant: { id: this.selectedGrant.grant_id } });
+ const eventName = 'submit team status for grant';
+ const eventParams = {
+ status_name: this.interestedOptions.find((option) => option.id === this.selectedInterestedCode)?.name,
+ };
+ gtagEvent(eventName, eventParams);
+ datadogRum.addAction(eventName, eventParams);
this.selectedInterestedCode = null;
}
},
@@ -391,14 +400,18 @@ export default {
interestedCode: agency.interested_code_id,
});
this.selectedGrant.interested_agencies = await this.getInterestedAgencies({ grantId: this.selectedGrant.grant_id });
- datadogRum.addAction('remove team status for grant', { team: { id: agency.agency_id }, status: agency.interested_code_id, grant: { id: this.selectedGrant.grant_id } });
+ const eventName = 'remove team status for grant';
+ gtagEvent(eventName);
+ datadogRum.addAction(eventName);
},
async assignAgenciesToGrant() {
await this.assignAgenciesToGrantAction({
grantId: this.selectedGrant.grant_id,
agencyIds: this.assignedAgencies.map((agency) => agency.id).concat(this.selectedAgencyToAssign.id),
});
- datadogRum.addAction('assign team to grant', { team: { id: this.selectedAgencyToAssign.id }, grant: { id: this.selectedGrant.grant_id } });
+ const eventName = 'assign team to grant';
+ gtagEvent(eventName);
+ datadogRum.addAction(eventName);
this.selectedAgencyToAssign = null;
this.assignedAgencies = await this.getGrantAssignedAgencies({ grantId: this.selectedGrant.grant_id });
},
@@ -408,7 +421,9 @@ export default {
agencyIds: [agency.id],
});
this.assignedAgencies = await this.getGrantAssignedAgencies({ grantId: this.selectedGrant.grant_id });
- datadogRum.addAction('remove team assignment from grant', { team: { id: this.selectedAgencyId }, grant: { id: this.selectedGrant.grant_id } });
+ const eventName = 'remove team assignment from grant';
+ gtagEvent(eventName);
+ datadogRum.addAction(eventName);
},
isAbleToUnmark(agencyId) {
return this.agencies.some((agency) => agency.id === agencyId);
@@ -424,6 +439,7 @@ export default {
});
},
copyUrl() {
+ gtagEvent('copy btn clicked');
navigator.clipboard.writeText(window.location.href);
// Show the success indicator
@@ -435,8 +451,14 @@ export default {
);
},
printPage() {
+ gtagEvent('print btn clicked');
window.print();
},
+ onOpenGrantsGov() {
+ // Note that we can execute this as a side effect of clicking on the outbound link only because it's set to
+ // load in a new tab. If it opened in the same tab, it would open the new URL before the event is logged.
+ gtagEvent('grants.gov btn clicked');
+ },
formatDate(dateString) {
return DateTime.fromISO(dateString).toLocaleString(DateTime.DATE_MED);
},