mirror of
https://github.com/langgenius/dify.git
synced 2025-12-19 17:27:16 -05:00
feat: Enhance Amplitude tracking across various components (#29662)
Co-authored-by: CodingOnStar <hanxujiang@dify.ai>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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`)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`)
|
||||
}
|
||||
|
||||
@@ -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?.()
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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') }
|
||||
|
||||
@@ -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')}>
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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')}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 })
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user