diff --git a/app/src/organisms/Desktop/SystemLanguagePreferenceModal/__tests__/SystemLanguagePreferenceModal.test.tsx b/app/src/organisms/Desktop/SystemLanguagePreferenceModal/__tests__/SystemLanguagePreferenceModal.test.tsx
index 104085fcb15..af4c4feb5d0 100644
--- a/app/src/organisms/Desktop/SystemLanguagePreferenceModal/__tests__/SystemLanguagePreferenceModal.test.tsx
+++ b/app/src/organisms/Desktop/SystemLanguagePreferenceModal/__tests__/SystemLanguagePreferenceModal.test.tsx
@@ -5,6 +5,10 @@ import { describe, it, vi, afterEach, beforeEach, expect } from 'vitest'
import { renderWithProviders } from '/app/__testing-utils__'
import { i18n } from '/app/i18n'
+import {
+ ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_MODAL,
+ useTrackEvent,
+} from '/app/redux/analytics'
import {
getAppLanguage,
getStoredSystemLanguage,
@@ -16,6 +20,7 @@ import { SystemLanguagePreferenceModal } from '..'
vi.mock('react-router-dom')
vi.mock('/app/redux/config')
vi.mock('/app/redux/shell')
+vi.mock('/app/redux/analytics')
const render = () => {
return renderWithProviders(, {
@@ -24,6 +29,7 @@ const render = () => {
}
const mockNavigate = vi.fn()
+const mockTrackEvent = vi.fn()
const MOCK_DEFAULT_LANGUAGE = 'en-US'
@@ -33,6 +39,7 @@ describe('SystemLanguagePreferenceModal', () => {
vi.mocked(getSystemLanguage).mockReturnValue(MOCK_DEFAULT_LANGUAGE)
vi.mocked(getStoredSystemLanguage).mockReturnValue(MOCK_DEFAULT_LANGUAGE)
vi.mocked(useNavigate).mockReturnValue(mockNavigate)
+ vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent)
})
afterEach(() => {
vi.resetAllMocks()
@@ -68,6 +75,14 @@ describe('SystemLanguagePreferenceModal', () => {
'language.systemLanguage',
MOCK_DEFAULT_LANGUAGE
)
+ expect(mockTrackEvent).toBeCalledWith({
+ name: ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_MODAL,
+ properties: {
+ language: MOCK_DEFAULT_LANGUAGE,
+ systemLanguage: MOCK_DEFAULT_LANGUAGE,
+ modalType: 'appBootModal',
+ },
+ })
})
it('should default to English (US) if system language is unsupported', () => {
@@ -90,6 +105,14 @@ describe('SystemLanguagePreferenceModal', () => {
MOCK_DEFAULT_LANGUAGE
)
expect(updateConfigValue).toBeCalledWith('language.systemLanguage', 'es-MX')
+ expect(mockTrackEvent).toBeCalledWith({
+ name: ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_MODAL,
+ properties: {
+ language: MOCK_DEFAULT_LANGUAGE,
+ systemLanguage: 'es-MX',
+ modalType: 'appBootModal',
+ },
+ })
})
it('should set a supported app language when system language is an unsupported locale of the same language', () => {
@@ -112,6 +135,14 @@ describe('SystemLanguagePreferenceModal', () => {
MOCK_DEFAULT_LANGUAGE
)
expect(updateConfigValue).toBeCalledWith('language.systemLanguage', 'en-GB')
+ expect(mockTrackEvent).toBeCalledWith({
+ name: ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_MODAL,
+ properties: {
+ language: MOCK_DEFAULT_LANGUAGE,
+ systemLanguage: 'en-GB',
+ modalType: 'appBootModal',
+ },
+ })
})
it('should render the correct header, description, and buttons when system language changes', () => {
@@ -139,6 +170,14 @@ describe('SystemLanguagePreferenceModal', () => {
'language.systemLanguage',
'zh-CN'
)
+ expect(mockTrackEvent).toBeCalledWith({
+ name: ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_MODAL,
+ properties: {
+ language: 'zh-CN',
+ systemLanguage: 'zh-CN',
+ modalType: 'systemLanguageUpdateModal',
+ },
+ })
fireEvent.click(secondaryButton)
expect(updateConfigValue).toHaveBeenNthCalledWith(
3,
@@ -168,6 +207,14 @@ describe('SystemLanguagePreferenceModal', () => {
'language.systemLanguage',
'zh-Hant'
)
+ expect(mockTrackEvent).toBeCalledWith({
+ name: ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_MODAL,
+ properties: {
+ language: 'zh-CN',
+ systemLanguage: 'zh-Hant',
+ modalType: 'systemLanguageUpdateModal',
+ },
+ })
fireEvent.click(secondaryButton)
expect(updateConfigValue).toHaveBeenNthCalledWith(
3,
diff --git a/app/src/organisms/Desktop/SystemLanguagePreferenceModal/index.tsx b/app/src/organisms/Desktop/SystemLanguagePreferenceModal/index.tsx
index d3b04f19061..f135c0fe10a 100644
--- a/app/src/organisms/Desktop/SystemLanguagePreferenceModal/index.tsx
+++ b/app/src/organisms/Desktop/SystemLanguagePreferenceModal/index.tsx
@@ -16,6 +16,10 @@ import {
} from '@opentrons/components'
import { LANGUAGES } from '/app/i18n'
+import {
+ ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_MODAL,
+ useTrackEvent,
+} from '/app/redux/analytics'
import {
getAppLanguage,
getStoredSystemLanguage,
@@ -32,7 +36,7 @@ type ArrayElement<
export function SystemLanguagePreferenceModal(): JSX.Element | null {
const { i18n, t } = useTranslation(['app_settings', 'shared', 'branded'])
-
+ const trackEvent = useTrackEvent()
const [currentOption, setCurrentOption] = useState(
LANGUAGES[0]
)
@@ -66,6 +70,16 @@ export function SystemLanguagePreferenceModal(): JSX.Element | null {
const handlePrimaryClick = (): void => {
dispatch(updateConfigValue('language.appLanguage', currentOption.value))
dispatch(updateConfigValue('language.systemLanguage', systemLanguage))
+ trackEvent({
+ name: ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_MODAL,
+ properties: {
+ language: currentOption.value,
+ systemLanguage,
+ modalType: showUpdateModal
+ ? 'systemLanguageUpdateModal'
+ : 'appBootModal',
+ },
+ })
}
const handleDropdownClick = (value: string): void => {
diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/LanguageSetting.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/LanguageSetting.tsx
index 49f58e26993..50af850a44f 100644
--- a/app/src/organisms/ODD/RobotSettingsDashboard/LanguageSetting.tsx
+++ b/app/src/organisms/ODD/RobotSettingsDashboard/LanguageSetting.tsx
@@ -1,7 +1,8 @@
-import { Fragment } from 'react'
+import { Fragment, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import styled from 'styled-components'
+import uuidv1 from 'uuid/v4'
import {
BORDERS,
@@ -14,6 +15,8 @@ import {
} from '@opentrons/components'
import { LANGUAGES } from '/app/i18n'
+import { ANALYTICS_LANGUAGE_UPDATED_ODD_SETTINGS } from '/app/redux/analytics'
+import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics'
import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation'
import { getAppLanguage, updateConfigValue } from '/app/redux/config'
@@ -42,16 +45,31 @@ interface LanguageSettingProps {
setCurrentOption: SetSettingOption
}
+const uuid: () => string = uuidv1
+
export function LanguageSetting({
setCurrentOption,
}: LanguageSettingProps): JSX.Element {
const { t } = useTranslation('app_settings')
const dispatch = useDispatch()
+ const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial()
+
+ let transactionId = ''
+ useEffect(() => {
+ transactionId = uuid()
+ }, [])
const appLanguage = useSelector(getAppLanguage)
const handleChange = (event: ChangeEvent): void => {
dispatch(updateConfigValue('language.appLanguage', event.target.value))
+ trackEventWithRobotSerial({
+ name: ANALYTICS_LANGUAGE_UPDATED_ODD_SETTINGS,
+ properties: {
+ language: event.target.value,
+ transactionId,
+ },
+ })
}
return (
diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/LanguageSetting.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/LanguageSetting.test.tsx
index 80d35ebea15..fe90eb2e1cb 100644
--- a/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/LanguageSetting.test.tsx
+++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/LanguageSetting.test.tsx
@@ -10,14 +10,18 @@ import {
SIMPLIFIED_CHINESE_DISPLAY_NAME,
SIMPLIFIED_CHINESE,
} from '/app/i18n'
+import { ANALYTICS_LANGUAGE_UPDATED_ODD_SETTINGS } from '/app/redux/analytics'
+import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics'
import { getAppLanguage, updateConfigValue } from '/app/redux/config'
import { renderWithProviders } from '/app/__testing-utils__'
import { LanguageSetting } from '../LanguageSetting'
vi.mock('/app/redux/config')
+vi.mock('/app/redux-resources/analytics')
const mockSetCurrentOption = vi.fn()
+const mockTrackEvent = vi.fn()
const render = (props: React.ComponentProps) => {
return renderWithProviders(, {
@@ -32,6 +36,9 @@ describe('LanguageSetting', () => {
setCurrentOption: mockSetCurrentOption,
}
vi.mocked(getAppLanguage).mockReturnValue(US_ENGLISH)
+ vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({
+ trackEventWithRobotSerial: mockTrackEvent,
+ })
})
it('should render text and buttons', () => {
@@ -49,6 +56,13 @@ describe('LanguageSetting', () => {
'language.appLanguage',
SIMPLIFIED_CHINESE
)
+ expect(mockTrackEvent).toHaveBeenCalledWith({
+ name: ANALYTICS_LANGUAGE_UPDATED_ODD_SETTINGS,
+ properties: {
+ language: SIMPLIFIED_CHINESE,
+ transactionId: expect.anything(),
+ },
+ })
})
it('should call mock function when tapping back button', () => {
diff --git a/app/src/pages/Desktop/AppSettings/GeneralSettings.tsx b/app/src/pages/Desktop/AppSettings/GeneralSettings.tsx
index 85b24816da6..eed4f5be96a 100644
--- a/app/src/pages/Desktop/AppSettings/GeneralSettings.tsx
+++ b/app/src/pages/Desktop/AppSettings/GeneralSettings.tsx
@@ -1,8 +1,9 @@
// app info card with version and updated
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import { useSelector, useDispatch } from 'react-redux'
+import uuidv1 from 'uuid/v4'
import {
ALIGN_CENTER,
@@ -41,6 +42,7 @@ import {
import {
useTrackEvent,
ANALYTICS_APP_UPDATE_NOTIFICATIONS_TOGGLED,
+ ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_SETTINGS,
} from '/app/redux/analytics'
import { getAppLanguage, updateConfigValue } from '/app/redux/config'
import { UpdateAppModal } from '/app/organisms/Desktop/UpdateAppModal'
@@ -55,6 +57,7 @@ const GITHUB_LINK =
'https://github.com/Opentrons/opentrons/blob/edge/app-shell/build/release-notes.md'
const ENABLE_APP_UPDATE_NOTIFICATIONS = 'Enable app update notifications'
+const uuid: () => string = uuidv1
export function GeneralSettings(): JSX.Element {
const { t } = useTranslation(['app_settings', 'shared', 'branded'])
@@ -68,9 +71,19 @@ export function GeneralSettings(): JSX.Element {
const appLanguage = useSelector(getAppLanguage)
const currentLanguageOption = LANGUAGES.find(lng => lng.value === appLanguage)
-
+ let transactionId = ''
+ useEffect(() => {
+ transactionId = uuid()
+ }, [])
const handleDropdownClick = (value: string): void => {
dispatch(updateConfigValue('language.appLanguage', value))
+ trackEvent({
+ name: ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_SETTINGS,
+ properties: {
+ language: value,
+ transactionId,
+ },
+ })
}
const [showUpdateBanner, setShowUpdateBanner] = useState(
diff --git a/app/src/pages/Desktop/AppSettings/__test__/GeneralSettings.test.tsx b/app/src/pages/Desktop/AppSettings/__test__/GeneralSettings.test.tsx
index 43d17a4d2cb..a06f4204bd7 100644
--- a/app/src/pages/Desktop/AppSettings/__test__/GeneralSettings.test.tsx
+++ b/app/src/pages/Desktop/AppSettings/__test__/GeneralSettings.test.tsx
@@ -12,6 +12,10 @@ import {
US_ENGLISH_DISPLAY_NAME,
} from '/app/i18n'
import { getAlertIsPermanentlyIgnored } from '/app/redux/alerts'
+import {
+ ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_SETTINGS,
+ useTrackEvent,
+} from '/app/redux/analytics'
import { getAppLanguage, updateConfigValue } from '/app/redux/config'
import * as Shell from '/app/redux/shell'
import { GeneralSettings } from '../GeneralSettings'
@@ -32,11 +36,14 @@ const render = (): ReturnType => {
)
}
+const mockTrackEvent = vi.fn()
+
describe('GeneralSettings', () => {
beforeEach(() => {
vi.mocked(Shell.getAvailableShellUpdate).mockReturnValue(null)
vi.mocked(getAlertIsPermanentlyIgnored).mockReturnValue(false)
vi.mocked(getAppLanguage).mockReturnValue(US_ENGLISH)
+ vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent)
})
afterEach(() => {
vi.resetAllMocks()
@@ -118,5 +125,12 @@ describe('GeneralSettings', () => {
'language.appLanguage',
SIMPLIFIED_CHINESE
)
+ expect(mockTrackEvent).toHaveBeenCalledWith({
+ name: ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_SETTINGS,
+ properties: {
+ language: SIMPLIFIED_CHINESE,
+ transactionId: expect.anything(),
+ },
+ })
})
})
diff --git a/app/src/pages/ODD/ChooseLanguage/__tests__/ChooseLanguage.test.tsx b/app/src/pages/ODD/ChooseLanguage/__tests__/ChooseLanguage.test.tsx
index 8508a7b4d08..fa5c793e2d2 100644
--- a/app/src/pages/ODD/ChooseLanguage/__tests__/ChooseLanguage.test.tsx
+++ b/app/src/pages/ODD/ChooseLanguage/__tests__/ChooseLanguage.test.tsx
@@ -1,10 +1,12 @@
-import { vi, it, describe, expect } from 'vitest'
+import { vi, it, describe, expect, beforeEach } from 'vitest'
import { fireEvent, screen } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import { renderWithProviders } from '/app/__testing-utils__'
import { i18n } from '/app/i18n'
-import { updateConfigValue } from '/app/redux/config'
+import { ANALYTICS_LANGUAGE_UPDATED_ODD_UNBOXING_FLOW } from '/app/redux/analytics'
+import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics'
+import { updateConfigValue, getAppLanguage } from '/app/redux/config'
import { ChooseLanguage } from '..'
import type { NavigateFunction } from 'react-router-dom'
@@ -18,6 +20,9 @@ vi.mock('react-router-dom', async importOriginal => {
}
})
vi.mock('/app/redux/config')
+vi.mock('/app/redux-resources/analytics')
+
+const mockTrackEvent = vi.fn()
const render = () => {
return renderWithProviders(
@@ -31,6 +36,12 @@ const render = () => {
}
describe('ChooseLanguage', () => {
+ beforeEach(() => {
+ vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({
+ trackEventWithRobotSerial: mockTrackEvent,
+ })
+ vi.mocked(getAppLanguage).mockReturnValue('en-US')
+ })
it('should render text, language options, and continue button', () => {
render()
screen.getByText('Choose your language')
@@ -54,6 +65,12 @@ describe('ChooseLanguage', () => {
it('should call mockNavigate when tapping continue', () => {
render()
fireEvent.click(screen.getByRole('button', { name: 'Continue' }))
+ expect(mockTrackEvent).toHaveBeenCalledWith({
+ name: ANALYTICS_LANGUAGE_UPDATED_ODD_UNBOXING_FLOW,
+ properties: {
+ language: 'en-US',
+ },
+ })
expect(mockNavigate).toHaveBeenCalledWith('/welcome')
})
})
diff --git a/app/src/pages/ODD/ChooseLanguage/index.tsx b/app/src/pages/ODD/ChooseLanguage/index.tsx
index d0110e68591..8ecb87451f7 100644
--- a/app/src/pages/ODD/ChooseLanguage/index.tsx
+++ b/app/src/pages/ODD/ChooseLanguage/index.tsx
@@ -13,6 +13,8 @@ import {
TYPOGRAPHY,
} from '@opentrons/components'
+import { ANALYTICS_LANGUAGE_UPDATED_ODD_UNBOXING_FLOW } from '/app/redux/analytics'
+import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics'
import { MediumButton } from '/app/atoms/buttons'
import { LANGUAGES, US_ENGLISH } from '/app/i18n'
import { RobotSetupHeader } from '/app/organisms/ODD/RobotSetupHeader'
@@ -24,6 +26,7 @@ export function ChooseLanguage(): JSX.Element {
const { i18n, t } = useTranslation(['app_settings', 'shared'])
const navigate = useNavigate()
const dispatch = useDispatch()
+ const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial()
const appLanguage = useSelector(getAppLanguage)
@@ -69,6 +72,12 @@ export function ChooseLanguage(): JSX.Element {
{
+ trackEventWithRobotSerial({
+ name: ANALYTICS_LANGUAGE_UPDATED_ODD_UNBOXING_FLOW,
+ properties: {
+ language: appLanguage,
+ },
+ })
navigate('/welcome')
}}
width="100%"
diff --git a/app/src/redux-resources/analytics/hooks/__tests__/useTrackProtocolRunEvent.test.tsx b/app/src/redux-resources/analytics/hooks/__tests__/useTrackProtocolRunEvent.test.tsx
index f769dc005c4..c12bcd5dbeb 100644
--- a/app/src/redux-resources/analytics/hooks/__tests__/useTrackProtocolRunEvent.test.tsx
+++ b/app/src/redux-resources/analytics/hooks/__tests__/useTrackProtocolRunEvent.test.tsx
@@ -12,6 +12,7 @@ import {
useTrackEvent,
ANALYTICS_PROTOCOL_RUN_ACTION,
} from '/app/redux/analytics'
+import { getAppLanguage } from '/app/redux/config'
import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__'
import { useRobot } from '/app/redux-resources/robots'
@@ -23,6 +24,7 @@ vi.mock('../useProtocolRunAnalyticsData')
vi.mock('/app/redux/discovery')
vi.mock('/app/redux/pipettes')
vi.mock('/app/redux/analytics')
+vi.mock('/app/redux/config')
vi.mock('/app/redux/robot-settings')
const RUN_ID = 'runId'
@@ -55,6 +57,7 @@ describe('useTrackProtocolRunEvent hook', () => {
)
vi.mocked(useRobot).mockReturnValue(mockConnectableRobot)
vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent)
+ vi.mocked(getAppLanguage).mockReturnValue('en-US')
when(vi.mocked(useProtocolRunAnalyticsData))
.calledWith(RUN_ID, mockConnectableRobot)
@@ -88,7 +91,11 @@ describe('useTrackProtocolRunEvent hook', () => {
)
expect(mockTrackEvent).toHaveBeenCalledWith({
name: ANALYTICS_PROTOCOL_RUN_ACTION.START,
- properties: { ...PROTOCOL_PROPERTIES, transactionId: RUN_ID },
+ properties: {
+ ...PROTOCOL_PROPERTIES,
+ transactionId: RUN_ID,
+ appLanguage: 'en-US',
+ },
})
})
diff --git a/app/src/redux-resources/analytics/hooks/useTrackProtocolRunEvent.ts b/app/src/redux-resources/analytics/hooks/useTrackProtocolRunEvent.ts
index 05c3ce16746..0603994d4b4 100644
--- a/app/src/redux-resources/analytics/hooks/useTrackProtocolRunEvent.ts
+++ b/app/src/redux-resources/analytics/hooks/useTrackProtocolRunEvent.ts
@@ -1,5 +1,7 @@
+import { useSelector } from 'react-redux'
import { useTrackEvent } from '/app/redux/analytics'
import { useProtocolRunAnalyticsData } from './useProtocolRunAnalyticsData'
+import { getAppLanguage } from '/app/redux/config'
import { useRobot } from '/app/redux-resources/robots'
interface ProtocolRunAnalyticsEvent {
@@ -21,7 +23,7 @@ export function useTrackProtocolRunEvent(
runId,
robot
)
-
+ const appLanguage = useSelector(getAppLanguage)
const trackProtocolRunEvent: TrackProtocolRunEvent = ({
name,
properties = {},
@@ -37,6 +39,7 @@ export function useTrackProtocolRunEvent(
// It's sometimes unavoidable (namely on the desktop app) to prevent sending an event multiple times.
// In these circumstances, we need an idempotency key to accurately filter events in Mixpanel.
transactionId: runId,
+ appLanguage,
},
})
})
diff --git a/app/src/redux/analytics/constants.ts b/app/src/redux/analytics/constants.ts
index cde9b0a1d59..aadeb7c6696 100644
--- a/app/src/redux/analytics/constants.ts
+++ b/app/src/redux/analytics/constants.ts
@@ -103,3 +103,15 @@ export const ANALYTICS_QUICK_TRANSFER_RERUN = 'quickTransferReRunFromSummary'
*/
export const ANALYTICS_RESOURCE_MONITOR_REPORT: 'analytics:RESOURCE_MONITOR_REPORT' =
'analytics:RESOURCE_MONITOR_REPORT'
+
+/**
+ * Internationalization Analytics
+ */
+export const ANALYTICS_LANGUAGE_UPDATED_ODD_UNBOXING_FLOW: 'languageUpdatedOddUnboxingFlow' =
+ 'languageUpdatedOddUnboxingFlow'
+export const ANALYTICS_LANGUAGE_UPDATED_ODD_SETTINGS: 'languageUpdatedOddSettings' =
+ 'languageUpdatedOddSettings'
+export const ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_MODAL: 'languageUpdatedDesktopAppModal' =
+ 'languageUpdatedDesktopAppModal'
+export const ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_SETTINGS: 'languageUpdatedDesktopAppSettings' =
+ 'languageUpdatedDesktopAppSettings'