Skip to content

Commit

Permalink
Initial identity-awareness implementation for GA.
Browse files Browse the repository at this point in the history
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
  • Loading branch information
iamEAP committed Jan 30, 2022
1 parent 2b06cca commit b40a0cc
Show file tree
Hide file tree
Showing 13 changed files with 602 additions and 36 deletions.
6 changes: 6 additions & 0 deletions .changeset/analytics-station-eleven.md
@@ -0,0 +1,6 @@
---
'@backstage/plugin-analytics-module-ga': patch
---

Added the ability to capture and set user IDs from Backstage's `identityApi`. For full instructions on how to
set this up, see [the User ID section of its README](https://github.com/backstage/backstage/tree/master/plugins/analytics-module-ga#user-ids)
1 change: 1 addition & 0 deletions .github/styles/vocab.txt
Expand Up @@ -223,6 +223,7 @@ productional
Protobuf
proxying
Proxying
pseudonymized
pubsub
pygments
pymdownx
Expand Down
74 changes: 71 additions & 3 deletions plugins/analytics-module-ga/README.md
Expand Up @@ -14,15 +14,22 @@ This plugin contains no other functionality.

```tsx
// packages/app/src/apis.ts
import { analyticsApiRef, configApiRef } from '@backstage/core-plugin-api';
import {
analyticsApiRef,
configApiRef,
identityApiRef,
} from '@backstage/core-plugin-api';
import { GoogleAnalytics } from '@backstage/plugin-analytics-module-ga';

export const apis: AnyApiFactory[] = [
// Instantiate and register the GA Analytics API Implementation.
createApiFactory({
api: analyticsApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) => GoogleAnalytics.fromConfig(configApi),
deps: { configApi: configApiRef, identityApi: identityApiRef },
factory: ({ configApi, identityApi }) =>
GoogleAnalytics.fromConfig(configApi, {
identityApi,
}),
}),
];
```
Expand Down Expand Up @@ -92,6 +99,66 @@ app:
key: someEventContextAttr
```

### User IDs

This plugin supports accurately deriving user-oriented metrics (like monthly
active users) using Google Analytics' [user ID views][ga-user-id-view]. To
enable this...

1. Be sure you've gone through the process of setting up a user ID view in your
Backstage instance's Google Analytics property (see docs linked above).
2. Make sure you instantiate `GoogleAnalytics` with an `identityApi` instance
passed to it, as shown in the installation section above.
3. Set `app.analytics.ga.identity` to either `required` or `optional` in your
`app.config.yaml`, like this:

```yaml
app:
analytics:
ga:
trackingId: UA-0000000-0
identity: optional
```

Set `identity` to `optional` if you need accurate session counts, including
cases where users do not sign in at all. Use `required` if you need all hits
to be associated with a user ID without exception (and don't mind if some
sessions are not captured, such as those where no sign in occur).

Note that, to comply with GA policies, the value of the User ID is
pseudonymized before being sent to GA. By default, it is a `sha256` hash of the
current user's `userEntityRef` as returned by the `identityApi`. To set a
different value, provide a custom implementation of the `identityApi` that
resolves a `userEntityRef` of the form `PrivateUser:namespace/YOUR-VALUE`. For
example:

```typescript
export const apis: AnyApiFactory[] = [
createApiFactory({
api: analyticsApiRef,
deps: { config: configApiRef, identityApi: identityApiRef },
factory: ({ identityApi, config }) => {
return new PseudononymizedIdentity(identityApi);
},
}),
];

class PseudononymizedIdentity implements IdentityApi {
constructor(private actualApi: IdentityApi) {}
async getBackstageIdentity(): Promise<BackstageUserIdentity> {
const { email = 'someone' } = await this.actualApi.getProfileInfo();
const hashedEmail = customHashingFunction(email);

return {
type: 'user',
userEntityRef: `PrivateUser:default/${hashedEmail}`,
ownershipEntityRefs: [],
};
}
// ...
}
```

### Debugging and Testing

In pre-production environments, you may wish to set additional configurations
Expand Down Expand Up @@ -147,3 +214,4 @@ app:

[what-is-a-custom-dimension]: https://support.google.com/analytics/answer/2709828
[configure-custom-dimension]: https://support.google.com/analytics/answer/2709828#configuration
[ga-user-id-view]: https://support.google.com/analytics/answer/3123669
12 changes: 7 additions & 5 deletions plugins/analytics-module-ga/api-report.md
Expand Up @@ -7,18 +7,20 @@ import { AnalyticsApi } from '@backstage/core-plugin-api';
import { AnalyticsEvent } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { Config } from '@backstage/config';
import { IdentityApi } from '@backstage/core-plugin-api';

// Warning: (ae-missing-release-tag) "analyticsModuleGA" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const analyticsModuleGA: BackstagePlugin<{}, {}>;

// Warning: (ae-missing-release-tag) "GoogleAnalytics" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export class GoogleAnalytics implements AnalyticsApi {
captureEvent(event: AnalyticsEvent): void;
static fromConfig(config: Config): GoogleAnalytics;
static fromConfig(
config: Config,
options?: {
identityApi?: IdentityApi;
},
): GoogleAnalytics;
}

// (No @packageDocumentation comment for this package)
Expand Down
19 changes: 19 additions & 0 deletions plugins/analytics-module-ga/config.d.ts
Expand Up @@ -34,6 +34,25 @@ export interface Config {
*/
scriptSrc?: string;

/**
* Controls how the identityApi is used when sending data to GA:
*
* - `disabled`: (Default) Explicitly prevents a user's identity from
* being used when capturing events in GA.
* - `optional`: Pageviews and hits are forwarded to GA as they happen
* and only include user identity metadata once known. Guarantees
* that hits are captured for all sessions, even if no sign in
* occurs, but may result in dropped hits in User ID views.
* - `required`: All pageviews and hits are deferred until an identity
* is known. Guarantees that all data sent to GA correlates to a user
* identity, but prevents GA from receiving events for sessions in
* which a user does not sign in. An `identityApi` instance must be
* passed during instantiation when set to this value.
*
* @visibility frontend
*/
identity?: 'disabled' | 'optional' | 'required';

/**
* Whether or not to log analytics debug statements to the console.
* Defaults to false.
Expand Down
1 change: 1 addition & 0 deletions plugins/analytics-module-ga/package.json
Expand Up @@ -21,6 +21,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.9.10",
"@backstage/config": "^0.1.13",
"@backstage/core-components": "^0.8.6",
"@backstage/core-plugin-api": "^0.6.0",
Expand Down

0 comments on commit b40a0cc

Please sign in to comment.