Simplify/clarify Analytics API types and naming.

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2021-09-16 14:16:16 +02:00
parent 83c163a234
commit 1ff7386291
25 changed files with 305 additions and 344 deletions
@@ -50,7 +50,7 @@ describe('GoogleAnalytics', () => {
});
describe('integration', () => {
const domain = {
const context = {
componentName: 'App',
pluginId: 'some-plugin',
releaseNum: 1337,
@@ -65,26 +65,26 @@ describe('GoogleAnalytics', () => {
{
type: 'dimension',
index: 1,
source: 'domain',
attribute: 'pluginId',
source: 'context',
key: 'pluginId',
},
{
type: 'dimension',
index: 2,
source: 'context',
attribute: 'extraDimension',
source: 'attributes',
key: 'extraDimension',
},
{
type: 'metric',
index: 1,
source: 'domain',
attribute: 'releaseNum',
source: 'context',
key: 'releaseNum',
},
{
type: 'metric',
index: 2,
source: 'context',
attribute: 'extraMetric',
source: 'attributes',
key: 'extraMetric',
},
],
},
@@ -95,9 +95,9 @@ describe('GoogleAnalytics', () => {
it('tracks basic pageview', () => {
const api = GoogleAnalytics.fromConfig(basicValidConfig);
api.captureEvent({
verb: 'navigate',
noun: '/',
domain,
action: 'navigate',
subject: '/',
context,
});
const [command, data] = ReactGA.testModeAPI.calls[1];
@@ -115,17 +115,17 @@ describe('GoogleAnalytics', () => {
const expectedLabel = 'on something';
const expectedValue = 42;
api.captureEvent({
verb: expectedAction,
noun: expectedLabel,
action: expectedAction,
subject: expectedLabel,
value: expectedValue,
domain,
context,
});
const [command, data] = ReactGA.testModeAPI.calls[1];
expect(command).toBe('send');
expect(data).toMatchObject({
hitType: 'event',
eventCategory: domain.componentName,
eventCategory: context.componentName,
eventAction: expectedAction,
eventLabel: expectedLabel,
eventValue: expectedValue,
@@ -135,17 +135,17 @@ describe('GoogleAnalytics', () => {
it('captures configured custom dimensions/metrics on pageviews', () => {
const api = GoogleAnalytics.fromConfig(advancedConfig);
api.captureEvent({
verb: 'navigate',
noun: '/a-page',
domain,
action: 'navigate',
subject: '/a-page',
context,
});
// Expect a set command first.
const [setCommand, setData] = ReactGA.testModeAPI.calls[1];
expect(setCommand).toBe('set');
expect(setData).toMatchObject({
dimension1: domain.pluginId,
metric1: domain.releaseNum,
dimension1: context.pluginId,
metric1: context.releaseNum,
});
// Followed by a send command.
@@ -164,26 +164,26 @@ describe('GoogleAnalytics', () => {
const expectedLabel = 'some query';
const expectedValue = 5;
api.captureEvent({
verb: expectedAction,
noun: expectedLabel,
action: expectedAction,
subject: expectedLabel,
value: expectedValue,
context: {
attributes: {
extraDimension: false,
extraMetric: 0,
},
domain,
context,
});
const [command, data] = ReactGA.testModeAPI.calls[1];
expect(command).toBe('send');
expect(data).toMatchObject({
hitType: 'event',
eventCategory: domain.componentName,
eventCategory: context.componentName,
eventAction: expectedAction,
eventLabel: expectedLabel,
eventValue: expectedValue,
dimension1: domain.pluginId,
metric1: domain.releaseNum,
dimension1: context.pluginId,
metric1: context.releaseNum,
dimension2: false,
metric2: 0,
});
@@ -193,12 +193,12 @@ describe('GoogleAnalytics', () => {
const api = GoogleAnalytics.fromConfig(advancedConfig);
api.captureEvent({
verb: 'verb',
noun: 'noun',
context: {
action: 'verb',
subject: 'noun',
attributes: {
extraMetric: 'not a number',
},
domain,
context,
});
const [, data] = ReactGA.testModeAPI.calls[1];
@@ -17,17 +17,17 @@
import ReactGA from 'react-ga';
import {
AnalyticsApi,
AnalyticsDomainValue,
AnalyticsEventContext,
DomainDecoratedAnalyticsEvent,
AnalyticsContextValue,
AnalyticsEventAttributes,
AnalyticsEvent,
} from '@backstage/core-plugin-api';
import { Config } from '@backstage/config';
type CDM = {
type: 'dimension' | 'metric';
index: number;
source: 'domain' | 'context';
attribute: string;
source: 'context' | 'attributes';
key: string;
};
/**
@@ -79,7 +79,7 @@ export class GoogleAnalytics implements AnalyticsApi {
type: c.getString('type') as CDM['type'],
index: c.getNumber('index'),
source: c.getString('source') as CDM['source'],
attribute: c.getString('attribute'),
key: c.getString('key'),
};
}) || [];
@@ -98,47 +98,45 @@ export class GoogleAnalytics implements AnalyticsApi {
* applied as they should be (set on pageview, merged object on events).
*/
captureEvent({
domain,
verb,
noun,
value,
context,
}: DomainDecoratedAnalyticsEvent) {
const customMetadata = this.getCustomDimensionMetrics(domain, context);
action,
subject,
value,
attributes,
}: AnalyticsEvent) {
const customMetadata = this.getCustomDimensionMetrics(context, attributes);
if (verb === 'navigate' && domain?.componentName === 'App') {
if (action === 'navigate' && context?.componentName === 'App') {
// Set any/all custom dimensions.
if (Object.keys(customMetadata).length) {
ReactGA.set(customMetadata);
}
ReactGA.pageview(noun);
ReactGA.pageview(subject);
return;
}
ReactGA.event({
category: domain.componentName || 'App',
action: verb,
label: noun,
category: context.componentName || 'App',
action,
label: subject,
value,
...customMetadata,
});
}
/**
* Returns an object of dimensions/metrics given an Analytics Domain and an
* Returns an object of dimensions/metrics given an Analytics Context and an
* Event Context, e.g. { dimension1: "some value", metric8: 42 }
*/
private getCustomDimensionMetrics(
domain: AnalyticsDomainValue,
context: AnalyticsEventContext = {},
context: AnalyticsContextValue,
attributes: AnalyticsEventAttributes = {},
) {
const dataArray = this.cdmConfig
.map(cdm => {
const value =
cdm.source === 'domain'
? domain[cdm.attribute]
: context[cdm.attribute];
cdm.source === 'context' ? context[cdm.key] : attributes[cdm.key];
// Never pass a non-numeric value on a metric.
if (cdm.type === 'metric' && typeof value !== 'number') {