feat: enhance event tracking with Amplitude integration across components

- Added event tracking for various user actions including dataset creation, plugin deletion, and tool selection.
- Introduced  prop in  for session replay functionality.
- Updated user properties in context based on user profile and workspace details.
- Refactored components to utilize the new tracking functions for improved analytics.
This commit is contained in:
CodingOnStar 2025-11-18 13:56:32 +08:00
parent 6b95723f1c
commit 3f01c06451
15 changed files with 1480 additions and 1347 deletions

View File

@ -7,6 +7,7 @@ import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser'
export type IAmplitudeProps = {
apiKey?: string
enableSessionReplay?: boolean
}
const AmplitudeProvider: FC<IAmplitudeProps> = ({

View File

@ -49,6 +49,7 @@ import { fetchInstalledAppList } from '@/service/explore'
import { AppModeEnum } from '@/types/app'
import type { PublishWorkflowParams } from '@/types/workflow'
import { basePath } from '@/utils/var'
import { trackEvent } from '../../amplitude'
const ACCESS_MODE_MAP: Record<AccessMode, { label: string, icon: React.ElementType }> = {
[AccessMode.ORGANIZATION]: {
@ -182,6 +183,12 @@ const AppPublisher = ({
try {
await onPublish?.(params)
setPublished(true)
trackEvent('app·_published_time', {
app_mode: appDetail?.mode,
app_id: appDetail?.id,
app_name: appDetail?.name,
time: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }),
})
}
catch {
setPublished(false)

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/amplitude'
const CreateCard = () => {
const { t } = useTranslation()
@ -22,6 +23,9 @@ const CreateCard = () => {
type: 'success',
message: t('datasetPipeline.creation.successTip'),
})
trackEvent('create_datasets_from_scratch', {
name: data.name,
})
invalidDatasetList()
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/amplitude'
type TemplateCardProps = {
pipeline: PipelineTemplate
@ -63,6 +64,13 @@ const TemplateCard = ({
type: 'success',
message: t('datasetPipeline.creation.successTip'),
})
trackEvent('create_datasets_with_pipeline', {
template_type: type,
template_name: pipeline.name,
chunk_structure: pipeline.chunk_structure,
})
invalidDatasetList()
if (newDataset.pipeline_id)
await handleCheckPluginDependencies(newDataset.pipeline_id, true)
@ -75,7 +83,7 @@ const TemplateCard = ({
})
},
})
}, [getPipelineTemplateInfo, createDataset, t, handleCheckPluginDependencies, push, invalidDatasetList])
}, [getPipelineTemplateInfo, createDataset, t, handleCheckPluginDependencies, push, invalidDatasetList, pipeline, 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/amplitude'
type IProps = {
show: boolean
@ -41,6 +42,9 @@ const EmptyDatasetCreationModal = ({
const dataset = await createEmptyDataset({ name: inputValue })
invalidDatasetList()
onHide()
trackEvent('create_empty_datasets', {
name: inputValue,
})
router.push(`/datasets/${dataset.id}/documents`)
}
catch {

View File

@ -63,6 +63,7 @@ import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/aler
import { noop } from 'lodash-es'
import { useDocLink } from '@/context/i18n'
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
import { trackEvent } from '@/app/components/amplitude'
const TextLabel: FC<PropsWithChildren> = (props) => {
return <label className='system-sm-semibold text-text-secondary'>{props.children}</label>
@ -571,6 +572,12 @@ const StepTwo = ({
updateIndexingTypeCache?.(indexType as string)
updateResultCache?.(data)
updateRetrievalMethodCache?.(retrievalConfig.search_method as string)
trackEvent('create_datasets', {
data_source_type: dataSourceType,
indexing_technique: indexType,
doc_form: currentDocForm,
})
},
},
)
@ -581,6 +588,13 @@ const StepTwo = ({
updateIndexingTypeCache?.(indexType as string)
updateResultCache?.(data)
updateRetrievalMethodCache?.(retrievalConfig.search_method as string)
// Track document addition to existing dataset
trackEvent('dataset_document_added', {
data_source_type: dataSourceType,
indexing_technique: indexType,
doc_form: currentDocForm,
})
},
})
}

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/amplitude'
const ExternalKnowledgeBaseConnector = () => {
const { notify } = useToastContext()
@ -17,6 +18,9 @@ const ExternalKnowledgeBaseConnector = () => {
setLoading(true)
const result = await createExternalKnowledgeBase({ body: formValue })
if (result && result.id) {
trackEvent('create_external_knowledge_base', {
value: formValue,
})
notify({ type: 'success', message: 'External Knowledge Base Connected Successfully' })
router.back()
}

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 '../../amplitude'
const i18nPrefix = 'plugin.action'
@ -199,6 +200,14 @@ const DetailHeader = ({
const handleDelete = useCallback(async () => {
showDeleting()
const res = await uninstallPlugin(id)
trackEvent('plugin_deleted', {
plugin_id: id,
plugin_name: label[locale],
plugin_category: category,
plugin_source: source,
plugin_version: version,
})
hideDeleting()
if (res.success) {
hideDeleteConfirm()

View File

@ -30,6 +30,7 @@ import { useCategories } from '../hooks'
import { usePluginPageContext } from '../plugin-page/context'
import { PluginCategoryEnum, type PluginDetail, PluginSource } from '../types'
import Action from './action'
import { trackEvent } from '../../amplitude'
type Props = {
className?: string
@ -77,6 +78,10 @@ const PluginItem: FC<Props> = ({
}, [status, deprecated_reason])
const handleDelete = useCallback(() => {
trackEvent('plugin_deleted', {
plugin_id,
plugin_name: name,
})
refreshPluginList({ category } as any)
}, [category, refreshPluginList])

View File

@ -9,6 +9,7 @@ import { getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
import cn from '@/utils/classnames'
import { RiCloseLine, RiDatabase2Line, RiLoader2Line, RiPlayLargeLine } from '@remixicon/react'
import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { trackEvent } from '@/app/components/amplitude'
type RunModeProps = {
text?: string
@ -53,6 +54,9 @@ const RunMode = ({
isDisabled ? 'rounded-l-md' : 'rounded-md',
)}
onClick={() => {
trackEvent('pipeline_start_action_time', {
time: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }),
})
handleWorkflowStartRunInWorkflow()
}}
disabled={isDisabled}

View File

@ -11,6 +11,7 @@ import BlockIcon from '../../block-icon'
import cn from '@/utils/classnames'
import { useTranslation } from 'react-i18next'
import { basePath } from '@/utils/var'
import { trackEvent } from '@/app/components/amplitude'
const normalizeProviderIcon = (icon: ToolWithProvider['icon']) => {
if (typeof icon === 'string' && basePath && icon.startsWith('/') && !icon.startsWith(`${basePath}/`))
@ -67,6 +68,13 @@ const ToolItem: FC<Props> = ({
params[item.name] = ''
})
}
trackEvent('tool_selected', {
tool_name: payload.name,
tool_parameters: payload.parameters,
tool_params: params,
plugin_id: provider.plugin_id,
plugin_unique_identifier: provider.plugin_unique_identifier,
})
onSelect(BlockEnum.Tool, {
provider_id: provider.id,
provider_type: provider.type,

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 '../../amplitude'
type RunModeProps = {
text?: string
@ -67,6 +68,11 @@ const RunMode = ({
return
}
trackEvent('app_start_action_time', {
time: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }),
action_type: option.type,
})
if (option.type === TriggerType.UserInput) {
handleWorkflowStartRunInWorkflow()
}

View File

@ -2,6 +2,7 @@ import { useCallback } from 'react'
import { produce } from 'immer'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { trackEvent } from '@/app/components/amplitude'
export const useWorkflowFailed = () => {
const workflowStore = useWorkflowStore()
@ -18,6 +19,12 @@ export const useWorkflowFailed = () => {
status: WorkflowRunningStatus.Failed,
}
}))
trackEvent('workflow_run_failed', {
workflow_id: workflowRunningData?.task_id,
error: workflowRunningData?.result.error,
data: workflowRunningData?.result,
})
}, [workflowStore])
return {

View File

@ -11,6 +11,7 @@ import { noop } from 'lodash-es'
import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils'
import { ZENDESK_FIELD_IDS } from '@/config'
import { useGlobalPublicStore } from './global-public-context'
import { setUserId, setUserProperties } from '@/app/components/amplitude'
export type AppContextValue = {
userProfile: UserProfileResponse
@ -159,6 +160,29 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
}, [currentWorkspace?.id])
// #endregion Zendesk conversation fields
useEffect(() => {
if (userProfile?.id) {
setUserId(userProfile.email)
setUserProperties({
email: userProfile.email,
name: userProfile.name,
has_password: userProfile.is_password_set,
})
}
}, [userProfile])
useEffect(() => {
if (currentWorkspace?.id && userProfile?.id) {
setUserProperties({
workspace_id: currentWorkspace.id,
workspace_name: currentWorkspace.name,
workspace_plan: currentWorkspace.plan,
workspace_status: currentWorkspace.status,
workspace_role: currentWorkspace.role,
})
}
}, [currentWorkspace])
return (
<AppContext.Provider value={{
userProfile,

File diff suppressed because it is too large Load Diff