-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(analytics-module-segment): create the analytics plugin module fo…
…r segment (#12) Signed-off-by: Alec Jacobs <[email protected]>
- Loading branch information
1 parent
df9e8a7
commit 797ab8d
Showing
18 changed files
with
1,225 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
# Analytics Module: Segment | ||
|
||
This plugin provides an opinionated implementation of the Backstage Analytics | ||
API for [Segment][segment]. Once installed and configured, analytics events will | ||
be sent to the configured Segment Workspace as your users navigate and use your Backstage instance. | ||
|
||
## Requirements | ||
|
||
This plugin requires an active workspace with [Segment][segment]. Please reference the [Getting Started Guide][getting-started] to get set up before proceeding. | ||
|
||
## Installation | ||
|
||
### Install the plugin package in your Backstage app: | ||
|
||
```sh | ||
# From your Backstage root directory | ||
yarn --cwd packages/app add @segment/backstage-plugin-analytics-module-segment | ||
``` | ||
|
||
### Wire up the API implementation to your App: | ||
|
||
```ts | ||
// packages/app/src/apis.ts | ||
import { | ||
analyticsApiRef, | ||
configApiRef, | ||
identityApiRef, | ||
} from '@backstage/core-plugin-api'; | ||
import { SegmentAnalytics } from '@segment/backstage-plugin-analytics-module-segment'; | ||
|
||
export const apis: AnyApiFactory[] = [ | ||
// Instantiate and register the SegmentAnalytics API Implementation. | ||
createApiFactory({ | ||
api: analyticsApiRef, | ||
deps: { configApi: configApiRef, identityApi: identityApiRef }, | ||
factory: ({ configApi, identityApi }) => | ||
SegmentAnalytics.fromConfig(configApi, { | ||
identityApi, | ||
}), | ||
}), | ||
]; | ||
``` | ||
|
||
#### Optional: Configure a user transform | ||
|
||
By default, this analytics plugin [identifies][identify] the user taking actions as the logged in Backstage User's entity reference string (e.g. `user:development/guest`). Currently, no other information is provided to the identify call. | ||
|
||
To anonymize users, a `userIdTransform` can be provided in one of two ways: | ||
|
||
1. The string value `sha-256` | ||
2. A custom transformation function that matches the contract of `(userEntityRef: string) => Promise<string>` | ||
|
||
If `sha-256` is provided, the user entity reference will be pseudonymized into a sha256 string value. | ||
|
||
```ts | ||
// packages/app/src/apis.ts | ||
import { | ||
analyticsApiRef, | ||
configApiRef, | ||
identityApiRef, | ||
} from '@backstage/core-plugin-api'; | ||
import { SegmentAnalytics } from '@segment/backstage-plugin-analytics-module-segment'; | ||
|
||
export const apis: AnyApiFactory[] = [ | ||
// Instantiate and register the SegmentAnalytics API Implementation. | ||
createApiFactory({ | ||
api: analyticsApiRef, | ||
deps: { configApi: configApiRef, identityApi: identityApiRef }, | ||
factory: ({ configApi, identityApi }) => | ||
SegmentAnalytics.fromConfig(configApi, { | ||
identityApi, | ||
userIdTransform: 'sha-256', | ||
}), | ||
}), | ||
]; | ||
``` | ||
|
||
For enhanced security, providing a custom transformation function can be used to hash the value in any means desired. | ||
|
||
```ts | ||
// packages/app/src/apis.ts | ||
import { | ||
analyticsApiRef, | ||
configApiRef, | ||
identityApiRef, | ||
} from '@backstage/core-plugin-api'; | ||
import { SegmentAnalytics } from '@segment/backstage-plugin-analytics-module-segment'; | ||
|
||
export const apis: AnyApiFactory[] = [ | ||
// Instantiate and register the SegmentAnalytics API Implementation. | ||
createApiFactory({ | ||
api: analyticsApiRef, | ||
deps: { configApi: configApiRef, identityApi: identityApiRef }, | ||
factory: ({ configApi, identityApi }) => | ||
SegmentAnalytics.fromConfig(configApi, { | ||
identityApi, | ||
async userIdTransform(userEntityRef: string) { | ||
const salt = configApi.getString( | ||
'custom.config.analytics.userIdSalt', | ||
); | ||
const textToChars = (text: string) => | ||
text.split('').map(c => c.charCodeAt(0)); | ||
const byteHex = (n: number) => | ||
('0' + Number(n).toString(16)).substring(-2); | ||
const applySaltToChar = (code: number) => | ||
textToChars(salt).reduce((a, b) => a ^ b, code); | ||
|
||
return textToChars(userEntityRef) | ||
.map(applySaltToChar) | ||
.map(byteHex) | ||
.join(''); | ||
}, | ||
}), | ||
}), | ||
]; | ||
``` | ||
|
||
#### Optional: Prevent user identification | ||
|
||
If you choose not to identify Backstage users in analytics events, simply neglect to provide the `identityApi` when initializing the `SegmentAnalytics` API. | ||
|
||
```ts | ||
// packages/app/src/apis.ts | ||
import { analyticsApiRef, configApiRef } from '@backstage/core-plugin-api'; | ||
import { SegmentAnalytics } from '@segment/backstage-plugin-analytics-module-segment'; | ||
|
||
export const apis: AnyApiFactory[] = [ | ||
// Instantiate and register the SegmentAnalytics API Implementation. | ||
createApiFactory({ | ||
api: analyticsApiRef, | ||
deps: { configApi: configApiRef }, | ||
factory: ({ configApi }) => SegmentAnalytics.fromConfig(configApi), | ||
}), | ||
]; | ||
``` | ||
|
||
Doing so will allow analytic events to continue to be sent to Segment, just with the the events being attributed to [anonymous user IDs][anonymous-ids]. | ||
|
||
### Configure the plugin in your `app-config.yaml`: | ||
|
||
The following is the minimum configuration required to start sending analytics | ||
events to Segment. The only requirement is the [write key][write-key] for the [Analytics.js Source][analytics.js-source] that was created for your Backstage instance. | ||
|
||
```yaml | ||
# app-config.yaml | ||
app: | ||
analytics: | ||
segment: | ||
writeKey: abcABCfooBARtestKEY | ||
``` | ||
## Configuration | ||
### Disabling | ||
In some pre-production environments, it may not be prudent to load the Segment Analytics plugin at all. In those cases, you can explicitly disable analytics through app configuration: | ||
```yaml | ||
# app-config.yaml | ||
app: | ||
analytics: | ||
segment: | ||
enabled: false # Prevent the analytics instance from loading | ||
writeKey: abcABCfooBARtestKEY # write key is still required in app-config | ||
``` | ||
### Debugging and Testing | ||
In pre-production environments, you may wish to set additional configurations | ||
to turn off reporting to Analytics and/or print debug statements to the | ||
console. In those cases, you can explicitly disable analytics through app configuration: | ||
```yaml | ||
app: | ||
analytics: | ||
segment: | ||
writeKey: abcABCfooBARtestKEY | ||
testMode: true # Prevents data being sent to Segment and logs what would have been sent instead | ||
debug: true # Configure debug on the Analytics module and write the Backstage analytics event to the web console | ||
``` | ||
### Analytics agent options | ||
Additional configuration is available to configure the Analytics.js agent as follows: | ||
```yaml | ||
app: | ||
analytics: | ||
segment: | ||
writeKey: abcABCfooBARtestKEY | ||
agent: | ||
# Disable storing any data on the client-side via cookies or localstorage | ||
disableClientPersistence: true | ||
# Disables automatically converting ISO string event properties into Dates. | ||
disableAutoISOConversion: true | ||
# Whether or not to capture page context early so that it is always up-to-date. | ||
initialPageView: true | ||
``` | ||
> [!NOTE]\ | ||
> The `testMode` and `debug` configuration fields work independently of each other. | ||
> If `debug` is `true` and `testMode` is `false`, analytics events will still be sent to Segment | ||
> and debug information will be written to the console still. | ||
|
||
[segment]: https://segment.com/ | ||
[getting-started]: https://segment.com/docs/getting-started/ | ||
[write-key]: https://segment.com/docs/connections/find-writekey/ | ||
[analytics.js-source]: https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/ | ||
[identify]: https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/#identify | ||
[anonymous-ids]: https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/identity/#anonymous-ids |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
export interface Config { | ||
app: { | ||
analytics: { | ||
segment: { | ||
/** | ||
* Controls whether the Segment Analytics module is enabled. | ||
* Defaults to true. | ||
* | ||
* @visibility frontend | ||
*/ | ||
enabled?: boolean; | ||
|
||
/** | ||
* Segment Analytics Write Key. Reference https://segment.com/docs/connections/find-writekey/ | ||
* to find your write key | ||
* | ||
* @visibility frontend | ||
*/ | ||
writeKey: string; | ||
|
||
/** | ||
* Whether to log analytics debug statements and events to the console. Does not prevent sending of events. | ||
* Defaults to false. | ||
* | ||
* @visibility frontend | ||
*/ | ||
debug?: boolean; | ||
|
||
/** | ||
* Prevents events from actually being sent and instead logged as console output when set to true. | ||
* Defaults to false. | ||
* | ||
* @visibility frontend | ||
*/ | ||
testMode?: boolean; | ||
|
||
/** | ||
* Configuration options for the Segment Analytics agent. | ||
* | ||
* @visibility frontend | ||
*/ | ||
agent?: { | ||
/** | ||
* Disables storing any data on the client-side via cookies or localstorage. | ||
* Defaults to false. | ||
* | ||
* @visibility frontend | ||
*/ | ||
disableClientPersistence?: boolean; | ||
|
||
/** | ||
* Disables automatically converting ISO string event properties into Dates. | ||
* ISO string to Date conversions occur right before sending events to a classic device mode integration, | ||
* after any destination middleware have been ran. | ||
* Defaults to false. | ||
* | ||
* @visibility frontend | ||
*/ | ||
disableAutoISOConversion?: boolean; | ||
|
||
/** | ||
* Whether or not to capture page context early so that it is always up-to-date. | ||
* Defaults to false. | ||
* | ||
* @visibility frontend | ||
*/ | ||
initialPageView?: boolean; | ||
}; | ||
}; | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import React from 'react'; | ||
import { Link } from '@backstage/core-components'; | ||
|
||
export const Playground = () => { | ||
return ( | ||
<> | ||
<Link to="#clicked">Click Here</Link> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import React from 'react'; | ||
import { | ||
analyticsApiRef, | ||
configApiRef, | ||
identityApiRef, | ||
} from '@backstage/core-plugin-api'; | ||
import { createDevApp } from '@backstage/dev-utils'; | ||
|
||
import { Playground } from './Playground'; | ||
import { SegmentAnalytics } from '../src'; | ||
|
||
createDevApp() | ||
.registerApi({ | ||
api: analyticsApiRef, | ||
deps: { configApi: configApiRef, identityApi: identityApiRef }, | ||
factory: ({ configApi, identityApi }) => | ||
SegmentAnalytics.fromConfig(configApi, { | ||
identityApi, | ||
}), | ||
}) | ||
.addPage({ | ||
path: '/segment', | ||
title: 'Segment Playground', | ||
element: <Playground />, | ||
}) | ||
.render(); |
Oops, something went wrong.