Evan Moncuso 689ede2da5
[UI] - introduce Posthog for Vault Dedicated managed clusters (#30425)
* add dummy provider and wire it into the app

* add tests for analytics service

* add posthog provider

* wire in posthog

* add HVD limitation for analytics and add unit test

* filter out sensitive event properties

* add changelog

* run copywrite headers

* update logging tests for analytics service

* update changelog format

* disable telemetry in test mode

* remove unnecessary test

* self review

* Update vault-reporting addon with analytics tracking changes

* address review feedback

---------

Co-authored-by: Jim Wright <jim.wright@hashicorp.com>
2025-05-23 19:40:29 +00:00

171 lines
4.0 KiB
TypeScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import posthog from 'posthog-js/dist/module.no-external';
import type { AnalyticsProvider } from 'vault/vault/analytics';
import type { CaptureResult } from 'posthog-js/dist/module.no-external';
interface PostHogConfig {
enabled: boolean;
project_id: string;
api_host: string;
}
/*
formatEvent takes the default posthog capture event and trims it down to only the things we want to collect
PostHog collects a lot of stuff by default, this removes everything pretaining to urls and possible customer info in favor of generic information
*/
const redactEvent = (cr: CaptureResult | null) => {
if (cr === null) return cr;
const { properties } = cr;
// extract ONLY what we need
// this is definitely ridiculous, but it's the best way to be sure we're not accidentally including things that aren't wanted
const {
$browser,
$browser_language,
$browser_language_prefix,
$browser_version,
$device_id,
$device_type,
$groups,
$insert_id,
$is_identified,
$lib,
$lib_rate_limit_remaining_tokens,
$lib_version,
$os,
$os_version,
$pageview_id,
$process_person_profile,
$raw_user_agent,
$recording_status,
$screen_height,
$screen_width,
$sdk_debug_current_session_duration,
$sdk_debug_replay_internal_buffer_length,
$sdk_debug_replay_internal_buffer_size,
$sdk_debug_retry_queue_size,
$sdk_debug_session_start,
$session_id,
$time,
$user_id,
$viewport_height,
$viewport_width,
$window_id,
distinct_id,
routeName,
token,
} = properties;
// replay into the sent object
return {
...cr,
properties: {
$browser,
$browser_version,
$browser_language,
$browser_language_prefix,
$os,
$os_version,
$device_type,
$raw_user_agent,
$screen_height,
$screen_width,
$viewport_height,
$viewport_width,
$lib,
$lib_version,
$insert_id,
$time,
$device_id,
$user_id,
$groups,
$session_id,
$window_id,
$recording_status,
$sdk_debug_replay_internal_buffer_length,
$sdk_debug_replay_internal_buffer_size,
$sdk_debug_current_session_duration,
$sdk_debug_session_start,
$sdk_debug_retry_queue_size,
$pageview_id,
$is_identified,
$process_person_profile,
$lib_rate_limit_remaining_tokens,
distinct_id,
routeName,
token,
},
};
};
export const PROVIDER_NAME = 'posthog';
export class PostHogProvider implements AnalyticsProvider {
name = PROVIDER_NAME;
client = posthog;
licenseId = '';
get anonymousId() {
return this.client.get_distinct_id();
}
start(config: unknown) {
const { enabled, project_id, api_host } = config as PostHogConfig;
if (enabled) {
this.client.init(project_id, {
api_host,
person_profiles: 'identified_only',
persistence: 'memory',
autocapture: false,
disable_session_recording: true,
advanced_disable_decide: true,
capture_pageview: false,
before_send: redactEvent,
});
}
}
identify(identifier: string, traits: Record<string, string>) {
this.licenseId = traits['licenseId'] || '';
this.client.identify(identifier, {
...traits,
});
}
trackPageView(routeName: string /* metadata: Record<string, string> */) {
// use licenseId as a grouping for this cluster
if (this.licenseId) {
this.client.capture('$pageview', {
routeName,
$groups: {
licenseId: this.licenseId,
},
});
} else {
this.client.capture('$pageview', { routeName });
}
}
trackEvent(eventName: string, metadata: Record<string, string> = {}) {
// use licenseId as a grouping for this cluster
if (this.licenseId) {
this.client.capture(eventName, {
...metadata,
$groups: {
licenseId: this.licenseId,
},
});
} else {
this.client.capture(eventName, metadata);
}
}
}