Review feedback.

Co-authored-by: Camila Belo <camilaibs@gmail.com>
Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2021-09-26 18:31:22 +02:00
parent 3f1bfab314
commit b500fac551
16 changed files with 129 additions and 99 deletions
+15 -7
View File
@@ -8,9 +8,9 @@ This plugin contains no other functionality.
## Installation
1. Install this plugin in your Backstage app:
1. Install the plugin package in your Backstage app:
`cd packages/app && yarn add @backstage/plugin-analytics-module-ga`
2. Add the API implementation to your App and configure the plugin (see below).
2. Wire up the API implementation to your App:
```tsx
// packages/app/src/apis.ts
@@ -27,7 +27,7 @@ export const apis: AnyApiFactory[] = [
];
```
## Configuration
3. Configure the plugin in your `app-config.yaml`:
The following is the minimum configuration required to start sending analytics
events to GA. All that's needed is your Universal Analytics tracking ID:
@@ -41,11 +41,11 @@ app:
trackingId: UA-0000000-0
```
### Plugin Analytics
## Configuration
In order to be able to analyze usage of your Backstage instance _by plugin_, we
strongly recommend configuring at least one custom dimension to capture Plugin
IDs associated with events, including page views.
strongly recommend configuring at least one [custom dimension][what-is-a-custom-dimension]
to capture Plugin IDs associated with events, including page views.
1. First, [configure the custom dimension in GA][configure-custom-dimension].
Be sure to set the Scope to `hit`, and name it something like `Plugin`. Note
@@ -84,6 +84,10 @@ app:
index: 2
source: context
key: routeRef
- type: dimension
index: 3
source: context
key: extension
- type: metric
index: 1
source: attributes
@@ -104,6 +108,9 @@ app:
debug: true # Logs analytics event to the web console
```
You might commonly set the above in an `app-config.local.yaml` file, which is
normally `gitignore`'d but loaded and merged in when Backstage is bootstrapped.
## Development
If you would like to contribute improvements to this plugin, the easiest way to
@@ -141,4 +148,5 @@ app:
key: pluginId
```
[configure-custom-dimension]: https://support.google.com/analytics/answer/2709828?hl=en#configuration
[what-is-a-custom-dimension]: https://support.google.com/analytics/answer/2709828
[configure-custom-dimension]: https://support.google.com/analytics/answer/2709828#configuration
+1
View File
@@ -4,6 +4,7 @@
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
@@ -53,6 +53,7 @@ describe('GoogleAnalytics', () => {
const context = {
extension: 'App',
pluginId: 'some-plugin',
routeRef: 'unknown',
releaseNum: 1337,
};
const advancedConfig = new ConfigReader({
@@ -23,25 +23,18 @@ import {
} from '@backstage/core-plugin-api';
import { Config } from '@backstage/config';
type CDM = {
type CustomDimensionOrMetricConfig = {
type: 'dimension' | 'metric';
index: number;
source: 'context' | 'attributes';
key: string;
};
/**
* Type guard for emptiness check in array filter.
*/
function notEmpty<T>(value: T | undefined): value is T {
return value !== undefined;
}
/**
* Google Analytics API provider for the Backstage Analytics API.
*/
export class GoogleAnalytics implements AnalyticsApi {
private readonly cdmConfig: CDM[];
private readonly cdmConfig: CustomDimensionOrMetricConfig[];
/**
* Instantiate the implementation and initialize ReactGA.
@@ -52,7 +45,7 @@ export class GoogleAnalytics implements AnalyticsApi {
testMode,
debug,
}: {
cdmConfig: CDM[];
cdmConfig: CustomDimensionOrMetricConfig[];
trackingId: string;
testMode: boolean;
debug: boolean;
@@ -69,19 +62,22 @@ export class GoogleAnalytics implements AnalyticsApi {
static fromConfig(config: Config) {
// Get all necessary configuration.
const trackingId = config.getString('app.analytics.ga.trackingId');
const debug = !!config.getOptionalBoolean('app.analytics.ga.debug');
const testMode = !!config.getOptionalBoolean('app.analytics.ga.testMode');
const debug = config.getOptionalBoolean('app.analytics.ga.debug') ?? false;
const testMode =
config.getOptionalBoolean('app.analytics.ga.testMode') ?? false;
const cdmConfig =
config
.getOptionalConfigArray('app.analytics.ga.customDimensionsMetrics')
?.map(c => {
return {
type: c.getString('type') as CDM['type'],
type: c.getString('type') as CustomDimensionOrMetricConfig['type'],
index: c.getNumber('index'),
source: c.getString('source') as CDM['source'],
source: c.getString(
'source',
) as CustomDimensionOrMetricConfig['source'],
key: c.getString('key'),
};
}) || [];
}) ?? [];
// Return an implementation instance.
return new GoogleAnalytics({
@@ -106,7 +102,7 @@ export class GoogleAnalytics implements AnalyticsApi {
}: AnalyticsEvent) {
const customMetadata = this.getCustomDimensionMetrics(context, attributes);
if (action === 'navigate' && context?.extension === 'App') {
if (action === 'navigate' && context.extension === 'App') {
// Set any/all custom dimensions.
if (Object.keys(customMetadata).length) {
ReactGA.set(customMetadata);
@@ -127,34 +123,32 @@ export class GoogleAnalytics implements AnalyticsApi {
/**
* Returns an object of dimensions/metrics given an Analytics Context and an
* Event Context, e.g. { dimension1: "some value", metric8: 42 }
* Event Attributes, e.g. { dimension1: "some value", metric8: 42 }
*/
private getCustomDimensionMetrics(
context: AnalyticsContextValue,
attributes: AnalyticsEventAttributes = {},
) {
const dataArray = this.cdmConfig
.map(cdm => {
const value =
cdm.source === 'context' ? context[cdm.key] : attributes[cdm.key];
const customDimensionsMetrics: { [x: string]: string | number | boolean } =
{};
// Never pass a non-numeric value on a metric.
if (cdm.type === 'metric' && typeof value !== 'number') {
return undefined;
}
this.cdmConfig.forEach(config => {
const value =
config.source === 'context'
? context[config.key]
: attributes[config.key];
return value !== undefined
? {
[`${cdm.type}${cdm.index}`]: value,
}
: undefined;
})
.filter(notEmpty);
// Never pass a non-numeric value on a metric.
if (config.type === 'metric' && typeof value !== 'number') {
return;
}
return dataArray.length
? dataArray.reduce((result, cd) => {
return Object.assign(result, cd);
})
: {};
// Only set defined values.
if (value !== undefined) {
customDimensionsMetrics[`${config.type}${config.index}`] = value;
}
});
return customDimensionsMetrics;
}
}