Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/platform/settings/components/SettingItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import FormItem from '@/components/common/FormItem.vue'
import { st } from '@/i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { SettingOption, SettingParams } from '@/platform/settings/types'
import { useTelemetry } from '@/platform/telemetry'
import type { Settings } from '@/schemas/apiSchema'
import { normalizeI18nKey } from '@/utils/formatUtil'

const props = defineProps<{
Expand Down Expand Up @@ -75,7 +77,22 @@ const formItem = computed(() => {

const settingStore = useSettingStore()
const settingValue = computed(() => settingStore.get(props.setting.id))
const updateSettingValue = async (value: any) => {
await settingStore.set(props.setting.id, value)
const updateSettingValue = async <K extends keyof Settings>(
newValue: Settings[K]
) => {
const telemetry = useTelemetry()
const settingId = props.setting.id
const previousValue = settingValue.value

await settingStore.set(settingId, newValue)

const normalizedValue = settingStore.get(settingId)
if (previousValue !== normalizedValue) {
telemetry?.trackSettingChanged({
setting_id: settingId,
previous_value: previousValue,
new_value: normalizedValue
})
}
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { app } from '@/scripts/app'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { NodeSourceType } from '@/types/nodeSource'
import { reduceAllNodes } from '@/utils/graphTraversalUtil'
import { normalizeSurveyResponses } from '../../utils/surveyNormalization'

import type {
AuthMetadata,
Expand All @@ -27,20 +26,22 @@ import type {
NodeSearchResultMetadata,
PageVisibilityMetadata,
RunButtonProperties,
SettingChangedMetadata,
SurveyResponses,
TabCountMetadata,
TelemetryEventName,
TelemetryEventProperties,
TelemetryProvider,
TemplateFilterMetadata,
TemplateLibraryMetadata,
TemplateLibraryClosedMetadata,
TemplateLibraryMetadata,
TemplateMetadata,
UiButtonClickMetadata,
WorkflowCreatedMetadata,
WorkflowImportMetadata
} from '../../types'
import { TelemetryEvents } from '../../types'
import { normalizeSurveyResponses } from '../../utils/surveyNormalization'

interface QueuedEvent {
eventName: TelemetryEventName
Expand Down Expand Up @@ -333,6 +334,10 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
this.trackEvent(TelemetryEvents.EXECUTION_SUCCESS, metadata)
}

trackSettingChanged(metadata: SettingChangedMetadata): void {
this.trackEvent(TelemetryEvents.SETTING_CHANGED, metadata)
}

trackUiButtonClicked(metadata: UiButtonClickMetadata): void {
this.trackEvent(TelemetryEvents.UI_BUTTON_CLICKED, metadata)
}
Expand Down
16 changes: 16 additions & 0 deletions src/platform/telemetry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,15 @@ export interface TabCountMetadata {
tab_count: number
}

/**
* Settings change metadata
*/
export interface SettingChangedMetadata {
setting_id: string
previous_value?: unknown
new_value?: unknown
}

/**
* Node search metadata
*/
Expand Down Expand Up @@ -306,6 +315,9 @@ export interface TelemetryProvider {
trackExecutionError(metadata: ExecutionErrorMetadata): void
trackExecutionSuccess(metadata: ExecutionSuccessMetadata): void

// Settings events
trackSettingChanged(metadata: SettingChangedMetadata): void

// Generic UI button click events
trackUiButtonClicked(metadata: UiButtonClickMetadata): void
}
Expand Down Expand Up @@ -365,6 +377,9 @@ export const TelemetryEvents = {
// Template Filter Analytics
TEMPLATE_FILTER_CHANGED: 'app:template_filter_changed',

// Settings
SETTING_CHANGED: 'app:setting_changed',

// Help Center Analytics
HELP_CENTER_OPENED: 'app:help_center_opened',
HELP_RESOURCE_CLICKED: 'app:help_resource_clicked',
Expand Down Expand Up @@ -404,6 +419,7 @@ export type TelemetryEventProperties =
| NodeSearchMetadata
| NodeSearchResultMetadata
| TemplateFilterMetadata
| SettingChangedMetadata
| UiButtonClickMetadata
| HelpCenterOpenedMetadata
| HelpResourceClickedMetadata
Expand Down
107 changes: 107 additions & 0 deletions tests-ui/tests/platform/settings/components/SettingItem.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { flushPromises, shallowMount } from '@vue/test-utils'
import { describe, expect, it, vi, beforeEach } from 'vitest'

import SettingItem from '@/platform/settings/components/SettingItem.vue'
import type { SettingParams } from '@/platform/settings/types'
import { i18n } from '@/i18n'

/**
* Verifies that SettingItem emits telemetry when its value changes
* and suppresses telemetry when the value remains the same.
*/
const trackSettingChanged = vi.fn()
vi.mock('@/platform/telemetry', () => ({
useTelemetry: vi.fn(() => ({
trackSettingChanged
}))
}))

const mockGet = vi.fn()
const mockSet = vi.fn()
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: () => ({
get: mockGet,
set: mockSet
})
}))

/**
* Minimal stub for FormItem that allows emitting `update:form-value`.
*/
const FormItemUpdateStub = {
template: '<div />',
emits: ['update:form-value'],
props: ['id', 'item', 'formValue']
}

describe('SettingItem (telemetry UI tracking)', () => {
beforeEach(() => {
vi.clearAllMocks()
})

const mountComponent = (setting: SettingParams) => {
return shallowMount(SettingItem, {
global: {
plugins: [i18n],
stubs: {
FormItem: FormItemUpdateStub,
Tag: true
}
},
props: {
setting
}
})
}

it('tracks telemetry when value changes via UI (uses normalized value)', async () => {
const settingParams: SettingParams = {
id: 'main.sub.setting.name',
name: 'Telemetry Visible',
type: 'text',
defaultValue: 'default'
}

mockGet.mockReturnValueOnce('default').mockReturnValueOnce('normalized')
mockSet.mockResolvedValue(undefined)

const wrapper = mountComponent(settingParams)

const newValue = 'newvalue'
const formItem = wrapper.findComponent(FormItemUpdateStub)
formItem.vm.$emit('update:form-value', newValue)

await flushPromises()

expect(trackSettingChanged).toHaveBeenCalledTimes(1)
expect(trackSettingChanged).toHaveBeenCalledWith(
expect.objectContaining({
setting_id: 'main.sub.setting.name',
previous_value: 'default',
new_value: 'normalized'
})
)
})

it('does not track telemetry when normalized value does not change', async () => {
const settingParams: SettingParams = {
id: 'main.sub.setting.name',
name: 'Telemetry Visible',
type: 'text',
defaultValue: 'same'
}

mockGet.mockReturnValueOnce('same').mockReturnValueOnce('same')
mockSet.mockResolvedValue(undefined)

const wrapper = mountComponent(settingParams)

const unchangedValue = 'same'
const formItem = wrapper.findComponent(FormItemUpdateStub)
formItem.vm.$emit('update:form-value', unchangedValue)

await flushPromises()

expect(trackSettingChanged).not.toHaveBeenCalled()
})
})
Loading