Use standard event names for page_view

Remove fnSend and send all captured events through ga4-events
Change the version to minor
Fix package.json
Changes to test to make it pass

Signed-off-by: sriram ramakrishnan <sramakr@gmail.com>
This commit is contained in:
sriram ramakrishnan
2023-03-29 19:27:36 -04:00
parent 22b46f7f56
commit db64baf57b
13 changed files with 262 additions and 252 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
---
'@backstage/plugin-analytics-module-ga4': major
'@backstage/plugin-analytics-module-ga4': minor
---
Plugin provides Backstage Analytics API for Google Analytics 4. Once installed and configured, analytics events will be sent to GA4 as your users navigate and use your Backstage instance
+1 -47
View File
@@ -1,47 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
plugins: ['jsdoc'],
rules: {
'jsdoc/check-access': 1,
'jsdoc/check-alignment': 1,
'jsdoc/check-param-names': ['warn', { checkDestructured: false }],
'jsdoc/check-property-names': 1,
'jsdoc/check-tag-names': ['warn', { definedTags: ['visibility'] }],
'jsdoc/check-types': 1,
'jsdoc/check-values': 1,
'jsdoc/empty-tags': 1,
'jsdoc/implements-on-classes': 1,
'jsdoc/multiline-blocks': 1,
'jsdoc/no-multi-asterisks': 1,
'jsdoc/no-types': 1,
'jsdoc/no-undefined-types': 1,
'jsdoc/require-asterisk-prefix': 1,
'jsdoc/require-description': 1,
'jsdoc/require-jsdoc': [
'warn',
{
publicOnly: true,
require: {
FunctionDeclaration: true,
MethodDefinition: true,
ClassDeclaration: true,
ArrowFunctionExpression: true,
FunctionExpression: true,
},
contexts: ['ExportNamedDeclaration > VariableDeclaration'],
},
],
'jsdoc/require-param': ['warn', { checkDestructured: false }],
'jsdoc/require-param-description': 1,
'jsdoc/require-param-name': 1,
'jsdoc/require-property': 1,
'jsdoc/require-property-description': 1,
'jsdoc/require-property-name': 1,
'jsdoc/require-property-type': 1,
'jsdoc/valid-types': 1,
},
settings: {
jsdoc: {
tagNamePreference: { 'tag constructor': 'constructor' },
},
},
});
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+2 -3
View File
@@ -62,9 +62,8 @@ backend:
## Configuration
In order to be able to analyze usage of your Backstage instance _by plugin_, we
strongly recommend configuring at least one [custom dimension][what-is-a-custom-dimension]
to capture Plugin IDs associated with events, including page views.
In order to be able to analyze usage of your Backstage instance by plugin, we recommend configuring [a content grouping](#enabling-content-grouping).
Additional dimensional data can be captured using custom dimensions, like this:
1. First, [configure the custom dimension in GA] [configure-custom-dimension].
Be sure to set the Scope to `Event`, and name it `dimension1`.
+2 -34
View File
@@ -46,38 +46,6 @@ export interface Config {
*/
identity?: 'disabled' | 'optional' | 'required';
/**
* Controls whether to send virtual pageviews on `search` events.
* Can be used to enable Site Search in GA.
*/
virtualSearchPageView?: {
/**
* - `disabled`: (Default) no virtual pageviews are sent
* - `only`: Sends virtual pageview _instead_ of the `search` event
* - `both`: Sends both the `search` event _and_ the virtual pageview
* @visibility frontend
*/
mode?: 'disabled' | 'only' | 'both';
/**
* Specifies on which path the main Search page is mounted.
* Defaults to `/search`.
* @visibility frontend
*/
mountPath?: string;
/**
* Specifies which query param is used for the term query in the virtual pageview URL.
* Defaults to `query`.
* @visibility frontend
*/
searchQuery?: string;
/**
* Specifies which query param is used for the category query in the virtual pageview URL.
* Skipped by default.
* @visibility frontend
*/
categoryQuery?: string;
};
/**
* Whether to log analytics debug statements to the console.
* Defaults to false.
@@ -114,14 +82,14 @@ export interface Config {
* context-name will be prefixed by c_, for example, pluginId will be c_pluginId in the event.
*
*/
allowedContexts?: Array<string>;
allowedContexts?: string[] | ['*'];
/**
*
* Attributes that will be sent as parameters in the event
* attribute-name will be prefixed by a_, for example , testAttribute will be c_testAttribute in the event.
*
*/
allowedAttributes?: Array<string>;
allowedAttributes?: string[] | ['*'];
};
};
};
+17 -15
View File
@@ -1,10 +1,9 @@
{
"name": "@backstage/plugin-analytics-module-ga4",
"version": "0.1.19-next.1",
"version": "0.0.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
@@ -18,37 +17,40 @@
"start": "backstage-cli package start",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"diff": "backstage-cli plugin:diff",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"clean": "backstage-cli package clean"
},
"dependencies": {
"@backstage/config": "^1.0.6",
"@backstage/core-components": "^0.12.4",
"@backstage/core-plugin-api": "^1.4.0",
"@backstage/theme": "^0.2.16",
"@backstage/config": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/theme": "workspace:^",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"@material-ui/lab": "4.0.0-alpha.61",
"react-ga4": "^2.0.0",
"react-use": "^17.2.4"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
"react": "^16.13.1 || ^17.0.0",
"react-dom": "^16.13.1 || ^17.0.0",
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
},
"devDependencies": {
"@backstage/cli": "^0.22.3",
"@backstage/core-app-api": "^1.5.0",
"@backstage/dev-utils": "^1.0.12",
"@backstage/test-utils": "^1.2.5",
"@testing-library/jest-dom": "^5.16.5",
"@backstage/cli": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/dev-utils": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@testing-library/dom": "^8.0.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^14.0.0",
"@types/jest": "^26.0.7",
"@types/node": "^16.11.26",
"@types/react": "^16.13.1 || ^17.0.0",
"cross-fetch": "^3.1.5",
"msw": "^0.44.0"
"msw": "^1.0.0"
},
"files": [
"dist",
@@ -1,3 +1,18 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import { IdentityApi } from '@backstage/core-plugin-api';
import ReactGA from 'react-ga4';
@@ -19,12 +34,6 @@ fnSet.mockImplementation((fieldObject: any) => {
return;
});
const fnSend = jest.spyOn(ReactGA, 'send');
// @ts-ignore
fnSend.mockImplementation((fieldObject: any) => {
return;
});
afterEach(() => {
jest.clearAllMocks();
});
@@ -38,7 +47,9 @@ describe('GoogleAnalytics4', () => {
};
const measurementId = 'G-000000-0';
const basicValidConfig = new ConfigReader({
app: { analytics: { ga4: { measurementId: measurementId, testMode: true } } },
app: {
analytics: { ga4: { measurementId: measurementId, testMode: true } },
},
});
describe('fromConfig', () => {
@@ -59,9 +70,10 @@ describe('GoogleAnalytics4', () => {
subject: '/',
context,
});
expect(fnSend).toHaveBeenCalledWith({
hitType: 'pageview',
page: '/',
expect(fnEvent).toHaveBeenCalledWith('page_view', {
action: 'page_view',
label: '/',
category: 'App',
});
});
});
@@ -107,6 +119,19 @@ describe('GoogleAnalytics4', () => {
},
});
const allowAllContextsAndAttrsConfig = new ConfigReader({
app: {
analytics: {
ga4: {
measurementId: measurementId,
testMode: true,
allowedContexts: ['*'],
allowedAttributes: ['*'],
},
},
},
});
it('testing content grouping', () => {
const api = GoogleAnalytics4.fromConfig(configWithContentGrouping);
api.captureEvent({
@@ -115,9 +140,11 @@ describe('GoogleAnalytics4', () => {
context,
});
expect(fnSend).toHaveBeenCalledWith({
hitType: 'pageview',
page: '/a-page',
expect(fnEvent).toHaveBeenCalledWith('page_view', {
action: 'page_view',
label: '/a-page',
category: 'App',
value: undefined,
content_group: context.pluginId,
});
});
@@ -133,12 +160,11 @@ describe('GoogleAnalytics4', () => {
value: expectedValue,
context,
});
expect(fnSend).toHaveBeenCalledWith({
hitType: 'event',
eventAction: 'search',
eventCategory: 'App',
eventLabel: 'search-term',
eventValue: 42,
expect(fnEvent).toHaveBeenCalledWith('search', {
action: 'search',
category: 'App',
label: 'search-term',
value: 42,
search_term: 'search-term',
});
});
@@ -156,12 +182,11 @@ describe('GoogleAnalytics4', () => {
context,
});
expect(fnSend).toHaveBeenCalledWith({
hitType: 'event',
eventCategory: context.extension,
eventAction: expectedAction,
eventLabel: expectedLabel,
eventValue: expectedValue,
expect(fnEvent).toHaveBeenCalledWith('click', {
action: 'click',
category: context.extension,
label: 'on something',
value: expectedValue,
});
});
@@ -173,14 +198,41 @@ describe('GoogleAnalytics4', () => {
context,
});
expect(fnSend).toHaveBeenCalledWith({
hitType: 'pageview',
page: '/a-page',
expect(fnEvent).toHaveBeenCalledWith('page_view', {
action: 'page_view',
label: '/a-page',
category: 'App',
value: undefined,
c_pluginId: context.pluginId,
c_releaseNum: context.releaseNum,
});
});
it('captures all dimensions/metrics on pageviews', () => {
const api = GoogleAnalytics4.fromConfig(allowAllContextsAndAttrsConfig);
api.captureEvent({
action: 'navigate',
subject: '/a-page',
context,
attributes: {
'attr-1': 'attr-value-1',
'attr-2': 'attr-value-2',
},
});
expect(fnEvent).toHaveBeenCalledWith('page_view', {
action: 'page_view',
category: 'App',
label: '/a-page',
value: undefined,
c_pluginId: context.pluginId,
c_releaseNum: context.releaseNum,
c_routeRef: context.routeRef,
c_extension: context.extension,
'a_attr-1': 'attr-value-1',
'a_attr-2': 'attr-value-2',
});
});
it('captures configured custom dimensions/metrics on events', () => {
const api = GoogleAnalytics4.fromConfig(advancedConfig);
@@ -198,12 +250,11 @@ describe('GoogleAnalytics4', () => {
context,
});
expect(fnSend).toHaveBeenCalledWith({
hitType: 'event',
eventCategory: context.extension,
eventAction: expectedAction,
eventLabel: expectedLabel,
eventValue: expectedValue,
expect(fnEvent).toHaveBeenCalledWith('search', {
action: 'search',
category: context.extension,
label: expectedLabel,
value: expectedValue,
c_pluginId: context.pluginId,
c_releaseNum: context.releaseNum,
search_term: expectedLabel,
@@ -223,9 +274,9 @@ describe('GoogleAnalytics4', () => {
});
expect(fnEvent).not.toHaveBeenCalledWith({
eventCategory: context.extension,
eventAction: 'verb',
eventLabel: 'noun',
category: context.extension,
action: 'verb',
label: 'noun',
c_pluginId: context.pluginId,
c_releaseNum: context.releaseNum,
c_extraMetric: 'not a number',
@@ -262,7 +313,11 @@ describe('GoogleAnalytics4', () => {
const optionalConfig = new ConfigReader({
app: {
analytics: {
ga4: { measurementId: measurementId, testMode: true, identity: 'optional' },
ga4: {
measurementId: measurementId,
testMode: true,
identity: 'optional',
},
},
},
});
@@ -288,7 +343,11 @@ describe('GoogleAnalytics4', () => {
const optionalConfig = new ConfigReader({
app: {
analytics: {
ga4: { measurementId: measurementId, testMode: true, identity: 'optional' },
ga4: {
measurementId: measurementId,
testMode: true,
identity: 'optional',
},
},
},
});
@@ -317,7 +376,11 @@ describe('GoogleAnalytics4', () => {
const disabledConfig = new ConfigReader({
app: {
analytics: {
ga4: { measurementId: measurementId, testMode: true, identity: 'disabled' },
ga4: {
measurementId: measurementId,
testMode: true,
identity: 'disabled',
},
},
},
});
@@ -332,9 +395,11 @@ describe('GoogleAnalytics4', () => {
await new Promise(resolve => setTimeout(resolve));
// A pageview should have been fired immediately.
expect(fnSend).toHaveBeenCalledWith({
hitType: 'pageview',
page: '/',
expect(fnEvent).toHaveBeenCalledWith('page_view', {
action: 'page_view',
label: '/',
category: 'App',
value: undefined,
});
// There should not have been a UserID set.
@@ -346,7 +411,11 @@ describe('GoogleAnalytics4', () => {
const requiredConfig = new ConfigReader({
app: {
analytics: {
ga4: { measurementId: measurementId, testMode: true, identity: 'required' },
ga4: {
measurementId: measurementId,
testMode: true,
identity: 'required',
},
},
},
});
@@ -359,7 +428,11 @@ describe('GoogleAnalytics4', () => {
const requiredConfig = new ConfigReader({
app: {
analytics: {
ga4: { measurementId: measurementId, testMode: true, identity: 'required' },
ga4: {
measurementId: measurementId,
testMode: true,
identity: 'required',
},
},
},
});
@@ -387,20 +460,21 @@ describe('GoogleAnalytics4', () => {
});
// Then a pageview should have been fired with a queue time.
expect(fnSend).toHaveBeenCalledWith({
hitType: 'pageview',
page: '/',
expect(fnEvent).toHaveBeenCalledWith('page_view', {
action: 'page_view',
label: '/',
timestamp_micros: expect.any(Number),
value: undefined,
category: 'App',
});
// Then an event should have been fired with a queue time.
expect(fnSend).toHaveBeenCalledWith({
hitType: 'event',
expect(fnEvent).toHaveBeenCalledWith('test', {
action: 'test',
timestamp_micros: expect.any(Number),
eventAction: 'test',
eventCategory: 'App',
eventLabel: 'some label',
eventValue: undefined,
label: 'some label',
category: 'App',
value: undefined,
});
// And subsequent hits should not have a queue time.
@@ -410,9 +484,11 @@ describe('GoogleAnalytics4', () => {
context,
});
expect(fnSend).toHaveBeenCalledWith({
hitType: 'pageview',
page: '/page-2',
expect(fnEvent).toHaveBeenCalledWith('page_view', {
action: 'page_view',
label: '/page-2',
category: 'App',
value: undefined,
});
});
});
@@ -1,3 +1,18 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ReactGA from 'react-ga4';
import {
AnalyticsApi,
@@ -7,7 +22,7 @@ import {
IdentityApi,
} from '@backstage/core-plugin-api';
import { Config } from '@backstage/config';
import { DeferredCapture } from '../../../util';
import { DeferredCapture } from '../../../util/DeferredCapture';
/**
* Google Analytics API provider for the Backstage Analytics API.
@@ -21,7 +36,6 @@ export class GoogleAnalytics4 implements AnalyticsApi {
private readonly contentGroupBy?: string;
private readonly allowedContexts?: string[];
private readonly allowedAttributes?: string[];
/**
* Instantiate the implementation and initialize ReactGA.
* @param options initializes Google Analytics module with the config
@@ -60,9 +74,8 @@ export class GoogleAnalytics4 implements AnalyticsApi {
});
this.contentGroupBy = contentGroupBy;
this.allowedContexts = allowedContexts;
this.allowedAttributes = allowedAttributes;
this.allowedContexts = allowedContexts;
// If identity is required, defer event capture until identity is known.
this.capture = new DeferredCapture({ defer: identity === 'required' });
@@ -82,8 +95,8 @@ export class GoogleAnalytics4 implements AnalyticsApi {
/**
* Instantiate a fully configured GA Analytics API implementation.
* @param config - Config object from app config
* @param options - options with identityApi and userIdTransform config
* @param config Config object from app config
* @param options options with identityApi and userIdTransform config
*/
static fromConfig(
config: Config,
@@ -135,7 +148,7 @@ export class GoogleAnalytics4 implements AnalyticsApi {
* Primary event capture implementation. Handles core navigate event as a
* pageview and the rest as custom events. All custom dimensions/metrics are
* applied as they should be (set on pageview, merged object on events).
* @param event - AnalyticsEvent type captured
* @param event AnalyticsEvent type captured
*/
captureEvent(event: AnalyticsEvent) {
const { context, action, subject, value, attributes } = event;
@@ -145,7 +158,15 @@ export class GoogleAnalytics4 implements AnalyticsApi {
}
if (action === 'navigate' && context.extension === 'App') {
this.capture.pageview(subject, customEventData);
this.capture.event(
{
category: context.extension || 'App',
action: 'page_view',
label: subject,
value,
},
customEventData,
);
return;
}
@@ -178,17 +199,27 @@ export class GoogleAnalytics4 implements AnalyticsApi {
[x: string]: string | number | boolean | undefined;
} = {};
this.allowedContexts?.forEach(ctx => {
const contextKeys =
this.allowedContexts?.join('') === '*'
? Object.keys(context)
: this.allowedContexts;
contextKeys?.forEach(ctx => {
if (context[ctx]) {
customEventParameters[`c_${ctx}`] = context[ctx];
}
});
this.allowedAttributes?.forEach(attr => {
const attrKeys =
this.allowedAttributes?.join('') === '*'
? Object.keys(attributes)
: this.allowedAttributes;
attrKeys?.forEach(attr => {
if (attributes[attr]) {
customEventParameters[`a_${attr}`] = attributes[attr];
}
});
return customEventParameters;
}
@@ -1 +1,16 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { GoogleAnalytics4 } from './GoogleAnalytics4';
+15 -1
View File
@@ -1,2 +1,16 @@
export { analyticsModuleGA4 } from './plugin';
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './apis/implementations/AnalyticsApi';
@@ -1,22 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { analyticsModuleGA4 } from './plugin';
describe('google-analytics', () => {
it('should export plugin', () => {
expect(analyticsModuleGA4).toBeDefined();
});
});
@@ -1,26 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createPlugin } from '@backstage/core-plugin-api';
/**
* @deprecated Importing and including this plugin in an app has no effect.
* This will be removed in a future release.
*
* @public
*/
export const analyticsModuleGA4 = createPlugin({
id: 'analytics-provider-ga4',
});
@@ -1,16 +1,29 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ReactGA from 'react-ga4';
import { UaEventOptions } from 'react-ga4/types/ga4';
type Hit = {
hitType: string;
data: {
hitType: 'pageview' | 'event';
[x: string]: any;
};
};
const PageViewEvent = 'pageview';
/**
* A wrapper around ReactGA that can optionally handle latent capture logic.
*
@@ -46,32 +59,6 @@ export class DeferredCapture {
}
}
/**
* Either forwards the pageview directly to GA, or (if configured) enqueues
* the pageview hit to be captured when ready.
* @param path pageview path
* @param metadata any object that can be passed as additional parameter to the event
*/
pageview(path: string, metadata: any = {}) {
if (this.queue) {
this.queue.push({
data: {
hitType: PageViewEvent,
timestamp_micros: Date.now() * 1000,
page: path,
...metadata,
},
});
return;
}
ReactGA.send({
hitType: PageViewEvent,
page: path,
...metadata,
});
}
/**
* Either forwards the event directly to GA, or (if configured) enqueues the
* event hit to be captured when ready.
@@ -80,15 +67,12 @@ export class DeferredCapture {
*/
event(eventDetails: UaEventOptions, metadata: any = {}) {
const data = {
hitType: 'event',
eventCategory: eventDetails.category,
eventLabel: eventDetails.label!,
eventAction: eventDetails.action,
eventValue: eventDetails.value,
...eventDetails,
...metadata,
};
if (this.queue) {
this.queue.push({
hitType: eventDetails.action,
data: {
...data,
timestamp_micros: Date.now() * 1000,
@@ -96,7 +80,7 @@ export class DeferredCapture {
});
return;
}
ReactGA.send(data);
ReactGA.event(eventDetails.action, data);
}
/**
@@ -105,7 +89,7 @@ export class DeferredCapture {
*/
private sendDeferred(hit: Hit) {
// Send the hit with the appropriate queue time (`qt`).
ReactGA.send({
ReactGA.event(hit.hitType, {
...hit.data,
});
}
@@ -1 +1,16 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { DeferredCapture } from './DeferredCapture';