diff --git a/web/app/components/app/in-site-message/index.spec.tsx b/web/app/components/app/in-site-message/index.spec.tsx index 69f036da17..530084074d 100644 --- a/web/app/components/app/in-site-message/index.spec.tsx +++ b/web/app/components/app/in-site-message/index.spec.tsx @@ -1,7 +1,13 @@ +import type { ComponentProps } from 'react' import type { InSiteMessageActionItem } from './index' import { fireEvent, render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import InSiteMessage from './index' +vi.mock('@/app/components/base/amplitude', () => ({ + trackEvent: vi.fn(), +})) + describe('InSiteMessage', () => { const originalLocation = window.location @@ -18,9 +24,10 @@ describe('InSiteMessage', () => { vi.unstubAllGlobals() }) - const renderComponent = (actions: InSiteMessageActionItem[], props?: Partial>) => { + const renderComponent = (actions: InSiteMessageActionItem[], props?: Partial>) => { return render( { describe('Rendering', () => { it('should render title, subtitle, markdown content, and action buttons', () => { const actions: InSiteMessageActionItem[] = [ - { action: 'close', text: 'Close', type: 'default' }, - { action: 'link', text: 'Learn more', type: 'primary', data: 'https://example.com' }, + { action: 'close', action_name: 'dismiss', text: 'Close', type: 'default' }, + { action: 'link', action_name: 'learn_more', text: 'Learn more', type: 'primary', data: 'https://example.com' }, ] renderComponent(actions, { className: 'custom-message' }) @@ -56,7 +63,7 @@ describe('InSiteMessage', () => { }) it('should fallback to default header background when headerBgUrl is empty string', () => { - const actions: InSiteMessageActionItem[] = [{ action: 'close', text: 'Close', type: 'default' }] + const actions: InSiteMessageActionItem[] = [{ action: 'close', action_name: 'dismiss', text: 'Close', type: 'default' }] const { container } = renderComponent(actions, { headerBgUrl: '' }) const header = container.querySelector('div[style]') @@ -68,7 +75,7 @@ describe('InSiteMessage', () => { describe('Actions', () => { it('should call onAction and hide component when close action is clicked', () => { const onAction = vi.fn() - const closeAction: InSiteMessageActionItem = { action: 'close', text: 'Close', type: 'default' } + const closeAction: InSiteMessageActionItem = { action: 'close', action_name: 'dismiss', text: 'Close', type: 'default' } renderComponent([closeAction], { onAction }) fireEvent.click(screen.getByRole('button', { name: 'Close' })) @@ -80,6 +87,7 @@ describe('InSiteMessage', () => { it('should open a new tab when link action data is a string', () => { const linkAction: InSiteMessageActionItem = { action: 'link', + action_name: 'confirm', text: 'Open link', type: 'primary', data: 'https://example.com', @@ -103,6 +111,7 @@ describe('InSiteMessage', () => { const linkAction: InSiteMessageActionItem = { action: 'link', + action_name: 'confirm', text: 'Open self', type: 'primary', data: { href: 'https://example.com/self', target: '_self' }, @@ -118,6 +127,7 @@ describe('InSiteMessage', () => { it('should not trigger navigation when link data is invalid', () => { const linkAction: InSiteMessageActionItem = { action: 'link', + action_name: 'confirm', text: 'Broken link', type: 'primary', data: { rel: 'noopener' }, diff --git a/web/app/components/app/in-site-message/index.tsx b/web/app/components/app/in-site-message/index.tsx index 9225eb8a15..0276257860 100644 --- a/web/app/components/app/in-site-message/index.tsx +++ b/web/app/components/app/in-site-message/index.tsx @@ -1,6 +1,7 @@ 'use client' -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' +import { trackEvent } from '@/app/components/base/amplitude' import Button from '@/app/components/base/button' import { MarkdownWithDirective } from '@/app/components/base/markdown-with-directive' import { cn } from '@/utils/classnames' @@ -10,12 +11,14 @@ type InSiteMessageButtonType = 'primary' | 'default' export type InSiteMessageActionItem = { action: InSiteMessageAction + action_name: string // for tracing and analytics data?: unknown text: string type: InSiteMessageButtonType } type InSiteMessageProps = { + notificationId: string actions: InSiteMessageActionItem[] className?: string headerBgUrl?: string @@ -52,6 +55,7 @@ function normalizeLinkData(data: unknown): { href: string, rel?: string, target? const DEFAULT_HEADER_BG_URL = '/in-site-message/header-bg.svg' function InSiteMessage({ + notificationId, actions, className, headerBgUrl = DEFAULT_HEADER_BG_URL, @@ -70,7 +74,17 @@ function InSiteMessage({ } }, [headerBgUrl]) + useEffect(() => { + trackEvent('in_site_message_show', { + notification_id: notificationId, + }) + }, [notificationId]) + const handleAction = (item: InSiteMessageActionItem) => { + trackEvent('in_site_message_action', { + notification_id: notificationId, + action: item.action_name, + }) onAction?.(item) if (item.action === 'close') { diff --git a/web/app/components/app/in-site-message/notification.spec.tsx b/web/app/components/app/in-site-message/notification.spec.tsx index 84fe3aebc7..0d86d8a91c 100644 --- a/web/app/components/app/in-site-message/notification.spec.tsx +++ b/web/app/components/app/in-site-message/notification.spec.tsx @@ -15,11 +15,16 @@ const { mockNotificationDismiss: vi.fn(), })) -vi.mock('@/config', () => ({ - get IS_CLOUD_EDITION() { - return mockConfig.isCloudEdition - }, -})) +vi.mock(import('@/config'), async (importOriginal) => { + const actual = await importOriginal() + + return { + ...actual, + get IS_CLOUD_EDITION() { + return mockConfig.isCloudEdition + }, + } +}) vi.mock('@/service/client', () => ({ consoleQuery: { diff --git a/web/app/components/app/in-site-message/notification.tsx b/web/app/components/app/in-site-message/notification.tsx index de256a4663..cebf6ffd91 100644 --- a/web/app/components/app/in-site-message/notification.tsx +++ b/web/app/components/app/in-site-message/notification.tsx @@ -75,6 +75,7 @@ function InSiteMessageNotification() { const fallbackActions: InSiteMessageActionItem[] = [ { type: 'default', + action_name: 'dismiss', text: t('operation.close', { ns: 'common' }), action: 'close', }, @@ -96,6 +97,7 @@ function InSiteMessageNotification() { return (