From db64baf57ba21411ad9ee7d663496fb372986b93 Mon Sep 17 00:00:00 2001 From: sriram ramakrishnan Date: Wed, 29 Mar 2023 19:27:36 -0400 Subject: [PATCH] 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 --- .changeset/mighty-cows-greet.md | 2 +- plugins/analytics-module-ga4/.eslintrc.js | 48 +---- plugins/analytics-module-ga4/README.md | 5 +- plugins/analytics-module-ga4/config.d.ts | 36 +--- plugins/analytics-module-ga4/package.json | 32 +-- .../AnalyticsApi/GoogleAnalytics4.test.ts | 190 ++++++++++++------ .../AnalyticsApi/GoogleAnalytics4.ts | 51 ++++- .../implementations/AnalyticsApi/index.ts | 15 ++ plugins/analytics-module-ga4/src/index.ts | 16 +- .../analytics-module-ga4/src/plugin.test.ts | 22 -- plugins/analytics-module-ga4/src/plugin.ts | 26 --- .../src/util/DeferredCapture.ts | 56 ++---- .../analytics-module-ga4/src/util/index.ts | 15 ++ 13 files changed, 262 insertions(+), 252 deletions(-) delete mode 100644 plugins/analytics-module-ga4/src/plugin.test.ts delete mode 100644 plugins/analytics-module-ga4/src/plugin.ts diff --git a/.changeset/mighty-cows-greet.md b/.changeset/mighty-cows-greet.md index 2d85ce371c..2e871db0c6 100644 --- a/.changeset/mighty-cows-greet.md +++ b/.changeset/mighty-cows-greet.md @@ -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 diff --git a/plugins/analytics-module-ga4/.eslintrc.js b/plugins/analytics-module-ga4/.eslintrc.js index 7e463f2382..e2a53a6ad2 100644 --- a/plugins/analytics-module-ga4/.eslintrc.js +++ b/plugins/analytics-module-ga4/.eslintrc.js @@ -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); diff --git a/plugins/analytics-module-ga4/README.md b/plugins/analytics-module-ga4/README.md index 99423c2ce7..7f4ce73b83 100644 --- a/plugins/analytics-module-ga4/README.md +++ b/plugins/analytics-module-ga4/README.md @@ -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`. diff --git a/plugins/analytics-module-ga4/config.d.ts b/plugins/analytics-module-ga4/config.d.ts index e6fede1262..d90ccb5938 100644 --- a/plugins/analytics-module-ga4/config.d.ts +++ b/plugins/analytics-module-ga4/config.d.ts @@ -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; + 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; + allowedAttributes?: string[] | ['*']; }; }; }; diff --git a/plugins/analytics-module-ga4/package.json b/plugins/analytics-module-ga4/package.json index 35c99d0e3f..df77a3a9de 100644 --- a/plugins/analytics-module-ga4/package.json +++ b/plugins/analytics-module-ga4/package.json @@ -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", diff --git a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts index 2de8053712..16db7075b3 100644 --- a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts +++ b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts @@ -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, }); }); }); diff --git a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts index 8f5ad65857..e38a0db1f8 100644 --- a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts +++ b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts @@ -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; } diff --git a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/index.ts b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/index.ts index a9773197ab..2b21af1b1b 100644 --- a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/index.ts +++ b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/index.ts @@ -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'; diff --git a/plugins/analytics-module-ga4/src/index.ts b/plugins/analytics-module-ga4/src/index.ts index dc712f25ef..0adf114679 100644 --- a/plugins/analytics-module-ga4/src/index.ts +++ b/plugins/analytics-module-ga4/src/index.ts @@ -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'; diff --git a/plugins/analytics-module-ga4/src/plugin.test.ts b/plugins/analytics-module-ga4/src/plugin.test.ts deleted file mode 100644 index 43e3104167..0000000000 --- a/plugins/analytics-module-ga4/src/plugin.test.ts +++ /dev/null @@ -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(); - }); -}); diff --git a/plugins/analytics-module-ga4/src/plugin.ts b/plugins/analytics-module-ga4/src/plugin.ts deleted file mode 100644 index 0cd1e8e5fb..0000000000 --- a/plugins/analytics-module-ga4/src/plugin.ts +++ /dev/null @@ -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', -}); diff --git a/plugins/analytics-module-ga4/src/util/DeferredCapture.ts b/plugins/analytics-module-ga4/src/util/DeferredCapture.ts index 9c829d3950..c2a1629d2a 100644 --- a/plugins/analytics-module-ga4/src/util/DeferredCapture.ts +++ b/plugins/analytics-module-ga4/src/util/DeferredCapture.ts @@ -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, }); } diff --git a/plugins/analytics-module-ga4/src/util/index.ts b/plugins/analytics-module-ga4/src/util/index.ts index 14c3ba7420..267e7a2e62 100644 --- a/plugins/analytics-module-ga4/src/util/index.ts +++ b/plugins/analytics-module-ga4/src/util/index.ts @@ -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';