diff --git a/.github/workflows/build-mac.yml b/.github/workflows/build-mac.yml index fc6f3258f..008af0f71 100644 --- a/.github/workflows/build-mac.yml +++ b/.github/workflows/build-mac.yml @@ -12,12 +12,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Replace token 1 + - name: Replace token 1 server run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go - - name: Replace token 2 + - name: Replace token 1 webapp + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + + - name: Replace token 2 server run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + - name: Replace token 2 webapp + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + - name: npm install run: cd webapp; npm install --no-optional diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 5e39786db..126fafb2a 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -11,12 +11,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Replace token 1 + - name: Replace token 1 server run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go - - name: Replace token 2 + - name: Replace token 1 webapp + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + + - name: Replace token 2 server run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + - name: Replace token 2 webapp + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + - name: npm install run: cd webapp; npm install --no-optional diff --git a/.github/workflows/build-win-wpf.yml b/.github/workflows/build-win-wpf.yml index 7eeeefda6..df6c698f5 100644 --- a/.github/workflows/build-win-wpf.yml +++ b/.github/workflows/build-win-wpf.yml @@ -11,12 +11,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Replace token 1 + - name: Replace token 1 server run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go - - name: Replace token 2 + - name: Replace token 1 webapp + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + + - name: Replace token 2 server run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + - name: Replace token 2 webapp + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.0.2 diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index 09caed001..3e5860dc4 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -16,12 +16,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Replace token 1 + - name: Replace token 1 server run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go - - name: Replace token 2 + - name: Replace token 1 webapp + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + + - name: Replace token 2 server run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + - name: Replace token 2 webapp + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + - name: npm install run: cd webapp; npm install --no-optional @@ -69,12 +75,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Replace token 1 + - name: Replace token 1 server run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go - - name: Replace token 2 + - name: Replace token 1 webapp + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + + - name: Replace token 2 server run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + - name: Replace token 2 webapp + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + - name: npm install run: cd webapp; npm install --no-optional @@ -105,12 +117,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Replace token 1 + - name: Replace token 1 server run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go - - name: Replace token 2 + - name: Replace token 1 webapp + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + + - name: Replace token 2 server run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + - name: Replace token 2 webapp + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.0.2 @@ -154,12 +172,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Replace token 1 + - name: Replace token 1 server run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go - - name: Replace token 2 + - name: Replace token 1 webapp + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + + - name: Replace token 2 server run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + - name: Replace token 2 webapp + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + - name: npm install run: cd webapp; npm install --no-optional diff --git a/.github/workflows/plugin-release.yml b/.github/workflows/plugin-release.yml index 851161def..ac0c4907e 100644 --- a/.github/workflows/plugin-release.yml +++ b/.github/workflows/plugin-release.yml @@ -11,12 +11,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Replace token 1 + - name: Replace token 1 server run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go - - name: Replace token 2 + - name: Replace token 1 webapp + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + + - name: Replace token 2 server run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_PROD_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + - name: Replace token 2 webapp + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_PROD_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + - name: npm install run: cd webapp; npm install --no-optional diff --git a/.github/workflows/prod-release.yml b/.github/workflows/prod-release.yml index 3ab06dab7..b1fcd6738 100644 --- a/.github/workflows/prod-release.yml +++ b/.github/workflows/prod-release.yml @@ -11,12 +11,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Replace token 1 + - name: Replace token 1 server run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go - - name: Replace token 2 + - name: Replace token 1 webapp + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + + - name: Replace token 2 server run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_PROD_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + - name: Replace token 2 webapp + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_PROD_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + - name: npm install run: cd webapp; npm install --no-optional diff --git a/mattermost-plugin/server/plugin.go b/mattermost-plugin/server/plugin.go index 471554628..edd3046d1 100644 --- a/mattermost-plugin/server/plugin.go +++ b/mattermost-plugin/server/plugin.go @@ -88,6 +88,13 @@ func (p *Plugin) OnActivate() error { baseURL = *mmconfig.ServiceSettings.SiteURL } + serverID := client.System.GetDiagnosticID() + + enableTelemetry := false + if mmconfig.LogSettings.EnableDiagnostics != nil { + enableTelemetry = *mmconfig.LogSettings.EnableDiagnostics + } + cfg := &config.Configuration{ ServerRoot: baseURL + "/plugins/focalboard", Port: -1, @@ -100,7 +107,8 @@ func (p *Plugin) OnActivate() error { FilesDriver: *mmconfig.FileSettings.DriverName, FilesPath: *mmconfig.FileSettings.Directory, FilesS3Config: filesS3Config, - Telemetry: true, + Telemetry: enableTelemetry, + TelemetryID: serverID, WebhookUpdate: []string{}, SessionExpireTime: 2592000, SessionRefreshTime: 18000, @@ -122,7 +130,6 @@ func (p *Plugin) OnActivate() error { db = layeredStore } - serverID := client.System.GetDiagnosticID() p.wsPluginAdapter = ws.NewPluginAdapter(p.API, auth.New(cfg, db)) server, err := server.New(cfg, "", db, logger, serverID, p.wsPluginAdapter) diff --git a/mattermost-plugin/webapp/src/index.tsx b/mattermost-plugin/webapp/src/index.tsx index ad7a2e21d..2eaf5326d 100644 --- a/mattermost-plugin/webapp/src/index.tsx +++ b/mattermost-plugin/webapp/src/index.tsx @@ -5,6 +5,8 @@ import {Store, Action} from 'redux' import {Provider as ReduxProvider} from 'react-redux' import {useHistory} from 'mm-react-router-dom' +import {rudderAnalytics, RudderTelemetryHandler} from 'mattermost-redux/client/rudder' + import {GlobalState} from 'mattermost-redux/types/store' import {getTheme} from 'mattermost-redux/selectors/entities/preferences' @@ -20,9 +22,12 @@ import FocalboardIcon from '../../../webapp/src/widgets/icons/logo' import {setMattermostTheme} from '../../../webapp/src/theme' import wsClient, {MMWebSocketClient, ACTION_UPDATE_BLOCK} from './../../../webapp/src/wsclient' +import TelemetryClient from '../../../webapp/src/telemetry/telemetryClient' + import '../../../webapp/src/styles/focalboard-variables.scss' import '../../../webapp/src/styles/main.scss' import '../../../webapp/src/styles/labels.scss' +import octoClient from '../../../webapp/src/octoClient' import manifest from './manifest' import ErrorBoundary from './error_boundary' @@ -32,6 +37,22 @@ import {PluginRegistry} from './types/mattermost-webapp' import './plugin.scss' +const TELEMETRY_RUDDER_KEY = 'placeholder_rudder_key' +const TELEMETRY_RUDDER_DATAPLANE_URL = 'placeholder_rudder_dataplane_url' +const TELEMETRY_OPTIONS = { + context: { + ip: '0.0.0.0', + }, + page: { + path: '', + referrer: '', + search: '', + title: '', + url: '', + }, + anonymousId: '00000000000000000000000000', +} + type Props = { webSocketClient: MMWebSocketClient } @@ -150,6 +171,32 @@ export default class Plugin { this.registry.registerCustomRoute('/', MainApp) } + const config = await octoClient.getClientConfig() + if (config?.telemetry) { + let rudderKey = TELEMETRY_RUDDER_KEY + let rudderUrl = TELEMETRY_RUDDER_DATAPLANE_URL + + if (rudderKey.startsWith('placeholder') && rudderUrl.startsWith('placeholder')) { + rudderKey = process.env.RUDDER_KEY as string //eslint-disable-line no-process-env + rudderUrl = process.env.RUDDER_DATAPLANE_URL as string //eslint-disable-line no-process-env + } + + if (rudderKey !== '') { + rudderAnalytics.load(rudderKey, rudderUrl) + + rudderAnalytics.identify(config?.telemetryid, {}, TELEMETRY_OPTIONS) + + rudderAnalytics.page('BoardsLoaded', '', + TELEMETRY_OPTIONS.page, + { + context: TELEMETRY_OPTIONS.context, + anonymousId: TELEMETRY_OPTIONS.anonymousId, + }) + + TelemetryClient.setTelemetryHandler(new RudderTelemetryHandler()) + } + } + // register websocket handlers this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_BLOCK}`, (e: any) => wsClient.updateBlockHandler(e.data)) } diff --git a/mattermost-plugin/webapp/webpack.config.js b/mattermost-plugin/webapp/webpack.config.js index 0a206bb10..fd32e6ffc 100644 --- a/mattermost-plugin/webapp/webpack.config.js +++ b/mattermost-plugin/webapp/webpack.config.js @@ -143,3 +143,11 @@ module.exports = { mode, plugins, }; + +const env = {}; +env.RUDDER_KEY = JSON.stringify(process.env.RUDDER_KEY || ''); //eslint-disable-line no-process-env +env.RUDDER_DATAPLANE_URL = JSON.stringify(process.env.RUDDER_DATAPLANE_URL || ''); //eslint-disable-line no-process-env + +module.exports.plugins.push(new webpack.DefinePlugin({ + 'process.env': env, +})); diff --git a/server/api/api.go b/server/api/api.go index 39b93f4ee..f2300ba5a 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -90,6 +90,7 @@ func (a *API) RegisterRoutes(r *mux.Router) { apiv1.HandleFunc("/login", a.handleLogin).Methods("POST") apiv1.HandleFunc("/register", a.handleRegister).Methods("POST") + apiv1.HandleFunc("/clientConfig", a.getClientConfig).Methods("GET") apiv1.HandleFunc("/workspaces/{workspaceID}/{rootID}/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") @@ -115,6 +116,17 @@ func (a *API) requireCSRFToken(next http.Handler) http.Handler { }) } +func (a *API) getClientConfig(w http.ResponseWriter, r *http.Request) { + clientConfig := a.app.GetClientConfig() + + configData, err := json.Marshal(clientConfig) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + jsonBytesResponse(w, http.StatusOK, configData) +} + func (a *API) checkCSRFToken(r *http.Request) bool { token := r.Header.Get(HeaderRequestedWith) return token == HeaderRequestedWithXML diff --git a/server/app/clientConfig.go b/server/app/clientConfig.go new file mode 100644 index 000000000..a06bf78c6 --- /dev/null +++ b/server/app/clientConfig.go @@ -0,0 +1,12 @@ +package app + +import ( + "github.com/mattermost/focalboard/server/model" +) + +func (a *App) GetClientConfig() *model.ClientConfig { + return &model.ClientConfig{ + Telemetry: a.config.Telemetry, + TelemetryID: a.config.TelemetryID, + } +} diff --git a/server/model/clientConfig.go b/server/model/clientConfig.go new file mode 100644 index 000000000..8d8ff1c60 --- /dev/null +++ b/server/model/clientConfig.go @@ -0,0 +1,6 @@ +package model + +type ClientConfig struct { + Telemetry bool `json:"telemetry"` + TelemetryID string `json:"telemetryid"` +} diff --git a/server/services/config/config.go b/server/services/config/config.go index 6a5d4ac13..418e4db64 100644 --- a/server/services/config/config.go +++ b/server/services/config/config.go @@ -38,6 +38,7 @@ type Configuration struct { FilesS3Config AmazonS3Config `json:"filess3config" mapstructure:"filess3config"` FilesPath string `json:"filespath" mapstructure:"filespath"` Telemetry bool `json:"telemetry" mapstructure:"telemetry"` + TelemetryID string `json:"telemetryid" mapstructure:"telemetryid"` PrometheusAddress string `json:"prometheus_address" mapstructure:"prometheus_address"` WebhookUpdate []string `json:"webhook_update" mapstructure:"webhook_update"` Secret string `json:"secret" mapstructure:"secret"` @@ -73,6 +74,7 @@ func ReadConfigFile() (*Configuration, error) { viper.SetDefault("FilesPath", "./files") viper.SetDefault("FilesDriver", "local") viper.SetDefault("Telemetry", true) + viper.SetDefault("TelemetryID", "") viper.SetDefault("WebhookUpdate", nil) viper.SetDefault("SessionExpireTime", 60*60*24*30) // 30 days session lifetime viper.SetDefault("SessionRefreshTime", 60*60*5) // 5 minutes session refresh diff --git a/webapp/src/app.tsx b/webapp/src/app.tsx index 18165db2b..1d1ec00d5 100644 --- a/webapp/src/app.tsx +++ b/webapp/src/app.tsx @@ -12,6 +12,8 @@ import {DndProvider} from 'react-dnd' import {HTML5Backend} from 'react-dnd-html5-backend' import {TouchBackend} from 'react-dnd-touch-backend' +import TelemetryClient from './telemetry/telemetryClient' + import {getMessages} from './i18n' import {FlashMessages} from './components/flashMessages' import BoardPage from './pages/boardPage' @@ -22,15 +24,18 @@ import LoginPage from './pages/loginPage' import RegisterPage from './pages/registerPage' import {Utils} from './utils' import wsClient from './wsclient' -import {fetchMe, getLoggedIn} from './store/users' +import {fetchMe, getLoggedIn, getMe} from './store/users' import {getLanguage, fetchLanguage} from './store/language' import {setGlobalError, getGlobalError} from './store/globalError' import {useAppSelector, useAppDispatch} from './store/hooks' +import {IUser} from './user' + const App = React.memo((): JSX.Element => { const language = useAppSelector(getLanguage) const loggedIn = useAppSelector(getLoggedIn) const globalError = useAppSelector(getGlobalError) + const me = useAppSelector(getMe) const dispatch = useAppDispatch() useEffect(() => { @@ -45,6 +50,12 @@ const App = React.memo((): JSX.Element => { } }, []) + useEffect(() => { + if (me) { + TelemetryClient.setUser(me) + } + }, [me]) + let globalErrorRedirect = null if (globalError) { globalErrorRedirect = diff --git a/webapp/src/components/centerPanel.tsx b/webapp/src/components/centerPanel.tsx index ed6b37060..4f0b3d2b0 100644 --- a/webapp/src/components/centerPanel.tsx +++ b/webapp/src/components/centerPanel.tsx @@ -19,6 +19,8 @@ import {updateView} from '../store/views' import './centerPanel.scss' +import TelemetryClient from '../../../webapp/src/telemetry/telemetryClient' + import CardDialog from './cardDialog' import RootPortal from './rootPortal' import TopBar from './topBar' @@ -80,6 +82,10 @@ class CenterPanel extends React.Component { } } + componentDidMount(): void { + TelemetryClient.trackEvent('boards', 'view', {viewType: this.props.activeView.fields.viewType}) + } + constructor(props: Props) { super(props) this.state = { @@ -92,6 +98,10 @@ class CenterPanel extends React.Component { return true } + componentDidUpdate(): void { + TelemetryClient.trackEvent('boards', 'view', {viewType: this.props.activeView.fields.viewType}) + } + render(): JSX.Element { const {groupByProperty, activeView, board, views, cards} = this.props const {visible: visibleGroups, hidden: hiddenGroups} = this.getVisibleAndHiddenGroups(cards, activeView.fields.visibleOptionIds, activeView.fields.hiddenOptionIds, groupByProperty) diff --git a/webapp/src/config/clientConfig.ts b/webapp/src/config/clientConfig.ts new file mode 100644 index 000000000..14491d271 --- /dev/null +++ b/webapp/src/config/clientConfig.ts @@ -0,0 +1,7 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +export type ClientConfig = { + telemetry: boolean, + telemetryid: string, +} diff --git a/webapp/src/octoClient.ts b/webapp/src/octoClient.ts index d2e67d91d..a20d93ddd 100644 --- a/webapp/src/octoClient.ts +++ b/webapp/src/octoClient.ts @@ -6,6 +6,7 @@ import {IWorkspace} from './blocks/workspace' import {OctoUtils} from './octoUtils' import {IUser} from './user' import {Utils} from './utils' +import {ClientConfig} from './config/clientConfig' // // OctoClient is the client interface to the server APIs @@ -64,6 +65,20 @@ class OctoClient { localStorage.removeItem('focalboardSessionId') } + async getClientConfig(): Promise { + const path = '/api/v1/clientConfig' + const response = await fetch(this.serverUrl + path, { + method: 'GET', + headers: this.headers(), + }) + if (response.status !== 200) { + return null + } + + const json = (await this.getJson(response, {})) as ClientConfig + return json + } + async register(email: string, username: string, password: string, token?: string): Promise<{code: number, json: any}> { const path = '/api/v1/register' const body = JSON.stringify({email, username, password, token}) diff --git a/webapp/src/telemetry/telemetry.ts b/webapp/src/telemetry/telemetry.ts new file mode 100644 index 000000000..5bbfcec52 --- /dev/null +++ b/webapp/src/telemetry/telemetry.ts @@ -0,0 +1,7 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +export interface TelemetryHandler { + trackEvent: (userId: string, userRoles: string, category: string, event: string, props?: any) => void; + pageVisited: (userId: string, userRoles: string, category: string, name: string) => void; +} diff --git a/webapp/src/telemetry/telemetryClient.test.ts b/webapp/src/telemetry/telemetryClient.test.ts new file mode 100644 index 000000000..39d63f068 --- /dev/null +++ b/webapp/src/telemetry/telemetryClient.test.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import '@testing-library/jest-dom' + +import TelemetryClient from './telemetryClient' + +describe('trackEvent', () => { + const track = jest.fn() + const page = jest.fn() + test('should call Rudder\'s track when a RudderTelemetryHandler is attached to TelemetryClient', () => { + TelemetryClient.setTelemetryHandler() + TelemetryClient.trackEvent('test', 'onClick') + TelemetryClient.pageVisited('focalboard', 'test') + expect(track).not.toHaveBeenCalled() + expect(page).not.toHaveBeenCalled() + + TelemetryClient.setTelemetryHandler({trackEvent: track, pageVisited: page}) + TelemetryClient.trackEvent('test', 'onClick') + TelemetryClient.pageVisited('focalboard', 'test') + + expect(track).toHaveBeenCalledTimes(1) + expect(page).toHaveBeenCalledTimes(1) + }) +}) diff --git a/webapp/src/telemetry/telemetryClient.ts b/webapp/src/telemetry/telemetryClient.ts new file mode 100644 index 000000000..87a888ed1 --- /dev/null +++ b/webapp/src/telemetry/telemetryClient.ts @@ -0,0 +1,37 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import {IUser} from '../user' + +import {TelemetryHandler} from './telemetry' + +class TelemetryClient { + public telemetryHandler?: TelemetryHandler + public user?: IUser + + setTelemetryHandler(telemetryHandler?: TelemetryHandler): void { + this.telemetryHandler = telemetryHandler + } + + setUser(user: IUser): void { + this.user = user + } + + trackEvent(category: string, event: string, props?: any): void { + if (this.telemetryHandler) { + const userId = this.user?.id + this.telemetryHandler.trackEvent(userId || '', '', category, event, props) + } + } + + pageVisited(category: string, name: string): void { + if (this.telemetryHandler) { + const userId = this.user?.id + this.telemetryHandler.pageVisited(userId || '', '', category, name) + } + } +} + +const telemetryClient = new TelemetryClient() + +export {TelemetryClient} +export default telemetryClient