mirror of
https://mirror.skon.top/github.com/langgenius/dify.git
synced 2026-04-20 15:20:15 +08:00
feat(web): add tracking for app preview events in AppCard component (#35347)
Co-authored-by: CodingOnStar <hanxujiang@dify.com>
This commit is contained in:
@@ -3,6 +3,8 @@ import type { App } from '@/models/explore'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
import { render, screen, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import AppListContext from '@/context/app-list-context'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import AppCard from '../index'
|
||||
|
||||
@@ -10,6 +12,10 @@ vi.mock('@heroicons/react/20/solid', () => ({
|
||||
PlusIcon: ({ className }: any) => <div data-testid="plus-icon" className={className} aria-label="Add icon">+</div>,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: vi.fn(),
|
||||
}))
|
||||
|
||||
const mockApp: App = {
|
||||
can_trial: true,
|
||||
app: {
|
||||
@@ -38,12 +44,30 @@ const mockApp: App = {
|
||||
}
|
||||
|
||||
describe('AppCard', () => {
|
||||
const mockSetShowTryAppPanel = vi.fn()
|
||||
const mockTrackEvent = vi.mocked(trackEvent)
|
||||
const defaultProps = {
|
||||
app: mockApp,
|
||||
canCreate: true,
|
||||
onCreate: vi.fn(),
|
||||
}
|
||||
|
||||
const renderWithProvider = (ui: React.ReactElement) => {
|
||||
return render(
|
||||
// eslint-disable-next-line react/no-context-provider
|
||||
<AppListContext.Provider
|
||||
value={{
|
||||
currentApp: undefined,
|
||||
isShowTryAppPanel: false,
|
||||
setShowTryAppPanel: mockSetShowTryAppPanel,
|
||||
controlHideCreateFromTemplatePanel: 0,
|
||||
}}
|
||||
>
|
||||
{ui}
|
||||
</AppListContext.Provider>,
|
||||
)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
@@ -217,6 +241,25 @@ describe('AppCard', () => {
|
||||
// Note: Card click doesn't trigger onCreate, only the button does
|
||||
expect(mockOnCreate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should track preview event and open try app panel when detail button is clicked', async () => {
|
||||
renderWithProvider(<AppCard {...defaultProps} />)
|
||||
|
||||
const button = screen.getByRole('button', { name: /explore\.appCard\.try/ })
|
||||
await userEvent.click(button)
|
||||
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith('preview_template', {
|
||||
template_id: mockApp.app_id,
|
||||
template_name: mockApp.app.name,
|
||||
template_mode: mockApp.app.mode,
|
||||
template_category: mockApp.category,
|
||||
page: 'studio',
|
||||
})
|
||||
expect(mockSetShowTryAppPanel).toHaveBeenCalledWith(true, {
|
||||
appId: mockApp.app_id,
|
||||
app: mockApp,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Keyboard Accessibility', () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { RiInformation2Line } from '@remixicon/react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContextSelector } from 'use-context-selector'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import AppListContext from '@/context/app-list-context'
|
||||
@@ -28,11 +29,16 @@ const AppCard = ({
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const isTrialApp = app.can_trial && systemFeatures.enable_trial_app
|
||||
const setShowTryAppPanel = useContextSelector(AppListContext, ctx => ctx.setShowTryAppPanel)
|
||||
const showTryAPPPanel = useCallback((appId: string) => {
|
||||
return () => {
|
||||
setShowTryAppPanel?.(true, { appId, app })
|
||||
}
|
||||
}, [setShowTryAppPanel, app.category])
|
||||
const handleShowTryAppPanel = useCallback(() => {
|
||||
trackEvent('preview_template', {
|
||||
template_id: app.app_id,
|
||||
template_name: appBasicInfo.name,
|
||||
template_mode: appBasicInfo.mode,
|
||||
template_category: app.category,
|
||||
page: 'studio',
|
||||
})
|
||||
setShowTryAppPanel?.(true, { appId: app.app_id, app })
|
||||
}, [setShowTryAppPanel, app, appBasicInfo])
|
||||
return (
|
||||
<div className={cn('group relative flex h-[132px] cursor-pointer flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 shadow-xs hover:shadow-lg')}>
|
||||
<div className="flex shrink-0 grow-0 items-center gap-3 pb-2">
|
||||
@@ -71,7 +77,7 @@ const AppCard = ({
|
||||
<span className="text-xs">{t('newApp.useTemplate', { ns: 'app' })}</span>
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={showTryAPPPanel(app.app_id)}>
|
||||
<Button onClick={handleShowTryAppPanel}>
|
||||
<RiInformation2Line className="mr-1 size-4" />
|
||||
<span>{t('appCard.try', { ns: 'explore' })}</span>
|
||||
</Button>
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { AppCardProps } from '../index'
|
||||
import type { App } from '@/models/explore'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import AppCard from '../index'
|
||||
|
||||
@@ -9,6 +10,10 @@ vi.mock('../../../app/type-selector', () => ({
|
||||
AppTypeIcon: ({ type }: { type: string }) => <div data-testid="app-type-icon">{type}</div>,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: vi.fn(),
|
||||
}))
|
||||
|
||||
const createApp = (overrides?: Partial<App>): App => ({
|
||||
can_trial: true,
|
||||
app_id: 'app-id',
|
||||
@@ -41,6 +46,7 @@ const createApp = (overrides?: Partial<App>): App => ({
|
||||
describe('AppCard', () => {
|
||||
const onCreate = vi.fn()
|
||||
const onTry = vi.fn()
|
||||
const mockTrackEvent = vi.mocked(trackEvent)
|
||||
|
||||
const renderComponent = (props?: Partial<AppCardProps>) => {
|
||||
const mergedProps: AppCardProps = {
|
||||
@@ -148,5 +154,21 @@ describe('AppCard', () => {
|
||||
|
||||
expect(onTry).toHaveBeenCalledWith({ appId: 'app-id', app })
|
||||
})
|
||||
|
||||
it('should track preview event when detail button is clicked', () => {
|
||||
const app = createApp()
|
||||
|
||||
renderComponent({ app, canCreate: true, isExplore: true })
|
||||
|
||||
fireEvent.click(screen.getByText('explore.appCard.try'))
|
||||
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith('preview_template', {
|
||||
template_id: app.app_id,
|
||||
template_name: app.app.name,
|
||||
template_mode: app.app.mode,
|
||||
template_category: app.category,
|
||||
page: 'explore',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PlusIcon } from '@heroicons/react/20/solid'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiInformation2Line } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
@@ -31,6 +32,13 @@ const AppCard = ({
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const isTrialApp = app.can_trial && systemFeatures.enable_trial_app
|
||||
const handleTryApp = () => {
|
||||
trackEvent('preview_template', {
|
||||
template_id: app.app_id,
|
||||
template_name: appBasicInfo.name,
|
||||
template_mode: appBasicInfo.mode,
|
||||
template_category: app.category,
|
||||
page: 'explore',
|
||||
})
|
||||
onTry({ appId: app.app_id, app })
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user