[MM-61193] Create trackFeatureEvent telemetry handler for paid features in web (#28834)

* frontend paid feature usage tracking

* fixes on the tooling

* fix lint errors

* fix types

* fix ci

* move from paid to SKU

* fix linter

* move categories and skus to depend on event instead of feature

* move to constants
Этот коммит содержится в:
Guillermo Vayá
2024-10-21 15:04:27 +02:00
коммит произвёл GitHub
родитель aa450f2432
Коммит 4cd73fc2d5
8 изменённых файлов: 82 добавлений и 1 удалений

Просмотреть файл

@@ -38,6 +38,10 @@ export function trackEvent(category, event, props) {
}
}
export function trackFeatureEvent(featureName, event, props) {
Client4.trackFeatureEvent(featureName, event, props);
}
export function pageVisited(category, name) {
Client4.pageVisited(category, name);
}

Просмотреть файл

@@ -10,8 +10,11 @@ import type {Group} from '@mattermost/types/groups';
import type {GlobalState} from '@mattermost/types/store';
import type {UserProfile} from '@mattermost/types/users';
import {TrackGroupsFeature, TrackInviteGroupEvent} from 'mattermost-redux/constants/telemetry';
import {getUser, makeDisplayNameGetter, makeGetProfilesByIdsAndUsernames} from 'mattermost-redux/selectors/entities/users';
import {trackFeatureEvent} from 'actions/telemetry_actions';
import type {Value} from 'components/multiselect/multiselect';
import WithTooltip from 'components/with_tooltip';
@@ -55,6 +58,7 @@ const GroupOption = (props: Props) => {
for (const profile of profiles) {
addUserProfile(profile);
}
trackFeatureEvent(TrackGroupsFeature, TrackInviteGroupEvent, {});
}, [addUserProfile, profiles]);
const onKeyDown = useCallback((e: KeyboardEvent) => {

Просмотреть файл

@@ -7,10 +7,19 @@ import * as rudderAnalytics from 'rudder-sdk-js';
import type {TelemetryHandler} from '@mattermost/client';
import {TrackMiscCategory, TrackActionCategory, TrackEnterpriseSKU, TrackProfessionalSKU, TrackInviteGroupEvent} from 'mattermost-redux/constants/telemetry';
import {isSystemAdmin} from 'mattermost-redux/utils/user_utils';
export {rudderAnalytics};
const eventSKUs: {[event: string]: string[]} = {
[TrackInviteGroupEvent]: [TrackProfessionalSKU, TrackEnterpriseSKU],
};
const eventCategory: {[event: string]: string} = {
[TrackInviteGroupEvent]: TrackActionCategory,
};
export class RudderTelemetryHandler implements TelemetryHandler {
trackEvent(userId: string, userRoles: string, category: string, event: string, props?: any) {
const properties = Object.assign({
@@ -36,6 +45,25 @@ export class RudderTelemetryHandler implements TelemetryHandler {
rudderAnalytics.track('event', properties, options);
}
trackFeatureEvent(userId: string, userRoles: string, featureName: string, event: string, props?: any) {
const properties = Object.assign({
category: getEventCategory(event),
type: event,
user_actual_id: userId,
user_actual_role: getActualRoles(userRoles),
}, props);
const options = {
context: {
feature: {
name: featureName,
skus: getSKUs(event),
},
},
};
rudderAnalytics.track(event, properties, options);
}
pageVisited(userId: string, userRoles: string, category: string, name: string) {
rudderAnalytics.page(
category,
@@ -62,3 +90,22 @@ export class RudderTelemetryHandler implements TelemetryHandler {
function getActualRoles(userRoles: string) {
return userRoles && isSystemAdmin(userRoles) ? 'system_admin, system_user' : 'system_user';
}
function getSKUs(eventName: string) {
const skus: string[] | undefined = eventSKUs[eventName];
if (skus === undefined) {
// Next line is to be aware if you've forgotten to add a SKU, add an empty array for Team edition
// eslint-disable-next-line
console.warn(`Event ${eventName} has no SKUs attached`);
}
return skus ?? [];
}
function getEventCategory(eventName: string) {
const category: string | undefined = eventCategory[eventName];
if (category === undefined) {
// eslint-disable-next-line
console.warn(`Event ${eventName} doesn't have a category`);
}
return category ?? TrackMiscCategory;
}

Просмотреть файл

@@ -12,8 +12,9 @@ import RequestStatus from './request_status';
import Roles from './roles';
import Stats from './stats';
import Teams from './teams';
import Telemetry from './telemetry';
import Threads from './threads';
import Users from './users';
import WebsocketEvents from './websocket';
export {General, Preferences, Posts, Files, RequestStatus, WebsocketEvents, Teams, Stats, Permissions, Emoji, Plugins, Users, Roles, Threads};
export {General, Preferences, Posts, Files, RequestStatus, WebsocketEvents, Teams, Stats, Permissions, Emoji, Plugins, Users, Roles, Threads, Telemetry};

Просмотреть файл

@@ -0,0 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// SKUs
export const TrackProfessionalSKU = 'professional';
export const TrackEnterpriseSKU = 'enterprise';
// Features
export const TrackGroupsFeature: string = 'custom_groups';
// Events
export const TrackInviteGroupEvent: string = 'invite_group_to_channel';
// Categories
export const TrackActionCategory: string = 'action';
export const TrackMiscCategory: string = 'miscellaneous';
export default {TrackActionCategory, TrackMiscCategory, TrackInviteGroupEvent, TrackGroupsFeature, TrackEnterpriseSKU, TrackProfessionalSKU};

Просмотреть файл

@@ -82,6 +82,7 @@ describe('ClientError', () => {
describe('trackEvent', () => {
class TestTelemetryHandler implements TelemetryHandler {
trackEvent = jest.fn();
trackFeatureEvent = jest.fn();
pageVisited = jest.fn();
}

Просмотреть файл

@@ -4327,6 +4327,11 @@ export default class Client4 {
this.telemetryHandler.trackEvent(this.userId, this.userRoles, category, event, props);
}
}
trackFeatureEvent(featureName: string, event: string, props?: any) {
if (this.telemetryHandler) {
this.telemetryHandler.trackFeatureEvent(this.userId, this.userRoles, featureName, event, props);
}
}
pageVisited(category: string, name: string) {
if (this.telemetryHandler) {

Просмотреть файл

@@ -3,5 +3,6 @@
export interface TelemetryHandler {
trackEvent: (userId: string, userRoles: string, category: string, event: string, props?: any) => void;
trackFeatureEvent: (userId: string, userRoles: string, featureName: string, event: string, props?: any) => void;
pageVisited: (userId: string, userRoles: string, category: string, name: string) => void;
}