feat: Enhance Amplitude tracking across various components (#29662)

Co-authored-by: CodingOnStar <hanxujiang@dify.ai>
This commit is contained in:
Coding On Star
2025-12-15 15:25:10 +08:00
committed by GitHub
parent 724cd57dbf
commit d942adf3b2
15 changed files with 96 additions and 5 deletions

View File

@@ -51,6 +51,7 @@ import { AppModeEnum } from '@/types/app'
import type { PublishWorkflowParams } from '@/types/workflow'
import { basePath } from '@/utils/var'
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
import { trackEvent } from '@/app/components/base/amplitude'
const ACCESS_MODE_MAP: Record<AccessMode, { label: string, icon: React.ElementType }> = {
[AccessMode.ORGANIZATION]: {
@@ -189,11 +190,12 @@ const AppPublisher = ({
try {
await onPublish?.(params)
setPublished(true)
trackEvent('app_published_time', { action_mode: 'app', app_id: appDetail?.id, app_name: appDetail?.name })
}
catch {
setPublished(false)
}
}, [onPublish])
}, [appDetail, onPublish])
const handleRestore = useCallback(async () => {
try {

View File

@@ -15,6 +15,43 @@ export const isAmplitudeEnabled = () => {
return IS_CLOUD_EDITION && !!AMPLITUDE_API_KEY
}
// Map URL pathname to English page name for consistent Amplitude tracking
const getEnglishPageName = (pathname: string): string => {
// Remove leading slash and get the first segment
const segments = pathname.replace(/^\//, '').split('/')
const firstSegment = segments[0] || 'home'
const pageNameMap: Record<string, string> = {
'': 'Home',
'apps': 'Studio',
'datasets': 'Knowledge',
'explore': 'Explore',
'tools': 'Tools',
'account': 'Account',
'signin': 'Sign In',
'signup': 'Sign Up',
}
return pageNameMap[firstSegment] || firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1)
}
// Enrichment plugin to override page title with English name for page view events
const pageNameEnrichmentPlugin = (): amplitude.Types.EnrichmentPlugin => {
return {
name: 'page-name-enrichment',
type: 'enrichment',
setup: async () => undefined,
execute: async (event: amplitude.Types.Event) => {
// Only modify page view events
if (event.event_type === '[Amplitude] Page Viewed' && event.event_properties) {
const pathname = typeof window !== 'undefined' ? window.location.pathname : ''
event.event_properties['[Amplitude] Page Title'] = getEnglishPageName(pathname)
}
return event
},
}
}
const AmplitudeProvider: FC<IAmplitudeProps> = ({
sessionReplaySampleRate = 1,
}) => {
@@ -31,10 +68,11 @@ const AmplitudeProvider: FC<IAmplitudeProps> = ({
formInteractions: true,
fileDownloads: true,
},
// Enable debug logs in development environment
logLevel: amplitude.Types.LogLevel.Warn,
})
// Add page name enrichment plugin to override page title with English name
amplitude.add(pageNameEnrichmentPlugin())
// Add Session Replay plugin
const sessionReplay = sessionReplayPlugin({
sampleRate: sessionReplaySampleRate,

View File

@@ -5,6 +5,7 @@ import { useCreatePipelineDataset } from '@/service/knowledge/use-create-dataset
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
import Toast from '@/app/components/base/toast'
import { useRouter } from 'next/navigation'
import { trackEvent } from '@/app/components/base/amplitude'
const CreateCard = () => {
const { t } = useTranslation()
@@ -23,6 +24,9 @@ const CreateCard = () => {
message: t('datasetPipeline.creation.successTip'),
})
invalidDatasetList()
trackEvent('create_datasets_from_scratch', {
dataset_id: id,
})
push(`/datasets/${id}/pipeline`)
}
},

View File

@@ -19,6 +19,7 @@ import Content from './content'
import Actions from './actions'
import { useCreatePipelineDatasetFromCustomized } from '@/service/knowledge/use-create-dataset'
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
import { trackEvent } from '@/app/components/base/amplitude'
type TemplateCardProps = {
pipeline: PipelineTemplate
@@ -66,6 +67,11 @@ const TemplateCard = ({
invalidDatasetList()
if (newDataset.pipeline_id)
await handleCheckPluginDependencies(newDataset.pipeline_id, true)
trackEvent('create_datasets_with_pipeline', {
template_name: pipeline.name,
template_id: pipeline.id,
template_type: type,
})
push(`/datasets/${newDataset.dataset_id}/pipeline`)
},
onError: () => {
@@ -75,7 +81,7 @@ const TemplateCard = ({
})
},
})
}, [getPipelineTemplateInfo, createDataset, t, handleCheckPluginDependencies, push, invalidDatasetList])
}, [getPipelineTemplateInfo, createDataset, t, handleCheckPluginDependencies, push, invalidDatasetList, pipeline.name, pipeline.id, type])
const handleShowTemplateDetails = useCallback(() => {
setShowDetailModal(true)

View File

@@ -12,6 +12,7 @@ import Button from '@/app/components/base/button'
import { ToastContext } from '@/app/components/base/toast'
import { createEmptyDataset } from '@/service/datasets'
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
import { trackEvent } from '@/app/components/base/amplitude'
type IProps = {
show: boolean
@@ -40,6 +41,10 @@ const EmptyDatasetCreationModal = ({
try {
const dataset = await createEmptyDataset({ name: inputValue })
invalidDatasetList()
trackEvent('create_empty_datasets', {
name: inputValue,
dataset_id: dataset.id,
})
onHide()
router.push(`/datasets/${dataset.id}/documents`)
}

View File

@@ -64,6 +64,7 @@ import { noop } from 'lodash-es'
import { useDocLink } from '@/context/i18n'
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
import { checkShowMultiModalTip } from '../../settings/utils'
import { trackEvent } from '@/app/components/base/amplitude'
const TextLabel: FC<PropsWithChildren> = (props) => {
return <label className='system-sm-semibold text-text-secondary'>{props.children}</label>
@@ -568,6 +569,10 @@ const StepTwo = ({
if (mutateDatasetRes)
mutateDatasetRes()
invalidDatasetList()
trackEvent('create_datasets', {
data_source_type: dataSourceType,
indexing_technique: getIndexing_technique(),
})
onStepChange?.(+1)
if (isSetting)
onSave?.()

View File

@@ -40,6 +40,7 @@ import UpgradeCard from '../../create/step-one/upgrade-card'
import Divider from '@/app/components/base/divider'
import { useBoolean } from 'ahooks'
import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal'
import { trackEvent } from '@/app/components/base/amplitude'
const CreateFormPipeline = () => {
const { t } = useTranslation()
@@ -343,6 +344,10 @@ const CreateFormPipeline = () => {
setBatchId((res as PublishedPipelineRunResponse).batch || '')
setDocuments((res as PublishedPipelineRunResponse).documents || [])
handleNextStep()
trackEvent('dataset_document_added', {
data_source_type: datasourceType,
indexing_technique: 'pipeline',
})
},
})
}, [dataSourceStore, datasource, datasourceType, handleNextStep, pipelineId, runPublishedPipeline])

View File

@@ -6,6 +6,7 @@ import { useToastContext } from '@/app/components/base/toast'
import ExternalKnowledgeBaseCreate from '@/app/components/datasets/external-knowledge-base/create'
import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations'
import { createExternalKnowledgeBase } from '@/service/datasets'
import { trackEvent } from '@/app/components/base/amplitude'
const ExternalKnowledgeBaseConnector = () => {
const { notify } = useToastContext()
@@ -18,6 +19,10 @@ const ExternalKnowledgeBaseConnector = () => {
const result = await createExternalKnowledgeBase({ body: formValue })
if (result && result.id) {
notify({ type: 'success', message: 'External Knowledge Base Connected Successfully' })
trackEvent('create_external_knowledge_base', {
provider: formValue.provider,
name: formValue.name,
})
router.back()
}
else { throw new Error('Failed to create external knowledge base') }

View File

@@ -44,6 +44,7 @@ import { AUTO_UPDATE_MODE } from '../reference-setting-modal/auto-update-setting
import { convertUTCDaySecondsToLocalSeconds, timeOfDayToDayjs } from '../reference-setting-modal/auto-update-setting/utils'
import type { PluginDetail } from '../types'
import { PluginCategoryEnum, PluginSource } from '../types'
import { trackEvent } from '@/app/components/base/amplitude'
const i18nPrefix = 'plugin.action'
@@ -212,8 +213,9 @@ const DetailHeader = ({
refreshModelProviders()
if (PluginCategoryEnum.tool.includes(category))
invalidateAllToolProviders()
trackEvent('plugin_uninstalled', { plugin_id, plugin_name: name })
}
}, [showDeleting, id, hideDeleting, hideDeleteConfirm, onUpdate, category, refreshModelProviders, invalidateAllToolProviders])
}, [showDeleting, id, hideDeleting, hideDeleteConfirm, onUpdate, category, refreshModelProviders, invalidateAllToolProviders, plugin_id, name])
return (
<div className={cn('shrink-0 border-b border-divider-subtle bg-components-panel-bg p-4 pb-3', isReadmeView && 'border-b-0 bg-transparent p-0')}>

View File

@@ -21,6 +21,7 @@ import { useDataSourceStore, useDataSourceStoreWithSelector } from '@/app/compon
import { useShallow } from 'zustand/react/shallow'
import { useWorkflowStore } from '@/app/components/workflow/store'
import StepIndicator from './step-indicator'
import { trackEvent } from '@/app/components/base/amplitude'
const Preparation = () => {
const {
@@ -121,6 +122,7 @@ const Preparation = () => {
datasource_type: datasourceType,
datasource_info_list: datasourceInfoList,
})
trackEvent('pipeline_start_action_time', { action_type: 'document_processing' })
setIsPreparingDataSource?.(false)
}, [dataSourceStore, datasource, datasourceType, handleRun, workflowStore])

View File

@@ -47,6 +47,7 @@ import { useModalContextSelector } from '@/context/modal-context'
import Link from 'next/link'
import { useDatasetApiAccessUrl } from '@/hooks/use-api-access-url'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { trackEvent } from '@/app/components/base/amplitude'
const PUBLISH_SHORTCUT = ['ctrl', '⇧', 'P']
@@ -109,6 +110,7 @@ const Popup = () => {
releaseNotes: params?.releaseNotes || '',
})
setPublished(true)
trackEvent('app_published_time', { action_mode: 'pipeline', app_id: datasetId, app_name: params?.title || '' })
if (res) {
notify({
type: 'success',

View File

@@ -29,6 +29,7 @@ import { post } from '@/service/base'
import { ContentType } from '@/service/fetch'
import { TriggerType } from '@/app/components/workflow/header/test-run-menu'
import { AppModeEnum } from '@/types/app'
import { trackEvent } from '@/app/components/base/amplitude'
type HandleRunMode = TriggerType
type HandleRunOptions = {
@@ -359,6 +360,7 @@ export const useWorkflowRun = () => {
if (onError)
onError(params)
trackEvent('workflow_run_failed', { workflow_id: flowId, reason: params.error, node_type: params.node_type })
}
const wrappedOnCompleted: IOtherOptions['onCompleted'] = async (hasError?: boolean, errorMessage?: string) => {

View File

@@ -13,6 +13,7 @@ import { useTranslation } from 'react-i18next'
import useTheme from '@/hooks/use-theme'
import { Theme } from '@/types/app'
import { basePath } from '@/utils/var'
import { trackEvent } from '@/app/components/base/amplitude'
const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => {
if (!icon)
@@ -102,6 +103,10 @@ const ToolItem: FC<Props> = ({
params,
meta: provider.meta,
})
trackEvent('tool_selected', {
tool_name: payload.name,
plugin_id: provider.plugin_id,
})
}}
>
<div className={cn('system-sm-medium h-8 truncate border-l-2 border-divider-subtle pl-4 leading-8 text-text-secondary')}>

View File

@@ -12,6 +12,7 @@ import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAnd
import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options'
import TestRunMenu, { type TestRunMenuRef, type TriggerOption, TriggerType } from './test-run-menu'
import { useToastContext } from '@/app/components/base/toast'
import { trackEvent } from '@/app/components/base/amplitude'
type RunModeProps = {
text?: string
@@ -69,22 +70,27 @@ const RunMode = ({
if (option.type === TriggerType.UserInput) {
handleWorkflowStartRunInWorkflow()
trackEvent('app_start_action_time', { action_type: 'user_input' })
}
else if (option.type === TriggerType.Schedule) {
handleWorkflowTriggerScheduleRunInWorkflow(option.nodeId)
trackEvent('app_start_action_time', { action_type: 'schedule' })
}
else if (option.type === TriggerType.Webhook) {
if (option.nodeId)
handleWorkflowTriggerWebhookRunInWorkflow({ nodeId: option.nodeId })
trackEvent('app_start_action_time', { action_type: 'webhook' })
}
else if (option.type === TriggerType.Plugin) {
if (option.nodeId)
handleWorkflowTriggerPluginRunInWorkflow(option.nodeId)
trackEvent('app_start_action_time', { action_type: 'plugin' })
}
else if (option.type === TriggerType.All) {
const targetNodeIds = option.relatedNodeIds?.filter(Boolean)
if (targetNodeIds && targetNodeIds.length > 0)
handleWorkflowRunAllTriggersInWorkflow(targetNodeIds)
trackEvent('app_start_action_time', { action_type: 'all' })
}
else {
// Placeholder for trigger-specific execution logic for schedule, webhook, plugin types

View File

@@ -68,6 +68,7 @@ import {
useAllMCPTools,
useAllWorkflowTools,
} from '@/service/use-tools'
import { trackEvent } from '@/app/components/base/amplitude'
// eslint-disable-next-line ts/no-unsafe-function-type
const checkValidFns: Partial<Record<BlockEnum, Function>> = {
@@ -973,6 +974,7 @@ const useOneStepRun = <T>({
_singleRunningStatus: NodeRunningStatus.Failed,
},
})
trackEvent('workflow_run_failed', { workflow_id: flowId, node_id: id, reason: res.error, node_type: data?.type })
},
},
)