mirror of
https://github.com/langgenius/dify.git
synced 2026-08-31 01:36:38 +08:00
feat: track inline agent and preview mode events (#41373)
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { act, waitFor } from '@testing-library/react'
|
||||
import { getDefaultStore } from 'jotai'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { defaultAgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import {
|
||||
agentComposerDraftAtom,
|
||||
agentComposerSavedDraftAtom,
|
||||
} from '@/features/agent-v2/agent-composer/store'
|
||||
import { AgentScope } from '@/features/agent-v2/analytics'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { FlowType } from '@/types/common'
|
||||
import { renderWorkflowHook } from '../../../__tests__/workflow-test-env'
|
||||
import { useWorkflowInlineAgentConfigureSync } from '../agent-soul-config'
|
||||
@@ -128,6 +131,7 @@ const mockSnippetComposerQueryOptions = vi.hoisted(() =>
|
||||
},
|
||||
),
|
||||
)
|
||||
const trackCreateAppMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: {
|
||||
@@ -141,6 +145,10 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', ()
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/create-app-tracking', () => ({
|
||||
trackCreateApp: trackCreateAppMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
agent: {
|
||||
@@ -318,6 +326,7 @@ describe('useWorkflowInlineAgentDetail', () => {
|
||||
describe('useCreateInlineAgentBinding', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
useAppStore.getState().setAppDetail({ mode: AppModeEnum.WORKFLOW } as never)
|
||||
mockDefaultModel.value = {
|
||||
model: 'gpt-4o-mini',
|
||||
model_type: 'llm',
|
||||
@@ -405,6 +414,44 @@ describe('useCreateInlineAgentBinding', () => {
|
||||
}),
|
||||
}),
|
||||
)
|
||||
expect(trackCreateAppMock).toHaveBeenCalledWith({
|
||||
source: 'studio_blank',
|
||||
appMode: 'agent-v2',
|
||||
agentScope: AgentScope.InWorkflow,
|
||||
})
|
||||
})
|
||||
|
||||
it('tracks inline agent creation with the chatflow scope', async () => {
|
||||
useAppStore.getState().setAppDetail({ mode: AppModeEnum.ADVANCED_CHAT } as never)
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
mutations: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
const { result } = renderWorkflowHook(() => useCreateInlineAgentBinding(), {
|
||||
queryClient,
|
||||
hooksStoreProps: {
|
||||
configsMap: {
|
||||
flowId: 'chatflow-1',
|
||||
flowType: FlowType.appFlow,
|
||||
fileSettings: {} as never,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
act(() => {
|
||||
void result.current.createInlineAgentBinding('node-1')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(trackCreateAppMock).toHaveBeenCalledWith({
|
||||
source: 'studio_blank',
|
||||
appMode: 'agent-v2',
|
||||
agentScope: AgentScope.InChatflow,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('creates inline agent through the snippet composer API', async () => {
|
||||
|
||||
+15
@@ -6,7 +6,10 @@ import type { ReactNode, Ref } from 'react'
|
||||
import type { AgentBuildDraftChangeSummary } from '@/features/agent-v2/agent-detail/configure/components/orchestrate/build-draft-changes-context'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { AgentScope } from '@/features/agent-v2/analytics'
|
||||
import { renderWithConsoleQuery as render } from '@/test/console/query-data'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { FlowType } from '@/types/common'
|
||||
import { WorkflowInlineAgentConfigureWorkspace } from '../agent-orchestrate-panel-content'
|
||||
|
||||
@@ -24,6 +27,7 @@ const mocks = vi.hoisted(() => ({
|
||||
saveAgentSoulConfig: vi.fn(),
|
||||
saveDraft: vi.fn(),
|
||||
stopBuildChat: vi.fn(),
|
||||
trackEvent: vi.fn(),
|
||||
uploadAgentSandboxFile: vi.fn(),
|
||||
uploadWorkflowSandboxFile: vi.fn(),
|
||||
}))
|
||||
@@ -34,6 +38,10 @@ vi.mock('@/features/agent-v2/permissions', () => ({
|
||||
useCanManageAgents: () => permission.canManageAgents,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: mocks.trackEvent,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useDefaultModel: () => ({
|
||||
data: undefined,
|
||||
@@ -486,6 +494,7 @@ function createDeferredPromise<T>() {
|
||||
describe('WorkflowInlineAgentConfigureWorkspace', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
useAppStore.getState().setAppDetail({ mode: AppModeEnum.WORKFLOW } as never)
|
||||
mocks.completeBuildConversation = undefined
|
||||
permission.canManageAgents = true
|
||||
mocks.loadBuildDraft.mockRejectedValue(new Response(null, { status: 404 }))
|
||||
@@ -595,6 +604,9 @@ describe('WorkflowInlineAgentConfigureWorkspace', () => {
|
||||
await user.click(screen.getByRole('button', { name: 'send preview message' }))
|
||||
|
||||
await waitFor(() => expect(mocks.saveDraft).toHaveBeenCalled())
|
||||
expect(mocks.trackEvent).toHaveBeenCalledWith('agent_preview_mode_run', {
|
||||
agent_scope: AgentScope.InWorkflow,
|
||||
})
|
||||
expect(mocks.saveBuildDraft).not.toHaveBeenCalled()
|
||||
|
||||
await user.click(
|
||||
@@ -967,6 +979,9 @@ describe('WorkflowInlineAgentConfigureWorkspace', () => {
|
||||
|
||||
expect(saveDraftCallOrder).toBeLessThan(saveBuildDraftCallOrder)
|
||||
expect(mocks.checkoutBuildDraft).not.toHaveBeenCalled()
|
||||
expect(mocks.trackEvent).toHaveBeenCalledWith('agent_build_mode_run', {
|
||||
agent_scope: AgentScope.InWorkflow,
|
||||
})
|
||||
})
|
||||
|
||||
it('should use the saved build draft response as the build chat source', async () => {
|
||||
|
||||
+11
-2
@@ -65,6 +65,11 @@ import {
|
||||
useAgentConfigureBuildDraftData,
|
||||
} from '@/features/agent-v2/agent-detail/configure/use-agent-configure-build-draft'
|
||||
import { useAgentConfigureSessionController } from '@/features/agent-v2/agent-detail/configure/use-agent-configure-session-controller'
|
||||
import {
|
||||
trackAgentBuildModeRun,
|
||||
trackAgentPreviewModeRun,
|
||||
useInlineAgentScope,
|
||||
} from '@/features/agent-v2/analytics'
|
||||
import { useCanManageAgents } from '@/features/agent-v2/permissions'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
@@ -294,6 +299,7 @@ function WorkflowInlineAgentConfigureWorkspaceContent({
|
||||
}) {
|
||||
const { t } = useTranslation('common')
|
||||
const { t: tAgent } = useTranslation('agentV2')
|
||||
const agentScope = useInlineAgentScope()
|
||||
const queryClient = useQueryClient()
|
||||
const jotaiStore = useJotaiStore()
|
||||
const setBuildDraftSoulSourceOverride = buildDraft.setSoulSourceOverride
|
||||
@@ -779,15 +785,18 @@ function WorkflowInlineAgentConfigureWorkspaceContent({
|
||||
}}
|
||||
onSaveDraftBeforeRun={
|
||||
rightPanelMode === 'build'
|
||||
? () => {
|
||||
return runBuildPreparation({
|
||||
? async () => {
|
||||
const preparedBuildDraft = await runBuildPreparation({
|
||||
generation: buildCallbackGeneration,
|
||||
markBuildChatStarted: true,
|
||||
prepare: prepareInlineBuildDraftBeforeRun,
|
||||
})
|
||||
trackAgentBuildModeRun(agentScope)
|
||||
return preparedBuildDraft
|
||||
}
|
||||
: async () => {
|
||||
await saveDraft()
|
||||
trackAgentPreviewModeRun(agentScope)
|
||||
}
|
||||
}
|
||||
onSendInterrupted={() => {
|
||||
|
||||
@@ -6,8 +6,10 @@ import { useTranslation } from 'react-i18next'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { useHooksStore } from '@/app/components/workflow/hooks-store'
|
||||
import { useInlineAgentScope } from '@/features/agent-v2/analytics'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { FlowType } from '@/types/common'
|
||||
import { trackCreateApp } from '@/utils/create-app-tracking'
|
||||
import { getDefaultAgentSoul } from './agent-soul-config'
|
||||
|
||||
type CreatedInlineAgentBinding = AgentInlineBinding & {
|
||||
@@ -99,6 +101,7 @@ export function useWorkflowInlineAgentDetail(
|
||||
export function useCreateInlineAgentBinding() {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const configsMap = useHooksStore((state) => state.configsMap)
|
||||
const agentScope = useInlineAgentScope()
|
||||
const { data: defaultModel } = useDefaultModel(ModelTypeEnum.textGeneration)
|
||||
const queryClient = useQueryClient()
|
||||
const { isPending: isAppComposerPending, mutateAsync: mutateAppComposerAsync } = useMutation(
|
||||
@@ -187,6 +190,11 @@ export function useCreateInlineAgentBinding() {
|
||||
composerState,
|
||||
)
|
||||
}
|
||||
trackCreateApp({
|
||||
source: 'studio_blank',
|
||||
appMode: 'agent-v2',
|
||||
agentScope,
|
||||
})
|
||||
options?.onSuccess?.({
|
||||
binding_type: 'inline_agent',
|
||||
agent_id: binding.agent_id,
|
||||
@@ -197,6 +205,7 @@ export function useCreateInlineAgentBinding() {
|
||||
}
|
||||
},
|
||||
[
|
||||
agentScope,
|
||||
configsMap?.flowId,
|
||||
configsMap?.flowType,
|
||||
defaultModel,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { ScopeProvider } from 'jotai-scope'
|
||||
import { useState } from 'react'
|
||||
import { AgentScope } from '@/features/agent-v2/analytics'
|
||||
import { renderWithNuqs as render } from '@/test/nuqs-testing'
|
||||
import { AgentConfigureComposerScope } from '../components/composer-session'
|
||||
import { useAgentConfigureData } from '../hooks'
|
||||
@@ -1429,7 +1430,9 @@ describe('AgentConfigurePage', () => {
|
||||
expect(screen.getByRole('region', { name: 'preview-chat' })).toHaveTextContent(
|
||||
'draftType:draft',
|
||||
)
|
||||
expect(trackEventMock).not.toHaveBeenCalled()
|
||||
expect(trackEventMock).toHaveBeenCalledWith('agent_preview_mode_run', {
|
||||
agent_scope: AgentScope.Global,
|
||||
})
|
||||
expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toHaveTextContent(
|
||||
'readonly:no',
|
||||
)
|
||||
@@ -2011,7 +2014,9 @@ describe('AgentConfigurePage', () => {
|
||||
expect(screen.getByRole('region', { name: 'build-chat' })).toHaveTextContent('sent:yes')
|
||||
})
|
||||
expect(mocks.checkoutBuildDraft).not.toHaveBeenCalled()
|
||||
expect(trackEventMock).toHaveBeenCalledWith('agent_build_mode_run')
|
||||
expect(trackEventMock).toHaveBeenCalledWith('agent_build_mode_run', {
|
||||
agent_scope: AgentScope.Global,
|
||||
})
|
||||
})
|
||||
|
||||
it('should show the working directory action after the first build reply completes', async () => {
|
||||
|
||||
@@ -14,11 +14,15 @@ import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { ScopeProvider } from 'jotai-scope'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { agentSoulConfigToFormState } from '@/features/agent-v2/agent-composer/conversions'
|
||||
import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provider'
|
||||
import { rebaseAgentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store'
|
||||
import {
|
||||
AgentScope,
|
||||
trackAgentBuildModeRun,
|
||||
trackAgentPreviewModeRun,
|
||||
} from '@/features/agent-v2/analytics'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { useAgentConfigureModelOptions } from '../hooks'
|
||||
import {
|
||||
@@ -588,10 +592,13 @@ function AgentConfigurePageComposerContent({
|
||||
markBuildChatStarted: true,
|
||||
prepare: buildDraftActions.prepareBuildDraftBeforeRun,
|
||||
})
|
||||
trackEvent('agent_build_mode_run')
|
||||
trackAgentBuildModeRun(AgentScope.Global)
|
||||
return preparedBuildDraft
|
||||
}
|
||||
: saveDraft
|
||||
: async () => {
|
||||
await saveDraft()
|
||||
trackAgentPreviewModeRun(AgentScope.Global)
|
||||
}
|
||||
}
|
||||
onSendInterrupted={() => {
|
||||
if (rightPanelChatMode === 'build') finishBuildAction(buildCallbackGeneration)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
export type AgentScope = (typeof AgentScope)[keyof typeof AgentScope]
|
||||
export const AgentScope = {
|
||||
InWorkflow: 'in_workflow',
|
||||
InChatflow: 'in_chatflow',
|
||||
Global: 'global',
|
||||
} as const
|
||||
|
||||
export const useInlineAgentScope = () => {
|
||||
const appMode = useAppStore((state) => state.appDetail?.mode)
|
||||
|
||||
return appMode === AppModeEnum.ADVANCED_CHAT ? AgentScope.InChatflow : AgentScope.InWorkflow
|
||||
}
|
||||
|
||||
export const trackAgentBuildModeRun = (agentScope: AgentScope) => {
|
||||
return trackEvent('agent_build_mode_run', {
|
||||
agent_scope: agentScope,
|
||||
})
|
||||
}
|
||||
|
||||
export const trackAgentPreviewModeRun = (agentScope: AgentScope) => {
|
||||
return trackEvent('agent_preview_mode_run', {
|
||||
agent_scope: agentScope,
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { act, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { AgentScope } from '@/features/agent-v2/analytics'
|
||||
import { CreateAgentDialog } from '../create-agent-dialog'
|
||||
|
||||
const mutationMock = vi.hoisted(() => ({
|
||||
@@ -115,6 +116,7 @@ describe('CreateAgentDialog', () => {
|
||||
expect(trackCreateAppMock).toHaveBeenCalledWith({
|
||||
source: 'studio_blank',
|
||||
appMode: 'agent-v2',
|
||||
agentScope: AgentScope.Global,
|
||||
})
|
||||
expect(routerPushMock).toHaveBeenCalledWith('/agents/agent-1/configure')
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useMutation } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
||||
import { AgentScope } from '@/features/agent-v2/analytics'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { trackCreateApp } from '@/utils/create-app-tracking'
|
||||
@@ -80,6 +81,7 @@ export function CreateAgentDialog({ open, onOpenChange }: CreateAgentDialogProps
|
||||
trackCreateApp({
|
||||
source: 'studio_blank',
|
||||
appMode: 'agent-v2',
|
||||
agentScope: AgentScope.Global,
|
||||
})
|
||||
toast.success(t(($) => $['roster.createSuccess']))
|
||||
handleOpenChange(false)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import * as amplitude from '@/app/components/base/amplitude/utils'
|
||||
import { AgentScope } from '@/features/agent-v2/analytics'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import {
|
||||
buildCreateAppEventPayload,
|
||||
@@ -147,6 +148,7 @@ describe('create-app-tracking', () => {
|
||||
{
|
||||
source: 'studio_blank',
|
||||
appMode: 'agent-v2',
|
||||
agentScope: AgentScope.Global,
|
||||
},
|
||||
null,
|
||||
new Date(2026, 3, 13, 9, 8, 9),
|
||||
@@ -154,6 +156,7 @@ describe('create-app-tracking', () => {
|
||||
).toEqual({
|
||||
source: 'studio_blank',
|
||||
app_mode: 'agent-v2',
|
||||
agent_scope: 'global',
|
||||
time: '04-13-09:08:09',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AgentScope } from '@/features/agent-v2/analytics'
|
||||
import Cookies from 'js-cookie'
|
||||
import { flushEvents, trackEvent } from '@/app/components/base/amplitude/utils'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
@@ -32,6 +33,7 @@ type CreateAppSource =
|
||||
export type TrackCreateAppParams = {
|
||||
source: CreateAppSource
|
||||
appMode: string
|
||||
agentScope?: AgentScope
|
||||
templateId?: string
|
||||
}
|
||||
|
||||
@@ -177,6 +179,7 @@ export const buildCreateAppEventPayload = (
|
||||
source,
|
||||
app_mode: mapOriginalCreateAppMode(params.appMode),
|
||||
time: formatCreateAppTime(currentTime),
|
||||
...(params.agentScope ? { agent_scope: params.agentScope } : {}),
|
||||
...(params.templateId ? { template_id: params.templateId } : {}),
|
||||
...(externalAttribution
|
||||
? {
|
||||
|
||||
Reference in New Issue
Block a user