refactor(web): sync amplitude from profile query (#40472)

This commit is contained in:
yyh
2026-08-12 02:15:24 +00:00
committed by GitHub
parent 7aba539e82
commit 4c06bcf000
3 changed files with 96 additions and 62 deletions
@@ -1,13 +1,70 @@
'use client'
import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen'
import type { GetWorkspacesCurrentSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import { useEffect, useRef } from 'react'
import { setUserId, setUserProperties } from '@/app/components/base/amplitude'
import { flushRegistrationSuccess } from '@/app/components/base/amplitude/registration-tracking'
import { useAmplitudeInitialized } from '@/app/components/base/amplitude/use-amplitude-initialized'
import { useAnalyticsConsent } from '@/app/components/base/analytics-consent/consent-store'
import { amplitudeIdentitySyncAtom } from '@/context/amplitude-identity-sync'
import { currentWorkspaceAtom } from '@/context/workspace-state'
import { zendeskConversationSyncAtom } from '@/context/zendesk-conversation-sync'
import { userProfileQueryOptions } from '@/features/account-profile/client'
type AmplitudeProperties = Record<string, string | number | boolean>
function buildAmplitudeProperties({
currentWorkspace,
userProfile,
}: {
currentWorkspace: GetWorkspacesCurrentSummaryResponse
userProfile: GetAccountProfileResponse
}) {
const properties: AmplitudeProperties = {
email: userProfile.email,
name: userProfile.name,
has_password: userProfile.is_password_set,
}
if (currentWorkspace.id) {
properties.workspace_id = currentWorkspace.id
properties.workspace_name = currentWorkspace.name
if (currentWorkspace.plan) properties.workspace_plan = currentWorkspace.plan
properties.workspace_role = currentWorkspace.role
}
return properties
}
function AmplitudeIdentitySync() {
useAtomValue(amplitudeIdentitySyncAtom)
const { data: userProfile } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile,
})
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
const lastIdentityRef = useRef<string | undefined>(undefined)
useEffect(() => {
if (!userProfile.id) return
const properties = buildAmplitudeProperties({
currentWorkspace,
userProfile,
})
const identity = JSON.stringify({
userId: userProfile.email,
properties,
})
if (identity === lastIdentityRef.current) return
setUserId(userProfile.email)
setUserProperties(properties)
flushRegistrationSuccess()
lastIdentityRef.current = identity
}, [currentWorkspace, userProfile])
return null
}
@@ -565,5 +565,42 @@ describe('Console bootstrap', () => {
expect(flushRegistrationSuccess).toHaveBeenCalled()
})
})
it('should resync Amplitude only when identity properties change', async () => {
const { queryClient, rerender } = renderConsoleBootstrap()
await waitFor(() => expect(setUserProperties).toHaveBeenCalledTimes(1))
rerender(
<JotaiProvider>
<QueryClientProvider client={queryClient}>
<TestQueryClientHydrator queryClient={queryClient}>
<Suspense fallback={<span>loading</span>}>
<ExternalServiceSync />
<ConsoleBootstrapProbe />
</Suspense>
</TestQueryClientHydrator>
</QueryClientProvider>
</JotaiProvider>,
)
expect(setUserProperties).toHaveBeenCalledTimes(1)
act(() => {
queryClient.setQueryData(['user-profile'], {
...mockUserProfileResponseState.data,
profile: {
...mockUserProfileResponseState.data.profile,
name: 'Updated User',
},
})
})
await waitFor(() => {
expect(setUserProperties).toHaveBeenCalledTimes(2)
expect(setUserProperties).toHaveBeenLastCalledWith(
expect.objectContaining({ name: 'Updated User' }),
)
})
})
})
})
-60
View File
@@ -1,60 +0,0 @@
'use client'
import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen'
import type { GetWorkspacesCurrentSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
import { atom } from 'jotai'
import { atomEffect } from 'jotai-effect'
import { setUserId, setUserProperties } from '@/app/components/base/amplitude'
import { flushRegistrationSuccess } from '@/app/components/base/amplitude/registration-tracking'
import { userProfileAtom } from './account-state'
import { currentWorkspaceAtom } from './workspace-state'
type AmplitudeProperties = Record<string, string | number | boolean>
const amplitudeIdentityAtom = atom<string | undefined>(undefined)
function buildAmplitudeProperties({
currentWorkspace,
userProfile,
}: {
currentWorkspace: GetWorkspacesCurrentSummaryResponse
userProfile: GetAccountProfileResponse
}) {
const properties: AmplitudeProperties = {
email: userProfile.email,
name: userProfile.name,
has_password: userProfile.is_password_set,
}
if (currentWorkspace.id) {
properties.workspace_id = currentWorkspace.id
properties.workspace_name = currentWorkspace.name
if (currentWorkspace.plan) properties.workspace_plan = currentWorkspace.plan
properties.workspace_role = currentWorkspace.role
}
return properties
}
export const amplitudeIdentitySyncAtom = atomEffect((get, set) => {
const userProfile = get(userProfileAtom)
const currentWorkspace = get(currentWorkspaceAtom)
if (!userProfile.id) return
const properties = buildAmplitudeProperties({
currentWorkspace,
userProfile,
})
const identity = JSON.stringify({
userId: userProfile.email,
properties,
})
if (identity === get.peek(amplitudeIdentityAtom)) return
setUserId(userProfile.email)
setUserProperties(properties)
flushRegistrationSuccess()
set(amplitudeIdentityAtom, identity)
})