chore(core): replace with new track impl (#7735)

This commit is contained in:
liuyi
2024-08-06 17:15:15 +08:00
committed by GitHub
parent 7373e174db
commit d0f1bb24fd
59 changed files with 647 additions and 821 deletions

View File

@@ -6,7 +6,7 @@ import type { createStore } from 'jotai';
import { openSettingModalAtom, openWorkspaceListModalAtom } from '../atoms'; import { openSettingModalAtom, openWorkspaceListModalAtom } from '../atoms';
import type { useNavigateHelper } from '../hooks/use-navigate-helper'; import type { useNavigateHelper } from '../hooks/use-navigate-helper';
import { mixpanel, track } from '../mixpanel'; import { track } from '../mixpanel';
import { registerAffineCommand } from './registry'; import { registerAffineCommand } from './registry';
export function registerAffineNavigationCommands({ export function registerAffineNavigationCommands({
@@ -93,10 +93,7 @@ export function registerAffineNavigationCommands({
icon: <ArrowRightBigIcon />, icon: <ArrowRightBigIcon />,
label: t['com.affine.cmdk.affine.navigation.open-account-settings'](), label: t['com.affine.cmdk.affine.navigation.open-account-settings'](),
run() { run() {
mixpanel.track('AccountSettingsViewed', { track.$.cmdk.settings.openSettings({ to: 'account' });
// page:
segment: 'cmdk',
});
store.set(openSettingModalAtom, s => ({ store.set(openSettingModalAtom, s => ({
activeTab: 'account', activeTab: 'account',
open: !s.open, open: !s.open,

View File

@@ -1,6 +1,6 @@
import { Button, FlexWrapper, notify } from '@affine/component'; import { Button, FlexWrapper, notify } from '@affine/component';
import { openSettingModalAtom } from '@affine/core/atoms'; import { openSettingModalAtom } from '@affine/core/atoms';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { SubscriptionService } from '@affine/core/modules/cloud'; import { SubscriptionService } from '@affine/core/modules/cloud';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { AiIcon } from '@blocksuite/icons/rc'; import { AiIcon } from '@blocksuite/icons/rc';
@@ -65,11 +65,7 @@ export const AIOnboardingEdgeless = () => {
const mode = useLiveData(doc.mode$); const mode = useLiveData(doc.mode$);
const goToPricingPlans = useCallback(() => { const goToPricingPlans = useCallback(() => {
mixpanel.track('PlansViewed', { track.$.aiOnboarding.dialog.viewPlans();
page: 'whiteboard editor',
segment: 'ai onboarding',
module: 'whiteboard dialog',
});
setSettingModal({ setSettingModal({
open: true, open: true,
activeTab: 'plans', activeTab: 'plans',

View File

@@ -1,7 +1,7 @@
import { Button, IconButton, Modal } from '@affine/component'; import { Button, IconButton, Modal } from '@affine/component';
import { openSettingModalAtom } from '@affine/core/atoms'; import { openSettingModalAtom } from '@affine/core/atoms';
import { useBlurRoot } from '@affine/core/hooks/use-blur-root'; import { useBlurRoot } from '@affine/core/hooks/use-blur-root';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud'; import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
import { Trans, useI18n } from '@affine/i18n'; import { Trans, useI18n } from '@affine/i18n';
import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc'; import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc';
@@ -116,11 +116,7 @@ export const AIOnboardingGeneral = () => {
activeTab: 'plans', activeTab: 'plans',
scrollAnchor: 'aiPricingPlan', scrollAnchor: 'aiPricingPlan',
}); });
mixpanel.track('PlansViewed', { track.$.aiOnboarding.dialog.viewPlans();
page: 'whiteboard editor',
segment: 'ai onboarding',
module: 'general',
});
closeAndDismiss(); closeAndDismiss();
}, [closeAndDismiss, setSettingModal]); }, [closeAndDismiss, setSettingModal]);
const onPrev = useCallback(() => { const onPrev = useCallback(() => {

View File

@@ -1,7 +1,7 @@
import { notify, Skeleton } from '@affine/component'; import { notify, Skeleton } from '@affine/component';
import { Button } from '@affine/component/ui/button'; import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { OAuthProviderType } from '@affine/graphql'; import { OAuthProviderType } from '@affine/graphql';
import { GithubIcon, GoogleDuotoneIcon } from '@blocksuite/icons/rc'; import { GithubIcon, GoogleDuotoneIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
@@ -75,7 +75,7 @@ function OAuthProvider({
notify.error({ title: 'Failed to sign in, please try again.' }); notify.error({ title: 'Failed to sign in, please try again.' });
} finally { } finally {
setIsConnecting(false); setIsConnecting(false);
mixpanel.track('OAuth', { provider }); track.$.$.auth.oauth({ provider });
} }
}, [authService, provider, redirectUri]); }, [authService, provider, redirectUri]);

View File

@@ -3,7 +3,7 @@ import { AuthInput, ModalHeader } from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button'; import { Button } from '@affine/component/ui/button';
import { authAtom } from '@affine/core/atoms'; import { authAtom } from '@affine/core/atoms';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { Trans, useI18n } from '@affine/i18n'; import { Trans, useI18n } from '@affine/i18n';
import { ArrowRightBigIcon } from '@blocksuite/icons/rc'; import { ArrowRightBigIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
@@ -75,9 +75,7 @@ export const SignIn: FC<AuthPanelProps> = ({
if (hasPassword) { if (hasPassword) {
setAuthState('signInWithPassword'); setAuthState('signInWithPassword');
} else { } else {
mixpanel.track('SignIn', { track.$.$.auth.signIn();
email,
});
await authService.sendEmailMagicLink( await authService.sendEmailMagicLink(
email, email,
verifyToken, verifyToken,
@@ -93,9 +91,7 @@ export const SignIn: FC<AuthPanelProps> = ({
challenge, challenge,
searchParams.get('redirect_uri') searchParams.get('redirect_uri')
); );
mixpanel.track('SignUp', { track.$.$.auth.signUp();
email,
});
setAuthState('afterSignUpSendEmail'); setAuthState('afterSignUpSendEmail');
} }
} }

View File

@@ -1,5 +1,4 @@
import { Tooltip } from '@affine/component/ui/tooltip'; import { Tooltip } from '@affine/component/ui/tooltip';
import { mixpanel } from '@affine/core/mixpanel';
import { SubscriptionPlan } from '@affine/graphql'; import { SubscriptionPlan } from '@affine/graphql';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { useLiveData, useServices } from '@toeverything/infra'; import { useLiveData, useServices } from '@toeverything/infra';
@@ -44,10 +43,6 @@ export const UserPlanButton = () => {
activeTab: 'plans', activeTab: 'plans',
scrollAnchor: 'cloudPricingPlan', scrollAnchor: 'cloudPricingPlan',
}); });
mixpanel.track('PlansViewed', {
segment: 'settings panel',
module: 'profile and badge',
});
}, },
[setSettingModalAtom] [setSettingModalAtom]
); );
@@ -72,6 +67,7 @@ export const UserPlanButton = () => {
data-is-believer={isBeliever ? 'true' : undefined} data-is-believer={isBeliever ? 'true' : undefined}
className={styles.userPlanButton} className={styles.userPlanButton}
onClick={handleClick} onClick={handleClick}
data-event-props="$.settingsPanel.profileAndBadge.viewPlans"
> >
{planLabel} {planLabel}
</div> </div>

View File

@@ -3,7 +3,7 @@ import type { ConfirmModalProps } from '@affine/component/ui/modal';
import { ConfirmModal, Modal } from '@affine/component/ui/modal'; import { ConfirmModal, Modal } from '@affine/component/ui/modal';
import { authAtom } from '@affine/core/atoms'; import { authAtom } from '@affine/core/atoms';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
import { apis } from '@affine/electron-api'; import { apis } from '@affine/electron-api';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
@@ -222,9 +222,7 @@ export const CreateWorkspaceModal = ({
const onConfirmName = useAsyncCallback( const onConfirmName = useAsyncCallback(
async (name: string, workspaceFlavour: WorkspaceFlavour) => { async (name: string, workspaceFlavour: WorkspaceFlavour) => {
mixpanel.track('CreateWorkspace', { track.$.$.$.createWorkspace({ flavour: workspaceFlavour });
workspaceFlavour,
});
if (loading) return; if (loading) return;
setLoading(true); setLoading(true);

View File

@@ -4,7 +4,7 @@ import { Button, IconButton } from '@affine/component/ui/button';
import { Modal, useConfirmModal } from '@affine/component/ui/modal'; import { Modal, useConfirmModal } from '@affine/component/ui/modal';
import { openSettingModalAtom } from '@affine/core/atoms'; import { openSettingModalAtom } from '@affine/core/atoms';
import { useDocCollectionPageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title'; import { useDocCollectionPageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { i18nTime, Trans, useI18n } from '@affine/i18n'; import { i18nTime, Trans, useI18n } from '@affine/i18n';
@@ -108,18 +108,17 @@ const HistoryEditorPreview = ({
mode, mode,
title, title,
}: HistoryEditorPreviewProps) => { }: HistoryEditorPreviewProps) => {
const onSwitchToPageMode = useCallback(() => { const { onSwitchToPageMode, onSwitchToEdgelessMode } = useMemo(
mixpanel.track('Button', { () => ({
resolve: 'HistorySwitchToPageMode', onSwitchToPageMode: () => {
}); onModeChange('page');
onModeChange('page'); },
}, [onModeChange]); onSwitchToEdgelessMode: () => {
const onSwitchToEdgelessMode = useCallback(() => { onModeChange('edgeless');
mixpanel.track('Button', { },
resolve: 'HistorySwitchToEdgelessMode', }),
}); [onModeChange]
onModeChange('edgeless'); );
}, [onModeChange]);
const content = useMemo(() => { const content = useMemo(() => {
return ( return (
@@ -129,11 +128,15 @@ const HistoryEditorPreview = ({
<PageSwitchItem <PageSwitchItem
data-testid="switch-page-mode-button" data-testid="switch-page-mode-button"
active={mode === 'page'} active={mode === 'page'}
data-event-props="$.docHistory.$.switchPageMode"
data-event-args-type="page"
onClick={onSwitchToPageMode} onClick={onSwitchToPageMode}
/> />
<EdgelessSwitchItem <EdgelessSwitchItem
data-testid="switch-edgeless-mode-button" data-testid="switch-edgeless-mode-button"
active={mode === 'edgeless'} active={mode === 'edgeless'}
data-event-props="$.docHistory.$.switchPageMode"
data-event-args-type="edgeless"
onClick={onSwitchToEdgelessMode} onClick={onSwitchToEdgelessMode}
/> />
</StyledEditorModeSwitch> </StyledEditorModeSwitch>
@@ -229,9 +232,7 @@ const PlanPrompt = () => {
activeTab: 'plans', activeTab: 'plans',
scrollAnchor: 'cloudPricingPlan', scrollAnchor: 'cloudPricingPlan',
}); });
mixpanel.track('PlansViewed', { track.$.docHistory.$.viewPlans();
segment: 'doc history',
});
}, [setSettingModalAtom]); }, [setSettingModalAtom]);
const t = useI18n(); const t = useI18n();
@@ -562,9 +563,7 @@ export const GlobalPageHistoryModal = () => {
const workspace = useService(WorkspaceService).workspace; const workspace = useService(WorkspaceService).workspace;
const handleOpenChange = useCallback( const handleOpenChange = useCallback(
(open: boolean) => { (open: boolean) => {
mixpanel.track('Button', { track.$.docHistory.$[open ? 'open' : 'close']();
resolve: open ? 'OpenPageHistoryModal' : 'ClosePageHistoryModal',
});
setState(prev => ({ setState(prev => ({
...prev, ...prev,
open, open,

View File

@@ -1,6 +1,6 @@
import { ConfirmModal } from '@affine/component/ui/modal'; import { ConfirmModal } from '@affine/component/ui/modal';
import { openQuotaModalAtom, openSettingModalAtom } from '@affine/core/atoms'; import { openQuotaModalAtom, openSettingModalAtom } from '@affine/core/atoms';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { UserQuotaService } from '@affine/core/modules/cloud'; import { UserQuotaService } from '@affine/core/modules/cloud';
import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { WorkspaceQuotaService } from '@affine/core/modules/quota';
@@ -50,11 +50,7 @@ export const CloudQuotaModal = () => {
scrollAnchor: 'cloudPricingPlan', scrollAnchor: 'cloudPricingPlan',
}); });
mixpanel.track('PlansViewed', { track.$.paywall.storage.viewPlans();
segment: 'payment wall',
category: 'payment wall storage',
});
setOpen(false); setOpen(false);
}, [setOpen, setSettingModalAtom]); }, [setOpen, setSettingModalAtom]);

View File

@@ -1,12 +1,13 @@
import { Button, ErrorMessage, Skeleton } from '@affine/component'; import { Button, ErrorMessage, Skeleton } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components'; import { SettingRow } from '@affine/component/setting-components';
import { openSettingModalAtom } from '@affine/core/atoms'; import { openSettingModalAtom } from '@affine/core/atoms';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { import {
ServerConfigService, ServerConfigService,
SubscriptionService, SubscriptionService,
UserCopilotQuotaService, UserCopilotQuotaService,
} from '@affine/core/modules/cloud'; } from '@affine/core/modules/cloud';
import { SubscriptionPlan } from '@affine/graphql';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme'; import { cssVar } from '@toeverything/theme';
@@ -47,12 +48,7 @@ export const AIUsagePanel = () => {
open: true, open: true,
activeTab: 'billing', activeTab: 'billing',
}); });
mixpanel.track('BillingViewed', { track.$.settingsPanel.accountUsage.viewPlans({ plan: SubscriptionPlan.AI });
segment: 'settings panel',
module: 'account usage list',
control: 'change plan button',
type: 'ai subscription',
});
}, [setOpenSettingModal]); }, [setOpenSettingModal]);
if (loading) { if (loading) {
@@ -104,7 +100,7 @@ export const AIUsagePanel = () => {
> >
{copilotActionLimit === 'unlimited' ? ( {copilotActionLimit === 'unlimited' ? (
hasPaymentFeature && aiSubscription?.canceledAt ? ( hasPaymentFeature && aiSubscription?.canceledAt ? (
<AIResume module="billing subscription list" /> <AIResume />
) : ( ) : (
<Button onClick={openBilling}> <Button onClick={openBilling}>
{t['com.affine.payment.ai.usage.change-button-label']()} {t['com.affine.payment.ai.usage.change-button-label']()}

View File

@@ -6,7 +6,8 @@ import {
import { Avatar } from '@affine/component/ui/avatar'; import { Avatar } from '@affine/component/ui/avatar';
import { Button } from '@affine/component/ui/button'; import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { SubscriptionPlan } from '@affine/graphql';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { ArrowRightSmallIcon, CameraIcon } from '@blocksuite/icons/rc'; import { ArrowRightSmallIcon, CameraIcon } from '@blocksuite/icons/rc';
import { import {
@@ -38,9 +39,7 @@ export const UserAvatar = () => {
const handleUpdateUserAvatar = useAsyncCallback( const handleUpdateUserAvatar = useAsyncCallback(
async (file: File) => { async (file: File) => {
try { try {
mixpanel.track('UploadAvatar', { track.$.settingsPanel.accountSettings.uploadAvatar();
userId: account.id,
});
await session.uploadAvatar(file); await session.uploadAvatar(file);
notify.success({ title: 'Update user avatar success' }); notify.success({ title: 'Update user avatar success' });
} catch (e) { } catch (e) {
@@ -51,18 +50,16 @@ export const UserAvatar = () => {
}); });
} }
}, },
[account, session] [session]
); );
const handleRemoveUserAvatar = useAsyncCallback( const handleRemoveUserAvatar = useAsyncCallback(
async (e: MouseEvent<HTMLButtonElement>) => { async (e: MouseEvent<HTMLButtonElement>) => {
mixpanel.track('RemoveAvatar', { track.$.settingsPanel.accountSettings.removeAvatar();
userId: account.id,
});
e.stopPropagation(); e.stopPropagation();
await session.removeAvatar(); await session.removeAvatar();
}, },
[account, session] [session]
); );
return ( return (
@@ -104,9 +101,7 @@ export const AvatarAndName = () => {
} }
try { try {
mixpanel.track('UpdateUsername', { track.$.settingsPanel.accountSettings.updateUserName();
userId: account.id,
});
await session.updateLabel(input); await session.updateLabel(input);
} catch (e) { } catch (e) {
notify.error({ notify.error({
@@ -161,11 +156,8 @@ const StoragePanel = () => {
const setSettingModalAtom = useSetAtom(openSettingModalAtom); const setSettingModalAtom = useSetAtom(openSettingModalAtom);
const onUpgrade = useCallback(() => { const onUpgrade = useCallback(() => {
mixpanel.track('PlansViewed', { track.$.settingsPanel.accountUsage.viewPlans({
segment: 'settings panel', plan: SubscriptionPlan.Pro,
module: 'account usage list',
control: 'cloud storage upgrade button',
type: 'cloud subscription',
}); });
setSettingModalAtom({ setSettingModalAtom({
open: true, open: true,

View File

@@ -9,7 +9,7 @@ import { Button, IconButton } from '@affine/component/ui/button';
import { Loading } from '@affine/component/ui/loading'; import { Loading } from '@affine/component/ui/loading';
import { getUpgradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify'; import { getUpgradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import type { InvoicesQuery } from '@affine/graphql'; import type { InvoicesQuery } from '@affine/graphql';
import { import {
createCustomerPortalMutation, createCustomerPortalMutation,
@@ -112,22 +112,14 @@ const SubscriptionSettings = () => {
const openPlans = useCallback( const openPlans = useCallback(
(scrollAnchor?: PlansScrollAnchor) => { (scrollAnchor?: PlansScrollAnchor) => {
mixpanel.track('PlansViewed', { track.$.settingsPanel.billing.viewPlans();
type: proSubscription?.plan,
category: proSubscription?.recurring,
// page:
segment: 'settings panel',
module: 'billing subscription list',
control: 'change plan button',
});
setOpenSettingModalAtom({ setOpenSettingModalAtom({
open: true, open: true,
activeTab: 'plans', activeTab: 'plans',
scrollAnchor: scrollAnchor, scrollAnchor: scrollAnchor,
}); });
}, },
[proSubscription?.plan, proSubscription?.recurring, setOpenSettingModalAtom] [setOpenSettingModalAtom]
); );
const gotoCloudPlansSetting = useCallback( const gotoCloudPlansSetting = useCallback(
() => openPlans('cloudPricingPlan'), () => openPlans('cloudPricingPlan'),
@@ -238,7 +230,6 @@ const SubscriptionSettings = () => {
</SettingRow> </SettingRow>
) : ( ) : (
<CancelAction <CancelAction
module="billing subscription list"
open={openCancelModal} open={openCancelModal}
onOpenChange={setOpenCancelModal} onOpenChange={setOpenCancelModal}
> >
@@ -396,15 +387,9 @@ const AIPlanCard = ({ onClick }: { onClick: () => void }) => {
{price?.yearlyAmount ? ( {price?.yearlyAmount ? (
subscription ? ( subscription ? (
subscription.canceledAt ? ( subscription.canceledAt ? (
<AIResume <AIResume className={styles.planAction} />
module="billing subscription list"
className={styles.planAction}
/>
) : ( ) : (
<AICancel <AICancel className={styles.planAction} />
module="billing subscription list"
className={styles.planAction}
/>
) )
) : ( ) : (
<AISubscribe className={styles.planAction}> <AISubscribe className={styles.planAction}>
@@ -470,22 +455,16 @@ const ResumeSubscription = () => {
const subscription = useService(SubscriptionService).subscription; const subscription = useService(SubscriptionService).subscription;
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
setOpen(true); setOpen(true);
const type = subscription.pro$.value?.plan; }, []);
const category = subscription.pro$.value?.recurring;
if (type && category) {
mixpanel.track('PlanChangeStarted', {
segment: 'settings panel',
module: 'pricing plan list',
control: 'paying',
type,
category,
});
}
}, [subscription.pro$.value?.plan, subscription.pro$.value?.recurring]);
return ( return (
<ResumeAction open={open} onOpenChange={setOpen}> <ResumeAction open={open} onOpenChange={setOpen}>
<Button onClick={handleClick}> <Button
onClick={handleClick}
data-event-props="$.settingsPanel.plans.resumeSubscription"
data-event-args-type={subscription.pro$.value?.plan}
data-event-args-category={subscription.pro$.value?.recurring}
>
{t['com.affine.payment.billing-setting.resume-subscription']()} {t['com.affine.payment.billing-setting.resume-subscription']()}
</Button> </Button>
</ResumeAction> </ResumeAction>

View File

@@ -1,6 +1,6 @@
import { getDowngradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify'; import { getDowngradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { SubscriptionPlan } from '@affine/graphql'; import { SubscriptionPlan } from '@affine/graphql';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
@@ -20,11 +20,9 @@ export const CancelAction = ({
children, children,
open, open,
onOpenChange, onOpenChange,
module,
}: { }: {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
module: string;
} & PropsWithChildren) => { } & PropsWithChildren) => {
const [idempotencyKey, setIdempotencyKey] = useState(nanoid()); const [idempotencyKey, setIdempotencyKey] = useState(nanoid());
const [isMutating, setIsMutating] = useState(false); const [isMutating, setIsMutating] = useState(false);
@@ -35,14 +33,11 @@ export const CancelAction = ({
useEffect(() => { useEffect(() => {
if (!open || !proSubscription) return; if (!open || !proSubscription) return;
mixpanel.track('PlanChangeStarted', { track.$.settingsPanel.plans.cancelSubscription({
segment: 'settings panel', plan: proSubscription.plan,
module, recurring: proSubscription.recurring,
control: 'cancel',
type: proSubscription.plan,
category: proSubscription.recurring,
}); });
}, [module, open, proSubscription]); }, [open, proSubscription]);
const downgrade = useAsyncCallback(async () => { const downgrade = useAsyncCallback(async () => {
try { try {
@@ -57,10 +52,9 @@ export const CancelAction = ({
onOpenChange(false); onOpenChange(false);
const proSubscription = subscription.pro$.value; const proSubscription = subscription.pro$.value;
if (proSubscription) { if (proSubscription) {
mixpanel.track('PlanChangeSucceeded', { track.$.settingsPanel.plans.confirmCancelingSubscription({
control: 'cancel', plan: proSubscription.plan,
type: proSubscription.plan, recurring: proSubscription.recurring,
category: proSubscription.recurring,
}); });
} }
if (account && prevRecurring) { if (account && prevRecurring) {
@@ -127,10 +121,9 @@ export const ResumeAction = ({
onOpenChange(false); onOpenChange(false);
const proSubscription = subscription.pro$.value; const proSubscription = subscription.pro$.value;
if (proSubscription) { if (proSubscription) {
mixpanel.track('PlanChangeSucceeded', { track.$.settingsPanel.plans.confirmResumingSubscription({
control: 'paying', plan: proSubscription.plan,
type: proSubscription.plan, recurring: proSubscription.recurring,
category: proSubscription.recurring,
}); });
} }
} finally { } finally {

View File

@@ -2,7 +2,7 @@ import { Button, type ButtonProps, useConfirmModal } from '@affine/component';
import { useDowngradeNotify } from '@affine/core/components/affine/subscription-landing/notify'; import { useDowngradeNotify } from '@affine/core/components/affine/subscription-landing/notify';
import { getDowngradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify'; import { getDowngradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud'; import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
import { SubscriptionPlan } from '@affine/graphql'; import { SubscriptionPlan } from '@affine/graphql';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -10,10 +10,7 @@ import { useService } from '@toeverything/infra';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { useState } from 'react'; import { useState } from 'react';
export interface AICancelProps extends ButtonProps { export const AICancel = (btnProps: ButtonProps) => {
module: string;
}
export const AICancel = ({ module, ...btnProps }: AICancelProps) => {
const t = useI18n(); const t = useI18n();
const [isMutating, setMutating] = useState(false); const [isMutating, setMutating] = useState(false);
const [idempotencyKey, setIdempotencyKey] = useState(nanoid()); const [idempotencyKey, setIdempotencyKey] = useState(nanoid());
@@ -26,12 +23,9 @@ export const AICancel = ({ module, ...btnProps }: AICancelProps) => {
const cancel = useAsyncCallback(async () => { const cancel = useAsyncCallback(async () => {
const aiSubscription = subscription.ai$.value; const aiSubscription = subscription.ai$.value;
if (aiSubscription) { if (aiSubscription) {
mixpanel.track('PlanChangeStarted', { track.$.settingsPanel.plans.cancelSubscription({
module, plan: SubscriptionPlan.AI,
segment: 'settings panel', recurring: aiSubscription.recurring,
control: 'cancel',
type: SubscriptionPlan.AI,
category: aiSubscription.recurring,
}); });
} }
openConfirmModal({ openConfirmModal({
@@ -57,10 +51,9 @@ export const AICancel = ({ module, ...btnProps }: AICancelProps) => {
SubscriptionPlan.AI SubscriptionPlan.AI
); );
setIdempotencyKey(nanoid()); setIdempotencyKey(nanoid());
mixpanel.track('PlanChangeSucceeded', { track.$.settingsPanel.plans.confirmCancelingSubscription({
segment: 'settings panel', plan: SubscriptionPlan.AI,
control: 'plan cancel action', recurring: aiSubscription?.recurring,
type: subscription.ai$.value?.plan,
}); });
const account = authService.session.account$.value; const account = authService.session.account$.value;
const prevRecurring = subscription.ai$.value?.recurring; const prevRecurring = subscription.ai$.value?.recurring;
@@ -81,7 +74,6 @@ export const AICancel = ({ module, ...btnProps }: AICancelProps) => {
}, },
}); });
}, [ }, [
module,
subscription, subscription,
openConfirmModal, openConfirmModal,
t, t,

View File

@@ -5,7 +5,7 @@ import {
useConfirmModal, useConfirmModal,
} from '@affine/component'; } from '@affine/component';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { SubscriptionService } from '@affine/core/modules/cloud'; import { SubscriptionService } from '@affine/core/modules/cloud';
import { SubscriptionPlan } from '@affine/graphql'; import { SubscriptionPlan } from '@affine/graphql';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -15,11 +15,7 @@ import { cssVar } from '@toeverything/theme';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { useState } from 'react'; import { useState } from 'react';
export interface AIResumeProps extends ButtonProps { export const AIResume = (btnProps: ButtonProps) => {
module: string;
}
export const AIResume = ({ module, ...btnProps }: AIResumeProps) => {
const t = useI18n(); const t = useI18n();
const [idempotencyKey, setIdempotencyKey] = useState(nanoid()); const [idempotencyKey, setIdempotencyKey] = useState(nanoid());
const subscription = useService(SubscriptionService).subscription; const subscription = useService(SubscriptionService).subscription;
@@ -31,12 +27,9 @@ export const AIResume = ({ module, ...btnProps }: AIResumeProps) => {
const resume = useAsyncCallback(async () => { const resume = useAsyncCallback(async () => {
const aiSubscription = subscription.ai$.value; const aiSubscription = subscription.ai$.value;
if (aiSubscription) { if (aiSubscription) {
mixpanel.track('PlanChangeStarted', { track.$.settingsPanel.plans.resumeSubscription({
module, plan: SubscriptionPlan.AI,
segment: 'settings panel', recurring: aiSubscription.recurring,
control: 'paying',
type: aiSubscription.plan,
category: aiSubscription.recurring,
}); });
} }
@@ -58,10 +51,9 @@ export const AIResume = ({ module, ...btnProps }: AIResumeProps) => {
SubscriptionPlan.AI SubscriptionPlan.AI
); );
if (aiSubscription) { if (aiSubscription) {
mixpanel.track('PlanChangeSucceeded', { track.$.settingsPanel.plans.confirmResumingSubscription({
category: aiSubscription.recurring, plan: aiSubscription.plan,
control: 'paying', recurring: aiSubscription.recurring,
type: aiSubscription.plan,
}); });
} }
notify({ notify({
@@ -75,7 +67,7 @@ export const AIResume = ({ module, ...btnProps }: AIResumeProps) => {
setIdempotencyKey(nanoid()); setIdempotencyKey(nanoid());
}, },
}); });
}, [subscription, openConfirmModal, t, module, idempotencyKey]); }, [subscription, openConfirmModal, t, idempotencyKey]);
return ( return (
<Button <Button

View File

@@ -1,7 +1,7 @@
import { Button, type ButtonProps, Skeleton } from '@affine/component'; import { Button, type ButtonProps, Skeleton } from '@affine/component';
import { generateSubscriptionCallbackLink } from '@affine/core/hooks/affine/use-subscription-notify'; import { generateSubscriptionCallbackLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud'; import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
import { popupWindow } from '@affine/core/utils'; import { popupWindow } from '@affine/core/utils';
import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql'; import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql';
@@ -50,9 +50,9 @@ export const AISubscribe = ({
const subscribe = useAsyncCallback(async () => { const subscribe = useAsyncCallback(async () => {
setMutating(true); setMutating(true);
mixpanel.track('PlanUpgradeStarted', { track.$.settingsPanel.plans.checkout({
category: SubscriptionRecurring.Yearly, plan: SubscriptionPlan.AI,
type: SubscriptionPlan.AI, recurring: SubscriptionRecurring.Yearly,
}); });
try { try {
const session = await subscriptionService.createCheckoutSession({ const session = await subscriptionService.createCheckoutSession({

View File

@@ -53,15 +53,9 @@ export const AIPlan = () => {
isLoggedIn ? ( isLoggedIn ? (
subscription ? ( subscription ? (
subscription.canceledAt ? ( subscription.canceledAt ? (
<AIResume <AIResume className={styles.purchaseButton} />
module="pricing plan list"
className={styles.purchaseButton}
/>
) : ( ) : (
<AICancel <AICancel className={styles.purchaseButton} />
module="pricing plan list"
className={styles.purchaseButton}
/>
) )
) : ( ) : (
<> <>

View File

@@ -2,7 +2,7 @@ import { Button, type ButtonProps } from '@affine/component/ui/button';
import { Tooltip } from '@affine/component/ui/tooltip'; import { Tooltip } from '@affine/component/ui/tooltip';
import { generateSubscriptionCallbackLink } from '@affine/core/hooks/affine/use-subscription-notify'; import { generateSubscriptionCallbackLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud'; import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
import { popupWindow } from '@affine/core/utils'; import { popupWindow } from '@affine/core/utils';
import type { SubscriptionRecurring } from '@affine/graphql'; import type { SubscriptionRecurring } from '@affine/graphql';
@@ -190,7 +190,7 @@ const Downgrade = ({ disabled }: { disabled?: boolean }) => {
}, []); }, []);
return ( return (
<CancelAction module="pricing plan list" open={open} onOpenChange={setOpen}> <CancelAction open={open} onOpenChange={setOpen}>
<Tooltip content={tooltipContent} rootOptions={{ delayDuration: 0 }}> <Tooltip content={tooltipContent} rootOptions={{ delayDuration: 0 }}>
<div className={styles.planAction}> <div className={styles.planAction}>
<Button <Button
@@ -226,14 +226,13 @@ const BookDemo = ({ plan }: { plan: SubscriptionPlan }) => {
href={url} href={url}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
onClick={() => {
mixpanel.track('Button', {
resolve: 'BookDemo',
url,
});
}}
> >
<Button className={styles.planAction} variant="primary"> <Button
className={styles.planAction}
variant="primary"
data-event-props="$.settingsPanel.billing.bookDemo"
data-event-args-url={url}
>
{t['com.affine.payment.tell-us-use-case']()} {t['com.affine.payment.tell-us-use-case']()}
</Button> </Button>
</a> </a>
@@ -276,12 +275,9 @@ export const Upgrade = ({
const upgrade = useAsyncCallback(async () => { const upgrade = useAsyncCallback(async () => {
setMutating(true); setMutating(true);
mixpanel.track('PlanUpgradeStarted', { track.$.settingsPanel.plans.checkout({
segment: 'settings panel', plan: SubscriptionPlan.Pro,
module: 'pricing plan list', recurring: recurring,
control: 'pricing plan action',
type: 'cloud pro subscription',
category: recurring,
}); });
const link = await subscriptionService.createCheckoutSession({ const link = await subscriptionService.createCheckoutSession({
recurring, recurring,
@@ -338,12 +334,9 @@ const ChangeRecurring = ({
const subscription = useService(SubscriptionService).subscription; const subscription = useService(SubscriptionService).subscription;
const onStartChange = useCallback(() => { const onStartChange = useCallback(() => {
mixpanel.track('PlanChangeStarted', { track.$.settingsPanel.plans.changeSubscriptionRecurring({
segment: 'settings panel', plan: SubscriptionPlan.Pro,
module: 'pricing plan list', recurring: to,
control: 'paying',
type: SubscriptionPlan.Pro,
category: to,
}); });
setOpen(true); setOpen(true);
}, [to]); }, [to]);
@@ -422,12 +415,9 @@ const ResumeButton = () => {
setOpen(true); setOpen(true);
const pro = subscription.pro$.value; const pro = subscription.pro$.value;
if (pro) { if (pro) {
mixpanel.track('PlanChangeStarted', { track.$.settingsPanel.plans.resumeSubscription({
segment: 'settings panel', plan: SubscriptionPlan.Pro,
module: 'pricing plan list', recurring: pro.recurring,
control: 'paying',
type: SubscriptionPlan.Pro,
category: pro.recurring,
}); });
} }
}, [subscription.pro$.value]); }, [subscription.pro$.value]);

View File

@@ -6,7 +6,7 @@ import { Avatar } from '@affine/component/ui/avatar';
import { Tooltip } from '@affine/component/ui/tooltip'; import { Tooltip } from '@affine/component/ui/tooltip';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info'; import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { AuthService } from '@affine/core/modules/cloud'; import { AuthService } from '@affine/core/modules/cloud';
import { UserFeatureService } from '@affine/core/modules/cloud/services/user-feature'; import { UserFeatureService } from '@affine/core/modules/cloud/services/user-feature';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
@@ -22,7 +22,13 @@ import {
} from '@toeverything/infra'; } from '@toeverything/infra';
import clsx from 'clsx'; import clsx from 'clsx';
import { useAtom } from 'jotai/react'; import { useAtom } from 'jotai/react';
import { Suspense, useCallback, useEffect, useMemo } from 'react'; import {
type MouseEvent,
Suspense,
useCallback,
useEffect,
useMemo,
} from 'react';
import { authAtom } from '../../../../atoms'; import { authAtom } from '../../../../atoms';
import { UserPlanButton } from '../../auth/user-plan-button'; import { UserPlanButton } from '../../auth/user-plan-button';
@@ -115,29 +121,30 @@ export const SettingSidebar = ({
const t = useI18n(); const t = useI18n();
const loginStatus = useLiveData(useService(AuthService).session.status$); const loginStatus = useLiveData(useService(AuthService).session.status$);
const generalList = useGeneralSettingList(); const generalList = useGeneralSettingList();
const gotoTab = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
const tab = e.currentTarget.dataset.eventArg;
if (!tab) return;
track.$.settingsPanel.menu.openSettings({ to: tab });
onTabChange(tab as ActiveTab, null);
},
[onTabChange]
);
const onAccountSettingClick = useCallback(() => { const onAccountSettingClick = useCallback(() => {
mixpanel.track('AccountSettingsViewed', { track.$.settingsPanel.menu.openSettings({ to: 'account' });
// page:
segment: 'settings panel',
module: 'settings menu',
control: 'menu item',
});
onTabChange('account', null); onTabChange('account', null);
}, [onTabChange]); }, [onTabChange]);
const onWorkspaceSettingClick = useCallback( const onWorkspaceSettingClick = useCallback(
(subTab: WorkspaceSubTab, workspaceMetadata: WorkspaceMetadata) => { (subTab: WorkspaceSubTab, workspaceMetadata: WorkspaceMetadata) => {
mixpanel.track(`view workspace setting`, { track.$.settingsPanel.menu.openSettings({
// page: to: 'workspace',
segment: 'settings panel', control: subTab,
module: 'settings menu',
control: 'menu item',
type: subTab,
workspaceId: workspaceMetadata.id,
}); });
onTabChange(`workspace:${subTab}`, workspaceMetadata); onTabChange(`workspace:${subTab}`, workspaceMetadata);
}, },
[onTabChange] [onTabChange]
); );
return ( return (
<div className={style.settingSlideBar} data-testid="settings-sidebar"> <div className={style.settingSlideBar} data-testid="settings-sidebar">
<div className={style.sidebarTitle}> <div className={style.sidebarTitle}>
@@ -155,24 +162,8 @@ export const SettingSidebar = ({
})} })}
key={key} key={key}
title={title} title={title}
onClick={() => { data-event-arg={key}
if (key === 'billing') { onClick={gotoTab}
mixpanel.track('BillingViewed', {
// page:
segment: 'settings panel',
module: 'settings menu',
control: 'menu item',
});
} else if (key === 'plans') {
mixpanel.track('PlansViewed', {
// page:
segment: 'settings panel',
module: 'settings menu',
control: 'menu item',
});
}
onTabChange(key, null);
}}
data-testid={testId} data-testid={testId}
> >
{icon({ className: 'icon' })} {icon({ className: 'icon' })}

View File

@@ -21,7 +21,7 @@ import { useMemberCount } from '@affine/core/hooks/affine/use-member-count';
import type { Member } from '@affine/core/hooks/affine/use-members'; import type { Member } from '@affine/core/hooks/affine/use-members';
import { useMembers } from '@affine/core/hooks/affine/use-members'; import { useMembers } from '@affine/core/hooks/affine/use-members';
import { useRevokeMemberPermission } from '@affine/core/hooks/affine/use-revoke-member-permission'; import { useRevokeMemberPermission } from '@affine/core/hooks/affine/use-revoke-member-permission';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
@@ -146,11 +146,8 @@ export const CloudWorkspaceMembersPanel = () => {
activeTab: 'plans', activeTab: 'plans',
scrollAnchor: 'cloudPricingPlan', scrollAnchor: 'cloudPricingPlan',
}); });
mixpanel.track('PlansViewed', { track.$.settingsPanel.workspace.viewPlans({
// page: control: 'inviteMember',
segment: 'settings panel',
module: 'workspace setting',
control: 'invite member',
}); });
}, [setSettingModalAtom]); }, [setSettingModalAtom]);

View File

@@ -4,7 +4,7 @@ import { Button } from '@affine/component/ui/button';
import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu'; import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu';
import { useSharingUrl } from '@affine/core/hooks/affine/use-share-url'; import { useSharingUrl } from '@affine/core/hooks/affine/use-share-url';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { ServerConfigService } from '@affine/core/modules/cloud'; import { ServerConfigService } from '@affine/core/modules/cloud';
import { ShareService } from '@affine/core/modules/share-doc'; import { ShareService } from '@affine/core/modules/share-doc';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
@@ -106,11 +106,8 @@ export const AffineSharePage = (props: ShareMenuProps) => {
await shareService.share.enableShare( await shareService.share.enableShare(
mode === 'edgeless' ? PublicPageMode.Edgeless : PublicPageMode.Page mode === 'edgeless' ? PublicPageMode.Edgeless : PublicPageMode.Page
); );
mixpanel.track('ShareCreated', { track.$.header.share.createShareLink({
segment: 'sharing panel', mode,
module: 'public share',
control: 'share panel',
type: mode,
}); });
notify.success({ notify.success({
title: title:

View File

@@ -1,4 +1,4 @@
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { CloseIcon, DownloadIcon } from '@blocksuite/icons/rc'; import { CloseIcon, DownloadIcon } from '@blocksuite/icons/rc';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
@@ -21,9 +21,7 @@ export function AppDownloadButton({
// TODO(@JimmFly): unify this type of literal value. // TODO(@JimmFly): unify this type of literal value.
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
mixpanel.track('Button', { track.$.navigationPanel.bottomButtons.downloadApp();
resolve: 'GoToDownloadAppPage',
});
const url = `https://affine.pro/download?channel=stable`; const url = `https://affine.pro/download?channel=stable`;
open(url, '_blank'); open(url, '_blank');
}, []); }, []);

View File

@@ -2,7 +2,7 @@ import { notify } from '@affine/component';
import { authAtom, openSettingModalAtom } from '@affine/core/atoms'; import { authAtom, openSettingModalAtom } from '@affine/core/atoms';
import { AIProvider } from '@affine/core/blocksuite/presets/ai'; import { AIProvider } from '@affine/core/blocksuite/presets/ai';
import { toggleGeneralAIOnboarding } from '@affine/core/components/affine/ai-onboarding/apis'; import { toggleGeneralAIOnboarding } from '@affine/core/components/affine/ai-onboarding/apis';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { import {
getBaseUrl, getBaseUrl,
type getCopilotHistoriesQuery, type getCopilotHistoriesQuery,
@@ -470,10 +470,7 @@ Could you make a new website based on these notes and send back just the html fi
activeTab: 'billing', activeTab: 'billing',
open: true, open: true,
}); });
mixpanel.track('PlansViewed', { track.$.paywall.aiAction.viewPlans();
segment: 'payment wall',
category: 'payment wall ai action count',
});
}); });
AIProvider.slots.requestLogin.on(() => { AIProvider.slots.requestLogin.on(() => {

View File

@@ -1,5 +1,5 @@
import { AIProvider } from '@affine/core/blocksuite/presets/ai'; import { AIProvider } from '@affine/core/blocksuite/presets/ai';
import { mixpanel } from '@affine/core/mixpanel'; import { mixpanel, track } from '@affine/core/mixpanel';
import type { EditorHost } from '@blocksuite/block-std'; import type { EditorHost } from '@blocksuite/block-std';
import type { BlockModel } from '@blocksuite/store'; import type { BlockModel } from '@blocksuite/store';
import { lowerCase, omit } from 'lodash-es'; import { lowerCase, omit } from 'lodash-es';
@@ -13,7 +13,7 @@ type AIActionEventName =
| 'AI result accepted'; | 'AI result accepted';
type AIActionEventProperties = { type AIActionEventProperties = {
page: 'doc-editor' | 'whiteboard-editor'; page: 'doc' | 'edgeless';
segment: segment:
| 'AI action panel' | 'AI action panel'
| 'right side bar' | 'right side bar'
@@ -73,9 +73,7 @@ const trackAction = ({
}; };
const inferPageMode = (host: EditorHost) => { const inferPageMode = (host: EditorHost) => {
return host.querySelector('affine-page-root') return host.querySelector('affine-page-root') ? 'doc' : 'edgeless';
? 'doc-editor'
: 'whiteboard-editor';
}; };
const defaultActionOptions = [ const defaultActionOptions = [
@@ -253,14 +251,12 @@ const toTrackedOptions = (
export function setupTracker() { export function setupTracker() {
AIProvider.slots.requestUpgradePlan.on(() => { AIProvider.slots.requestUpgradePlan.on(() => {
mixpanel.track('AI', { track.$.paywall.aiAction.viewPlans();
action: 'requestUpgradePlan',
});
}); });
AIProvider.slots.requestLogin.on(() => { AIProvider.slots.requestLogin.on(() => {
mixpanel.track('AI', { track.$.$.auth.signIn({
action: 'requestLogin', control: 'aiAction',
}); });
}); });

View File

@@ -1,4 +1,4 @@
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { WorkspacePropertiesAdapter } from '@affine/core/modules/properties'; import { WorkspacePropertiesAdapter } from '@affine/core/modules/properties';
import { I18n, i18nTime } from '@affine/i18n'; import { I18n, i18nTime } from '@affine/i18n';
import type { EditorHost } from '@blocksuite/block-std'; import type { EditorHost } from '@blocksuite/block-std';
@@ -75,12 +75,7 @@ export function createLinkedWidgetConfig(framework: FrameworkProvider) {
inlineEditor, inlineEditor,
docId: doc.id, docId: doc.id,
}); });
mixpanel.track('LinkedDocCreated', { track.doc.editor.atMenu.linkDoc();
control: 'linked doc',
module: 'inline @',
type: 'doc',
other: 'existing doc',
});
}, },
})), })),
maxDisplay: MAX_DOCS, maxDisplay: MAX_DOCS,

View File

@@ -7,7 +7,7 @@ import {
toReactNode, toReactNode,
type useConfirmModal, type useConfirmModal,
} from '@affine/component'; } from '@affine/component';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { DocsSearchService } from '@affine/core/modules/docs-search'; import { DocsSearchService } from '@affine/core/modules/docs-search';
import { resolveLinkToDoc } from '@affine/core/modules/navigation'; import { resolveLinkToDoc } from '@affine/core/modules/navigation';
import type { PeekViewService } from '@affine/core/modules/peek-view'; import type { PeekViewService } from '@affine/core/modules/peek-view';
@@ -465,30 +465,11 @@ export function patchQuickSearchService(
pageId: linkedDoc.id, pageId: linkedDoc.id,
}, },
]); ]);
const isEdgeless =
rootComponent instanceof EdgelessRootBlockComponent;
if (result.isNewDoc) { if (result.isNewDoc) {
mixpanel.track('DocCreated', { track.doc.editor.slashMenu.createDoc({ control: 'linkDoc' });
control: 'linked doc', track.doc.editor.slashMenu.linkDoc({ control: 'createDoc' });
module: 'slash commands',
type: 'linked doc',
category: 'doc',
page: isEdgeless ? 'whiteboard editor' : 'doc editor',
});
mixpanel.track('LinkedDocCreated', {
control: 'new doc',
module: 'slash commands',
type: 'doc',
page: isEdgeless ? 'whiteboard editor' : 'doc editor',
});
} else {
mixpanel.track('LinkedDocCreated', {
control: 'linked doc',
module: 'slash commands',
type: 'doc',
page: isEdgeless ? 'whiteboard editor' : 'doc editor',
});
} }
track.doc.editor.slashMenu.linkDoc({ control: 'linkDoc' });
} else if ('userInput' in result) { } else if ('userInput' in result) {
const embedOptions = service.getEmbedBlockOptions( const embedOptions = service.getEmbedBlockOptions(
result.userInput result.userInput

View File

@@ -18,7 +18,7 @@ import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud';
import { useExportPage } from '@affine/core/hooks/affine/use-export-page'; import { useExportPage } from '@affine/core/hooks/affine/use-export-page';
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper'; import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { ViewService } from '@affine/core/modules/workbench/services/view'; import { ViewService } from '@affine/core/modules/workbench/services/view';
import { useDetailPageHeaderResponsive } from '@affine/core/pages/workspace/detail-page/use-header-responsive'; import { useDetailPageHeaderResponsive } from '@affine/core/pages/workspace/detail-page/use-header-responsive';
@@ -157,36 +157,23 @@ export const PageHeaderMenuButton = ({
const handleDuplicate = useCallback(() => { const handleDuplicate = useCallback(() => {
duplicate(pageId); duplicate(pageId);
mixpanel.track('DocCreated', { track.$.header.actions.createDoc({
segment: 'editor header', control: 'duplicate',
page: doc.mode$.value === 'page' ? 'doc editor' : 'edgeless editor',
module: 'header menu',
control: 'copy doc',
type: 'doc duplicate',
category: 'doc',
}); });
}, [doc.mode$.value, duplicate, pageId]); }, [duplicate, pageId]);
const onImportFile = useAsyncCallback(async () => { const onImportFile = useAsyncCallback(async () => {
const options = await importFile(); const options = await importFile();
if (options.isWorkspaceFile) { if (options.isWorkspaceFile) {
mixpanel.track('WorkspaceCreated', { track.$.header.actions.createWorkspace({
segment: 'editor header', control: 'import',
page: doc.mode$.value === 'page' ? 'doc editor' : 'edgeless editor',
module: 'header menu',
control: 'import button',
type: 'imported workspace',
}); });
} else { } else {
mixpanel.track('DocCreated', { track.$.header.actions.createDoc({
segment: 'editor header', control: 'import',
page: doc.mode$.value === 'page' ? 'doc editor' : 'edgeless editor',
module: 'header menu',
control: 'import button',
type: 'imported doc',
}); });
} }
}, [doc.mode$.value, importFile]); }, [importFile]);
const showResponsiveMenu = hideShare; const showResponsiveMenu = hideShare;
const ResponsiveMenuItems = ( const ResponsiveMenuItems = (

View File

@@ -1,6 +1,6 @@
import { Tooltip } from '@affine/component/ui/tooltip'; import { Tooltip } from '@affine/component/ui/tooltip';
import { useBlockSuiteDocMeta } from '@affine/core/hooks/use-block-suite-page-meta'; import { useBlockSuiteDocMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { import {
type DocMode, type DocMode,
@@ -62,8 +62,8 @@ export const EditorModeSwitch = ({
}, [currentMode, isPublic, doc, pageId, t, trash]); }, [currentMode, isPublic, doc, pageId, t, trash]);
const onSwitchToPageMode = useCallback(() => { const onSwitchToPageMode = useCallback(() => {
mixpanel.track('Button', { track.$.header.actions.switchPageMode({
resolve: 'SwitchToPageMode', mode: 'page',
}); });
if (currentMode === 'page' || isPublic) { if (currentMode === 'page' || isPublic) {
return; return;
@@ -73,8 +73,8 @@ export const EditorModeSwitch = ({
}, [currentMode, isPublic, doc, t]); }, [currentMode, isPublic, doc, t]);
const onSwitchToEdgelessMode = useCallback(() => { const onSwitchToEdgelessMode = useCallback(() => {
mixpanel.track('Button', { track.$.header.actions.switchPageMode({
resolve: 'SwitchToEdgelessMode', mode: 'edgeless',
}); });
if (currentMode === 'edgeless' || isPublic) { if (currentMode === 'edgeless' || isPublic) {
return; return;

View File

@@ -1,6 +1,6 @@
import { DropdownButton, Menu } from '@affine/component'; import { DropdownButton, Menu } from '@affine/component';
import { BlockCard } from '@affine/component/card/block-card'; import { BlockCard } from '@affine/component/card/block-card';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { EdgelessIcon, ImportIcon, PageIcon } from '@blocksuite/icons/rc'; import { EdgelessIcon, ImportIcon, PageIcon } from '@blocksuite/icons/rc';
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
@@ -70,26 +70,14 @@ export const NewPageButton = ({
const handleCreateNewPage = useCallback(() => { const handleCreateNewPage = useCallback(() => {
createNewPage(); createNewPage();
setOpen(false); setOpen(false);
mixpanel.track('DocCreated', { track.allDocs.header.actions.createDoc();
page: 'doc library',
segment: 'all doc',
module: 'doc list header',
control: 'new doc button',
type: 'doc',
category: 'page',
});
}, [createNewPage]); }, [createNewPage]);
const handleCreateNewEdgeless = useCallback(() => { const handleCreateNewEdgeless = useCallback(() => {
createNewEdgeless(); createNewEdgeless();
setOpen(false); setOpen(false);
mixpanel.track('DocCreated', { track.allDocs.header.actions.createDoc({
page: 'doc library', mode: 'edgeless',
segment: 'all doc',
module: 'doc list header',
control: 'new whiteboard button',
type: 'doc',
category: 'whiteboard',
}); });
}, [createNewEdgeless]); }, [createNewEdgeless]);

View File

@@ -7,7 +7,7 @@ import {
} from '@affine/component'; } from '@affine/component';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper'; import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import type { Tag } from '@affine/core/modules/tag'; import type { Tag } from '@affine/core/modules/tag';
import { TagService } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag';
import type { Collection } from '@affine/env/filter'; import type { Collection } from '@affine/env/filter';
@@ -49,21 +49,12 @@ export const PageListHeader = () => {
const onImportFile = useAsyncCallback(async () => { const onImportFile = useAsyncCallback(async () => {
const options = await importFile(); const options = await importFile();
if (options.isWorkspaceFile) { if (options.isWorkspaceFile) {
mixpanel.track('WorkspaceCreated', { track.allDocs.header.actions.createWorkspace({
page: 'doc library', control: 'import',
segment: 'all doc',
module: 'doc list header',
control: 'import button',
type: 'imported workspace',
}); });
} else { } else {
mixpanel.track('DocCreated', { track.allDocs.header.actions.createDoc({
page: 'doc library', control: 'import',
segment: 'all doc',
module: 'doc list header',
control: 'import button',
type: 'imported doc',
// category
}); });
} }
}, [importFile]); }, [importFile]);

View File

@@ -9,7 +9,7 @@ import {
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper'; import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper'; import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper'; import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { FavoriteService } from '@affine/core/modules/favorite'; import { FavoriteService } from '@affine/core/modules/favorite';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
@@ -115,13 +115,8 @@ export const PageOperationCell = ({
const onDuplicate = useCallback(() => { const onDuplicate = useCallback(() => {
duplicate(page.id, false); duplicate(page.id, false);
mixpanel.track('DocCreated', { track.allDocs.list.docMenu.createDoc({
segment: 'all doc', control: 'duplicate',
module: 'doc item menu',
control: 'copy doc',
type: 'doc duplicate',
category: 'doc',
page: 'doc library',
}); });
}, [duplicate, page.id]); }, [duplicate, page.id]);

View File

@@ -1,7 +1,7 @@
import { Loading } from '@affine/component'; import { Loading } from '@affine/component';
import { Divider } from '@affine/component/ui/divider'; import { Divider } from '@affine/component/ui/divider';
import { MenuItem } from '@affine/component/ui/menu'; import { MenuItem } from '@affine/component/ui/menu';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { AuthService } from '@affine/core/modules/cloud'; import { AuthService } from '@affine/core/modules/cloud';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { Logo1Icon } from '@blocksuite/icons/rc'; import { Logo1Icon } from '@blocksuite/icons/rc';
@@ -25,9 +25,7 @@ export const SignInItem = () => {
const t = useI18n(); const t = useI18n();
const onClickSignIn = useCallback(() => { const onClickSignIn = useCallback(() => {
mixpanel.track('Button', { track.$.navigationPanel.workspaceList.signIn();
resolve: 'SignIn',
});
setOpen(state => ({ setOpen(state => ({
...state, ...state,
openModal: true, openModal: true,
@@ -92,9 +90,7 @@ const UserWithWorkspaceListInner = ({
if (!isAuthenticated && !runtimeConfig.allowLocalWorkspace) { if (!isAuthenticated && !runtimeConfig.allowLocalWorkspace) {
return openSignInModal(); return openSignInModal();
} }
mixpanel.track('Button', { track.$.navigationPanel.workspaceList.createWorkspace();
resolve: 'NewWorkspace',
});
setOpenCreateWorkspaceModal('new'); setOpenCreateWorkspaceModal('new');
onEventEnd?.(); onEventEnd?.();
}, [ }, [
@@ -104,9 +100,10 @@ const UserWithWorkspaceListInner = ({
setOpenCreateWorkspaceModal, setOpenCreateWorkspaceModal,
]); ]);
track.$.navigationPanel.workspaceList.createWorkspace();
const onAddWorkspace = useCallback(() => { const onAddWorkspace = useCallback(() => {
mixpanel.track('Button', { track.$.navigationPanel.workspaceList.createWorkspace({
resolve: 'AddWorkspace', control: 'import',
}); });
setOpenCreateWorkspaceModal('add'); setOpenCreateWorkspaceModal('add');
onEventEnd?.(); onEventEnd?.();

View File

@@ -1,5 +1,5 @@
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { ImportIcon } from '@blocksuite/icons/rc'; import { ImportIcon } from '@blocksuite/icons/rc';
@@ -13,22 +13,11 @@ const ImportPage = ({ docCollection }: { docCollection: DocCollection }) => {
const onImportFile = useAsyncCallback(async () => { const onImportFile = useAsyncCallback(async () => {
const options = await importFile(); const options = await importFile();
if (options.isWorkspaceFile) { track.$.navigationPanel.workspaceList[
mixpanel.track('WorkspaceCreated', { options.isWorkspaceFile ? 'createWorkspace' : 'createDoc'
segment: 'navigation panel', ]({
module: 'doc list header', control: 'import',
control: 'import button', });
type: 'imported workspace',
});
} else {
mixpanel.track('DocCreated', {
segment: 'navigation panel',
module: 'doc list header',
control: 'import button',
type: 'imported doc',
// category
});
}
}, [importFile]); }, [importFile]);
return ( return (

View File

@@ -1,6 +1,6 @@
import { openSettingModalAtom } from '@affine/core/atoms'; import { openSettingModalAtom } from '@affine/core/atoms';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { import {
ExplorerCollections, ExplorerCollections,
ExplorerFavorites, ExplorerFavorites,
@@ -91,21 +91,14 @@ export const RootAppSidebar = (): ReactElement => {
async (e?: MouseEvent) => { async (e?: MouseEvent) => {
const page = pageHelper.createPage('page', false); const page = pageHelper.createPage('page', false);
page.load(); page.load();
mixpanel.track('DocCreated', { track.$.navigationPanel.$.createDoc();
segment: 'navigation panel',
module: 'bottom button',
control: 'new doc button',
category: 'page',
type: 'doc',
});
workbench.openDoc(page.id, { workbench.openDoc(page.id, {
at: e?.ctrlKey || e?.metaKey ? 'new-tab' : 'active', at: e?.ctrlKey || e?.metaKey ? 'new-tab' : 'active',
}); });
}, },
[pageHelper, workbench] [pageHelper, workbench]
); );
// Listen to the "New Page" action from the menu
useEffect(() => { useEffect(() => {
if (environment.isDesktop) { if (environment.isDesktop) {
return events?.applicationMenu.onNewPageAction(() => onClickNewPage()); return events?.applicationMenu.onNewPageAction(() => onClickNewPage());
@@ -120,12 +113,7 @@ export const RootAppSidebar = (): ReactElement => {
activeTab: 'appearance', activeTab: 'appearance',
open: true, open: true,
}); });
mixpanel.track('SettingsViewed', { track.$.navigationPanel.$.openSettings();
// page:
segment: 'navigation panel',
module: 'general list',
control: 'settings button',
});
}, [setOpenSettingModalAtom]); }, [setOpenSettingModalAtom]);
const sidebarOpen = useAtomValue(appSidebarOpenAtom); const sidebarOpen = useAtomValue(appSidebarOpenAtom);
@@ -153,7 +141,7 @@ export const RootAppSidebar = (): ReactElement => {
<QuickSearchInput <QuickSearchInput
className={quickSearch} className={quickSearch}
data-testid="slider-bar-quick-search-button" data-testid="slider-bar-quick-search-button"
data-event-props="$.navigationPanel.generalFunction.quickSearch" data-event-props="$.navigationPanel.$.quickSearch"
onClick={onOpenQuickSearchModal} onClick={onOpenQuickSearchModal}
/> />
<AddPageButton onClick={onClickNewPage} /> <AddPageButton onClick={onClickNewPage} />

View File

@@ -14,7 +14,7 @@ import {
openSettingModalAtom, openSettingModalAtom,
openSignOutModalAtom, openSignOutModalAtom,
} from '@affine/core/atoms'; } from '@affine/core/atoms';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { AccountIcon, SignOutIcon } from '@blocksuite/icons/rc'; import { AccountIcon, SignOutIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
@@ -83,12 +83,7 @@ const AccountMenu = () => {
const setOpenSignOutModalAtom = useSetAtom(openSignOutModalAtom); const setOpenSignOutModalAtom = useSetAtom(openSignOutModalAtom);
const onOpenAccountSetting = useCallback(() => { const onOpenAccountSetting = useCallback(() => {
mixpanel.track('AccountSettingsViewed', { track.$.navigationPanel.profileAndBadge.openSettings({ to: 'account' });
// page:
segment: 'navigation panel',
module: 'profile and badge',
control: 'profile and email',
});
setSettingModalAtom(prev => ({ setSettingModalAtom(prev => ({
...prev, ...prev,
open: true, open: true,

View File

@@ -1,5 +1,5 @@
import { Menu } from '@affine/component'; import { Menu } from '@affine/component';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { useService, WorkspacesService } from '@toeverything/infra'; import { useService, WorkspacesService } from '@toeverything/infra';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
@@ -16,9 +16,7 @@ export const WorkspaceSelector = () => {
setOpenUserWorkspaceList(false); setOpenUserWorkspaceList(false);
}, [setOpenUserWorkspaceList]); }, [setOpenUserWorkspaceList]);
const openUserWorkspaceList = useCallback(() => { const openUserWorkspaceList = useCallback(() => {
mixpanel.track('Button', { track.$.navigationPanel.workspaceList.open();
resolve: 'OpenWorkspaceList',
});
setOpenUserWorkspaceList(true); setOpenUserWorkspaceList(true);
}, [setOpenUserWorkspaceList]); }, [setOpenUserWorkspaceList]);

View File

@@ -2,7 +2,7 @@ import { Button } from '@affine/component/ui/button';
import { AffineShapeIcon } from '@affine/core/components/page-list'; // TODO(@eyhn): import from page-list temporarily, need to defined common svg icon/images management. import { AffineShapeIcon } from '@affine/core/components/page-list'; // TODO(@eyhn): import from page-list temporarily, need to defined common svg icon/images management.
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper'; import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { WorkspaceSubPath } from '@affine/core/shared'; import { WorkspaceSubPath } from '@affine/core/shared';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
@@ -26,9 +26,7 @@ export const WorkspaceUpgrade = function WorkspaceUpgrade() {
return; return;
} }
mixpanel.track('Button', { track.workspace.$.$.upgradeWorkspace();
resolve: 'UpgradeWorkspace',
});
try { try {
const newWorkspace = await currentWorkspace.upgrade.upgrade(); const newWorkspace = await currentWorkspace.upgrade.upgrade();

View File

@@ -3,7 +3,7 @@ import {
pushGlobalLoadingEventAtom, pushGlobalLoadingEventAtom,
resolveGlobalLoadingEventAtom, resolveGlobalLoadingEventAtom,
} from '@affine/component/global-loading'; } from '@affine/component/global-loading';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { apis } from '@affine/electron-api'; import { apis } from '@affine/electron-api';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import type { PageRootService, RootBlockModel } from '@blocksuite/blocks'; import type { PageRootService, RootBlockModel } from '@blocksuite/blocks';
@@ -26,10 +26,8 @@ async function exportHandler({ page, type }: ExportHandlerOptions) {
if (editorRoot) { if (editorRoot) {
pageService = editorRoot.spec.getService<PageRootService>('affine:page'); pageService = editorRoot.spec.getService<PageRootService>('affine:page');
} }
mixpanel.track('ShareCreated', { track.$.header.share.export({
type, type,
segment: 'sharing panel',
module: 'export share',
}); });
switch (type) { switch (type) {
case 'html': case 'html':

View File

@@ -4,7 +4,7 @@ import {
PreconditionStrategy, PreconditionStrategy,
registerAffineCommand, registerAffineCommand,
} from '@affine/core/commands'; } from '@affine/core/commands';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -161,10 +161,8 @@ export function useRegisterBlocksuiteEditorCommands() {
label: t['com.affine.header.option.duplicate'](), label: t['com.affine.header.option.duplicate'](),
run() { run() {
duplicate(docId); duplicate(docId);
mixpanel.track('DocCreated', { track.$.cmdk.$.createDoc({
control: 'cmdk', control: 'duplicate',
type: 'doc duplicate',
category: 'doc',
}); });
}, },
}) })

View File

@@ -1,5 +1,5 @@
import { notify } from '@affine/component'; import { notify } from '@affine/component';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch'; import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import type { Disposable } from '@blocksuite/global/utils'; import type { Disposable } from '@blocksuite/global/utils';
@@ -68,9 +68,8 @@ export const useSharingUrl = ({
.catch(err => { .catch(err => {
console.error(err); console.error(err);
}); });
mixpanel.track('ShareLinkCopied', { track.$.header.share.copyShareLink({
module: urlType === 'share' ? 'public share' : 'private share', type: urlType === 'share' ? 'public' : 'private',
type: 'link',
}); });
} else { } else {
notify.error({ notify.error({

View File

@@ -1,5 +1,5 @@
import { useUpgradeNotify } from '@affine/core/components/affine/subscription-landing/notify'; import { useUpgradeNotify } from '@affine/core/components/affine/subscription-landing/notify';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql'; import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
@@ -129,11 +129,9 @@ export const useSubscriptionNotifyReader = () => {
upgradeNotify(link); upgradeNotify(link);
localStorage.removeItem(localStorageKey); localStorage.removeItem(localStorageKey);
// mixpanel track.$.$.$.checkout({
mixpanel.track('PlanChangeSucceeded', { plan,
category: recurring, recurring,
type: plan,
control: 'new subscription',
}); });
} catch (err) { } catch (err) {
console.error('Failed to parse subscription callback link', err); console.error('Failed to parse subscription callback link', err);

View File

@@ -7,7 +7,7 @@ import { atomWithObservable, atomWithStorage } from 'jotai/utils';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { mixpanel } from '../mixpanel'; import { track } from '../mixpanel';
import { popupWindow } from '../utils'; import { popupWindow } from '../utils';
import { useAsyncCallback } from './affine-async-hooks'; import { useAsyncCallback } from './affine-async-hooks';
@@ -122,9 +122,7 @@ export const useAppUpdater = () => {
); );
const quitAndInstall = useCallback(() => { const quitAndInstall = useCallback(() => {
mixpanel.track('Button', { track.$.navigationPanel.bottomButtons.quitAndInstall();
resolve: 'QuitAndInstall',
});
if (updateReady) { if (updateReady) {
setAppQuitting(true); setAppQuitting(true);
apis?.updater.quitAndInstall().catch(err => { apis?.updater.quitAndInstall().catch(err => {
@@ -135,9 +133,7 @@ export const useAppUpdater = () => {
}, [updateReady]); }, [updateReady]);
const checkForUpdates = useCallback(async () => { const checkForUpdates = useCallback(async () => {
mixpanel.track('Button', { track.$.settingsPanel.about.checkUpdates();
resolve: 'CheckForUpdates',
});
if (checkingForUpdates) { if (checkingForUpdates) {
return; return;
} }
@@ -154,9 +150,7 @@ export const useAppUpdater = () => {
}, [checkingForUpdates, setCheckingForUpdates]); }, [checkingForUpdates, setCheckingForUpdates]);
const downloadUpdate = useCallback(() => { const downloadUpdate = useCallback(() => {
mixpanel.track('Button', { track.$.settingsPanel.about.downloadUpdate();
resolve: 'DownloadUpdate',
});
apis?.updater.downloadUpdate().catch(err => { apis?.updater.downloadUpdate().catch(err => {
console.error('Error downloading update:', err); console.error('Error downloading update:', err);
}); });
@@ -164,8 +158,8 @@ export const useAppUpdater = () => {
const toggleAutoDownload = useCallback( const toggleAutoDownload = useCallback(
(enable: boolean) => { (enable: boolean) => {
mixpanel.track('Button', { track.$.settingsPanel.about.changeAppSetting({
resolve: 'ToggleAutoDownload', key: 'autoDownload',
value: enable, value: enable,
}); });
setSetting({ setSetting({
@@ -177,8 +171,8 @@ export const useAppUpdater = () => {
const toggleAutoCheck = useCallback( const toggleAutoCheck = useCallback(
(enable: boolean) => { (enable: boolean) => {
mixpanel.track('Button', { track.$.settingsPanel.about.changeAppSetting({
resolve: 'ToggleAutoCheck', key: 'autoCheckUpdates',
value: enable, value: enable,
}); });
setSetting({ setSetting({
@@ -189,17 +183,13 @@ export const useAppUpdater = () => {
); );
const openChangelog = useAsyncCallback(async () => { const openChangelog = useAsyncCallback(async () => {
mixpanel.track('Button', { track.$.navigationPanel.bottomButtons.openChangelog();
resolve: 'OpenChangelog',
});
popupWindow(runtimeConfig.changelogUrl); popupWindow(runtimeConfig.changelogUrl);
await setChangelogUnread(true); await setChangelogUnread(true);
}, [setChangelogUnread]); }, [setChangelogUnread]);
const dismissChangelog = useAsyncCallback(async () => { const dismissChangelog = useAsyncCallback(async () => {
mixpanel.track('Button', { track.$.navigationPanel.bottomButtons.dismissChangelog();
resolve: 'DismissChangelog',
});
await setChangelogUnread(true); await setChangelogUnread(true);
}, [setChangelogUnread]); }, [setChangelogUnread]);

View File

@@ -15,7 +15,7 @@ export function makeTracker(trackFn: TrackFn): CallableEventsChain {
get(target, prop) { get(target, prop) {
if ( if (
typeof prop !== 'string' || typeof prop !== 'string' ||
prop === '$$typeof' /* webpack hot load reading this prop */ prop === '$$typeof' /* webpack hot-reload reads this prop */
) { ) {
return undefined; return undefined;
} }
@@ -55,7 +55,12 @@ export function makeTracker(trackFn: TrackFn): CallableEventsChain {
* @example * @example
* *
* ```html * ```html
* <button data-event-chain='$.cmdk.settings.quicksearch.changeLanguage' data-event-arg='cn' /> * <button
* data-event-chain='$.cmdk.settings.changeLanguage'
* data-event-arg='cn'
* <!-- or -->
* data-event-args-foo='bar'
* />
* ``` * ```
*/ */
export function enableAutoTrack(root: HTMLElement, trackFn: TrackFn) { export function enableAutoTrack(root: HTMLElement, trackFn: TrackFn) {
@@ -114,5 +119,6 @@ declare module 'react' {
interface HTMLAttributes<T> { interface HTMLAttributes<T> {
'data-event-props'?: EventsUnion; 'data-event-props'?: EventsUnion;
'data-event-arg'?: string; 'data-event-arg'?: string;
'data-event-args-control'?: string;
} }
} }

View File

@@ -1,43 +1,291 @@
// let '$' stands for unspecific matrix // let '$' stands for unspecific matrix
/* eslint-disable rxjs/finnish */ /* eslint-disable rxjs/finnish */
export interface Events {
$: { // SECTION: app events
cmdk: { type GeneralEvents = 'openMigrationDataHelp' | 'export';
settings: ['openSettings', 'changeLanguage']; type CmdkEvents = 'quickSearch';
}; type AppEvents =
navigationPanel: { | 'checkUpdates'
generalFunction: [ | 'downloadUpdate'
'quickSearch', | 'downloadApp'
'createDoc', | 'quitAndInstall'
'goToAllPage', | 'openChangelog'
'goToJournals', | 'dismissChangelog';
'openSettings', type NavigationEvents =
]; | 'openInNewTab'
collection: ['createDoc']; | 'openInSplitView'
bottomButtong: ['downloadApp', 'restartAndInstallUpdate']; | 'switchTab'
others: ['openTrash', 'export']; | 'switchSplitView'
| 'navigate'
| 'open'
| 'close'; // openclose modal/diaglog
// END SECTION
// SECTION: doc events
type WorkspaceEvents =
| 'createWorkspace'
| 'upgradeWorkspace'
| 'enableCloudWorkspace'
| 'import'
| 'export'
| 'openWorkspaceList';
type DocEvents =
| 'createDoc'
| 'renameDoc'
| 'linkDoc'
| 'deleteDoc'
| 'switchPageMode';
type EditorEvents = 'bold' | 'italic' | 'underline' | 'strikeThrough';
// END SECTION
// SECTION: setting events
type SettingEvents =
| 'openSettings'
| 'changeAppSetting'
| 'changeEditorSetting';
// END SECTION
// SECTION: organize events
type CollectionEvents =
| 'createCollection'
| 'deleteCollection'
| 'renameCollection'
| 'addDocToCollection';
type FolderEvents =
| 'createFolder'
| 'renameFolder'
| 'moveFolder'
| 'deleteFolder';
type TagEvents = 'createTag' | 'deleteTag' | 'renameTag' | 'tagDoc';
type FavoriteEvents = 'toggleFavorite';
type OrganizeItemEvents = // doc, link, folder, collection, tag
| 'createOrganizeItem'
| 'renameOrganizeItem'
| 'moveOrganizeItem'
| 'deleteOrganizeItem'
| 'orderOrganizeItem';
type OrganizeEvents =
| OrganizeItemEvents
| CollectionEvents
| FolderEvents
| TagEvents
| FavoriteEvents;
// END SECTION
// SECTION: cloud events
type ShareEvents = 'createShareLink' | 'copyShareLink';
type AuthEvents = 'signIn' | 'signUp' | 'oauth' | 'signOut';
type AccountEvents = 'uploadAvatar' | 'removeAvatar' | 'updateUserName';
type PaymentEvents =
| 'viewPlans'
| 'bookDemo'
| 'checkout'
| 'changeSubscriptionRecurring'
| 'confirmChangingSubscriptionRecurring'
| 'cancelSubscription'
| 'confirmCancelingSubscription'
| 'resumeSubscription'
| 'confirmResumingSubscription';
// END SECTION
type UserEvents =
| GeneralEvents
| AppEvents
| NavigationEvents
| WorkspaceEvents
| DocEvents
| EditorEvents
| SettingEvents
| CmdkEvents
| OrganizeEvents
| ShareEvents
| AuthEvents
| AccountEvents
| PaymentEvents;
interface PageDivision {
[page: string]: {
[segment: string]: {
[module: string]: UserEvents[];
}; };
}; };
}
const PageEvents = {
// page: {
// $: {}
// ^ if empty
// segment: {
// module: ['event1', 'event2']
// },
// },
// to: page.$.segment.module.event1()
$: {
$: {
$: ['createWorkspace', 'checkout'],
auth: ['oauth', 'signIn', 'signUp'],
},
settingsPanel: {
menu: ['openSettings'],
workspace: ['viewPlans'],
profileAndBadge: ['viewPlans'],
accountUsage: ['viewPlans'],
accountSettings: ['uploadAvatar', 'removeAvatar', 'updateUserName'],
plans: [
'checkout',
'changeSubscriptionRecurring',
'confirmChangingSubscriptionRecurring',
'cancelSubscription',
'confirmCancelingSubscription',
'resumeSubscription',
'confirmResumingSubscription',
],
billing: ['viewPlans', 'bookDemo'],
about: ['checkUpdates', 'downloadUpdate', 'changeAppSetting'],
},
cmdk: {
$: ['createDoc'],
settings: ['openSettings', 'changeAppSetting'],
},
navigationPanel: {
$: ['quickSearch', 'createDoc', 'navigate', 'openSettings'],
organize: [
'createOrganizeItem',
'renameOrganizeItem',
'moveOrganizeItem',
'deleteOrganizeItem',
'orderOrganizeItem',
'openInNewTab',
'openInSplitView',
'toggleFavorite',
],
docs: ['createDoc', 'deleteDoc', 'linkDoc'],
collections: ['createDoc', 'addDocToCollection'],
folders: ['createDoc'],
tags: ['createDoc', 'tagDoc'],
favorites: ['createDoc'],
migrationData: ['openMigrationDataHelp'],
bottomButtons: [
'downloadApp',
'quitAndInstall',
'openChangelog',
'dismissChangelog',
],
others: ['navigate', 'import'],
workspaceList: [
'open',
'signIn',
'createWorkspace',
'createDoc',
'openSettings',
],
profileAndBadge: ['openSettings'],
},
aiOnboarding: {
dialog: ['viewPlans'],
},
docHistory: {
$: ['open', 'close', 'switchPageMode', 'viewPlans'],
},
paywall: {
storage: ['viewPlans'],
aiAction: ['viewPlans'],
},
header: {
actions: ['createDoc', 'createWorkspace', 'switchPageMode'],
share: ['createShareLink', 'copyShareLink', 'export'],
},
},
doc: { doc: {
editor: { editor: {
formatToolbar: ['bold']; slashMenu: ['linkDoc', 'createDoc'],
atMenu: ['linkDoc'],
formatToolbar: ['bold'],
},
},
// remove when type added
// eslint-disable-next-line @typescript-eslint/ban-types
edgeless: {},
workspace: {
$: {
$: ['upgradeWorkspace'],
},
},
allDocs: {
header: {
actions: ['createDoc', 'createWorkspace'],
},
list: {
docMenu: ['createDoc'],
},
},
// remove when type added
// eslint-disable-next-line @typescript-eslint/ban-types
collection: {},
// remove when type added
// eslint-disable-next-line @typescript-eslint/ban-types
tag: {},
// remove when type added
// eslint-disable-next-line @typescript-eslint/ban-types
trash: {},
subscriptionLanding: {
$: {
$: ['checkout'],
},
},
} as const satisfies PageDivision;
type OrganizeItemType = 'doc' | 'folder' | 'collection' | 'tag' | 'favorite';
type OrganizeItemArgs =
| {
type: 'link';
target: OrganizeItemType;
}
| {
type: OrganizeItemType;
}; };
};
edgeless: { type PaymentEventArgs = {
editor: { plan: string;
formatToolbar: ['drawConnector']; recurring: string;
}; };
};
// remove when type added export type EventArgs = {
// eslint-disable-next-line @typescript-eslint/ban-types createWorkspace: { flavour: string };
allDocs: {}; oauth: { provider: string };
// remove when type added viewPlans: PaymentEventArgs;
// eslint-disable-next-line @typescript-eslint/ban-types checkout: PaymentEventArgs;
collection: {}; cancelSubscription: PaymentEventArgs;
// remove when type added confirmCancelingSubscription: PaymentEventArgs;
// eslint-disable-next-line @typescript-eslint/ban-types resumeSubscription: PaymentEventArgs;
tag: {}; confirmResumingSubscription: PaymentEventArgs;
// remove when type added changeSubscriptionRecurring: PaymentEventArgs;
// eslint-disable-next-line @typescript-eslint/ban-types confirmChangingSubscriptionRecurring: PaymentEventArgs;
trash: {}; navigate: { to: string };
} openSettings: { to: string };
changeAppSetting: { key: string; value: string | boolean | number };
changeEditorSetting: { key: string; value: string | boolean | number };
createOrganizeItem: OrganizeItemArgs;
renameOrganizeItem: OrganizeItemArgs;
moveOrganizeItem: OrganizeItemArgs;
deleteOrganizeItem: OrganizeItemArgs;
orderOrganizeItem: OrganizeItemArgs;
openInNewTab: { type: OrganizeItemType };
openInSplitView: { type: OrganizeItemType };
toggleFavorite: OrganizeItemArgs & { on: boolean };
createDoc: { mode?: 'edgeless' | 'page' };
switchPageMode: { mode: 'edgeless' | 'page' };
createShareLink: { mode: 'edgeless' | 'page' };
copyShareLink: { type: 'public' | 'private' };
export: { type: string };
};
// for type checking
// if it complains, check the definition of [EventArgs] to make sure it's key is a subset of [UserEvents]
export const YOU_MUST_DEFINE_ARGS_WITH_WRONG_EVENT_NAME: keyof EventArgs extends UserEvents
? true
: false = true;
export type Events = typeof PageEvents;

View File

@@ -1,13 +1,23 @@
import type { Events } from './events'; import type { EventArgs, Events } from './events';
type EventPropsOverride = {
page?: keyof Events;
segment?: string;
module?: string;
control?: string;
};
export type CallableEventsChain = { export type CallableEventsChain = {
[Page in keyof Events]: { [Page in keyof Events]: {
[Segment in keyof Events[Page]]: { [Segment in keyof Events[Page]]: {
[Module in keyof Events[Page][Segment]]: { [Module in keyof Events[Page][Segment]]: {
// @ts-expect-error ignore `symbol | number` as key // @ts-expect-error ignore `symbol | number` as key
[Control in Events[Page][Segment][Module][number]]: ( [Event in Events[Page][Segment][Module][number]]: Event extends keyof EventArgs
arg?: string ? (
) => void; // we make all args partial to simply satisfies nullish type checking
args?: Partial<EventArgs[Event]> & EventPropsOverride
) => void
: (args?: EventPropsOverride) => void;
}; };
}; };
}; };
@@ -18,14 +28,14 @@ export type EventsUnion = {
[Segment in keyof Events[Page]]: { [Segment in keyof Events[Page]]: {
[Module in keyof Events[Page][Segment]]: { [Module in keyof Events[Page][Segment]]: {
// @ts-expect-error ignore `symbol | number` as key // @ts-expect-error ignore `symbol | number` as key
[Control in Events[Page][Segment][Module][number]]: `${Page}.${Segment}.${Module}.${Control}`; [Event in Events[Page][Segment][Module][number]]: `${Page}.${Segment}.${Module}.${Event}`;
// @ts-expect-error ignore `symbol | number` as key // @ts-expect-error ignore `symbol | number` as key
}[Events[Page][Segment][Module][number]]; }[Events[Page][Segment][Module][number]];
}[keyof Events[Page][Segment]]; }[keyof Events[Page][Segment]];
}[keyof Events[Page]]; }[keyof Events[Page]];
}[keyof Events]; }[keyof Events];
// page > segment > module > [controls] // page > segment > module > [events]
type IsFourLevelsDeep< type IsFourLevelsDeep<
T, T,
Depth extends number[] = [], Depth extends number[] = [],

View File

@@ -10,7 +10,7 @@ import {
filterPage, filterPage,
useEditCollection, useEditCollection,
} from '@affine/core/components/page-list'; } from '@affine/core/components/page-list';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { CollectionService } from '@affine/core/modules/collection'; import { CollectionService } from '@affine/core/modules/collection';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { ShareDocsService } from '@affine/core/modules/share-doc'; import { ShareDocsService } from '@affine/core/modules/share-doc';
@@ -83,10 +83,9 @@ export const ExplorerCollectionNode = ({
...collection, ...collection,
name, name,
})); }));
mixpanel.track('CollectionRenamed', {
page: 'sidebar', track.$.navigationPanel.organize.renameOrganizeItem({
module: 'collection', type: 'collection',
control: 'rename collection',
}); });
toast(t['com.affine.toastMessage.rename']()); toast(t['com.affine.toastMessage.rename']());
} }
@@ -113,10 +112,10 @@ export const ExplorerCollectionNode = ({
if (collection && data.treeInstruction?.type === 'make-child') { if (collection && data.treeInstruction?.type === 'make-child') {
if (data.source.data.entity?.type === 'doc') { if (data.source.data.entity?.type === 'doc') {
handleAddDocToCollection(data.source.data.entity.id); handleAddDocToCollection(data.source.data.entity.id);
mixpanel.track('AddDocToCollection', { track.$.navigationPanel.organize.createOrganizeItem({
page: 'sidebar', type: 'link',
module: 'collection', target: 'doc',
control: 'drop doc on collection', control: 'drag',
}); });
} }
} else { } else {
@@ -144,10 +143,9 @@ export const ExplorerCollectionNode = ({
(data: DropTargetDropEvent<AffineDNDData>) => { (data: DropTargetDropEvent<AffineDNDData>) => {
if (collection && data.source.data.entity?.type === 'doc') { if (collection && data.source.data.entity?.type === 'doc') {
handleAddDocToCollection(data.source.data.entity.id); handleAddDocToCollection(data.source.data.entity.id);
mixpanel.track('AddDocToCollection', { track.$.navigationPanel.organize.createOrganizeItem({
page: 'sidebar', type: 'collection',
module: 'collection', control: 'drag',
control: 'drop doc on collection',
}); });
} }
}, },

View File

@@ -7,7 +7,7 @@ import {
} from '@affine/component'; } from '@affine/component';
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper'; import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { useDeleteCollectionInfo } from '@affine/core/hooks/affine/use-delete-collection-info'; import { useDeleteCollectionInfo } from '@affine/core/hooks/affine/use-delete-collection-info';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { CollectionService } from '@affine/core/modules/collection'; import { CollectionService } from '@affine/core/modules/collection';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
@@ -58,15 +58,9 @@ export const useExplorerCollectionNodeOperations = (
const createAndAddDocument = useCallback(() => { const createAndAddDocument = useCallback(() => {
const newDoc = docsService.createDoc(); const newDoc = docsService.createDoc();
collectionService.addPageToCollection(collectionId, newDoc.id); collectionService.addPageToCollection(collectionId, newDoc.id);
mixpanel.track('DocCreated', { track.$.navigationPanel.collections.createDoc();
page: 'sidebar', track.$.navigationPanel.collections.addDocToCollection({
module: 'collection', control: 'button',
control: 'add doc button',
});
mixpanel.track('AddDocToCollection', {
page: 'sidebar',
module: 'collection',
control: 'add doc button',
}); });
workbenchService.workbench.openDoc(newDoc.id); workbenchService.workbench.openDoc(newDoc.id);
onOpenCollapsed(); onOpenCollapsed();
@@ -80,12 +74,8 @@ export const useExplorerCollectionNodeOperations = (
const handleToggleFavoriteCollection = useCallback(() => { const handleToggleFavoriteCollection = useCallback(() => {
compatibleFavoriteItemsAdapter.toggle(collectionId, 'collection'); compatibleFavoriteItemsAdapter.toggle(collectionId, 'collection');
mixpanel.track('ToggleFavorite', { track.$.navigationPanel.organize.toggleFavorite({
page: 'sidebar',
module: 'collection',
control: 'toggle favorite collection button',
type: 'collection', type: 'collection',
id: collectionId,
}); });
}, [compatibleFavoriteItemsAdapter, collectionId]); }, [compatibleFavoriteItemsAdapter, collectionId]);
@@ -104,28 +94,20 @@ export const useExplorerCollectionNodeOperations = (
const handleOpenInSplitView = useCallback(() => { const handleOpenInSplitView = useCallback(() => {
workbenchService.workbench.openCollection(collectionId, { at: 'beside' }); workbenchService.workbench.openCollection(collectionId, { at: 'beside' });
mixpanel.track('OpenInSplitView', { track.$.navigationPanel.organize.openInSplitView({
page: 'sidebar', type: 'collection',
module: 'collection',
control: 'open in split view button',
}); });
}, [collectionId, workbenchService.workbench]); }, [collectionId, workbenchService.workbench]);
const handleOpenInNewTab = useCallback(() => { const handleOpenInNewTab = useCallback(() => {
workbenchService.workbench.openCollection(collectionId, { at: 'new-tab' }); workbenchService.workbench.openCollection(collectionId, { at: 'new-tab' });
mixpanel.track('OpenInNewTab', { track.$.navigationPanel.organize.openInNewTab({ type: 'collection' });
page: 'sidebar',
module: 'collection',
control: 'open in new tab button',
});
}, [collectionId, workbenchService.workbench]); }, [collectionId, workbenchService.workbench]);
const handleDeleteCollection = useCallback(() => { const handleDeleteCollection = useCallback(() => {
collectionService.deleteCollection(deleteInfo, collectionId); collectionService.deleteCollection(deleteInfo, collectionId);
mixpanel.track('CollectionDeleted', { track.$.navigationPanel.organize.deleteOrganizeItem({
page: 'sidebar', type: 'collection',
module: 'collection',
control: 'delete collection button',
}); });
}, [collectionId, collectionService, deleteInfo]); }, [collectionId, collectionService, deleteInfo]);

View File

@@ -7,7 +7,7 @@ import {
} from '@affine/component'; } from '@affine/component';
import { InfoModal } from '@affine/core/components/affine/page-properties'; import { InfoModal } from '@affine/core/components/affine/page-properties';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { DocsSearchService } from '@affine/core/modules/docs-search'; import { DocsSearchService } from '@affine/core/modules/docs-search';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -116,11 +116,7 @@ export const ExplorerDocNode = ({
const handleRename = useAsyncCallback( const handleRename = useAsyncCallback(
async (newName: string) => { async (newName: string) => {
await docsService.changeDocTitle(docId, newName); await docsService.changeDocTitle(docId, newName);
mixpanel.track('DocRenamed', { track.$.navigationPanel.organize.renameOrganizeItem({ type: 'doc' });
page: 'sidebar',
module: 'doc',
control: 'doc rename',
});
}, },
[docId, docsService] [docId, docsService]
); );
@@ -130,10 +126,8 @@ export const ExplorerDocNode = ({
if (data.treeInstruction?.type === 'make-child') { if (data.treeInstruction?.type === 'make-child') {
if (data.source.data.entity?.type === 'doc') { if (data.source.data.entity?.type === 'doc') {
await docsService.addLinkedDoc(docId, data.source.data.entity.id); await docsService.addLinkedDoc(docId, data.source.data.entity.id);
mixpanel.track('LinkedDocCreated', { track.$.navigationPanel.docs.linkDoc({
page: 'sidebar', control: 'drag',
module: 'doc',
control: 'drop doc on doc',
}); });
} else { } else {
toast(t['com.affine.rootAppSidebar.doc.link-doc-only']()); toast(t['com.affine.rootAppSidebar.doc.link-doc-only']());
@@ -164,10 +158,8 @@ export const ExplorerDocNode = ({
if (data.source.data.entity?.type === 'doc') { if (data.source.data.entity?.type === 'doc') {
// TODO(eyhn): timeout&error handling // TODO(eyhn): timeout&error handling
await docsService.addLinkedDoc(docId, data.source.data.entity.id); await docsService.addLinkedDoc(docId, data.source.data.entity.id);
mixpanel.track('LinkedDocCreated', { track.$.navigationPanel.docs.linkDoc({
page: 'sidebar', control: 'drag',
module: 'doc',
control: 'drop doc on doc',
}); });
} else { } else {
toast(t['com.affine.rootAppSidebar.doc.link-doc-only']()); toast(t['com.affine.rootAppSidebar.doc.link-doc-only']());

View File

@@ -8,7 +8,7 @@ import {
} from '@affine/component'; } from '@affine/component';
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper'; import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -68,10 +68,8 @@ export const useExplorerDocNodeOperations = (
}, },
onConfirm() { onConfirm() {
docRecord.moveToTrash(); docRecord.moveToTrash();
mixpanel.track('DocMovedTrash', { track.$.navigationPanel.docs.deleteDoc({
page: 'sidebar', control: 'button',
module: 'doc',
control: 'move to trash button',
}); });
toast(t['com.affine.toastMessage.movedTrash']()); toast(t['com.affine.toastMessage.movedTrash']());
}, },
@@ -82,10 +80,8 @@ export const useExplorerDocNodeOperations = (
workbenchService.workbench.openDoc(docId, { workbenchService.workbench.openDoc(docId, {
at: 'new-tab', at: 'new-tab',
}); });
mixpanel.track('OpenInNewTab', { track.$.navigationPanel.organize.openInNewTab({
page: 'sidebar', type: 'doc',
module: 'doc',
control: 'open in new tab button',
}); });
}, [docId, workbenchService]); }, [docId, workbenchService]);
@@ -93,10 +89,8 @@ export const useExplorerDocNodeOperations = (
workbenchService.workbench.openDoc(docId, { workbenchService.workbench.openDoc(docId, {
at: 'beside', at: 'beside',
}); });
mixpanel.track('OpenInSplitView', { track.$.navigationPanel.organize.openInSplitView({
page: 'sidebar', type: 'doc',
module: 'doc',
control: 'open in split view button',
}); });
}, [docId, workbenchService]); }, [docId, workbenchService]);
@@ -104,28 +98,16 @@ export const useExplorerDocNodeOperations = (
const newDoc = docsService.createDoc(); const newDoc = docsService.createDoc();
// TODO: handle timeout & error // TODO: handle timeout & error
await docsService.addLinkedDoc(docId, newDoc.id); await docsService.addLinkedDoc(docId, newDoc.id);
mixpanel.track('DocCreated', { track.$.navigationPanel.docs.createDoc({ control: 'linkDoc' });
page: 'sidebar', track.$.navigationPanel.docs.linkDoc({ control: 'createDoc' });
module: 'doc',
control: 'add linked doc button',
});
mixpanel.track('LinkedDocCreated', {
page: 'sidebar',
module: 'doc',
control: 'add linked doc button',
});
workbenchService.workbench.openDoc(newDoc.id); workbenchService.workbench.openDoc(newDoc.id);
options.openNodeCollapsed(); options.openNodeCollapsed();
}, [docId, options, docsService, workbenchService.workbench]); }, [docId, options, docsService, workbenchService.workbench]);
const handleToggleFavoriteDoc = useCallback(() => { const handleToggleFavoriteDoc = useCallback(() => {
compatibleFavoriteItemsAdapter.toggle(docId, 'doc'); compatibleFavoriteItemsAdapter.toggle(docId, 'doc');
mixpanel.track('ToggleFavorite', { track.$.navigationPanel.organize.toggleFavorite({
page: 'sidebar',
module: 'doc',
control: 'toggle favorite button',
type: 'doc', type: 'doc',
id: docId,
}); });
}, [docId, compatibleFavoriteItemsAdapter]); }, [docId, compatibleFavoriteItemsAdapter]);

View File

@@ -15,7 +15,7 @@ import {
useSelectDoc, useSelectDoc,
useSelectTag, useSelectTag,
} from '@affine/core/components/page-list/selector'; } from '@affine/core/components/page-list/selector';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { import {
type FolderNode, type FolderNode,
OrganizeService, OrganizeService,
@@ -178,10 +178,8 @@ export const ExplorerFolderNodeFolder = ({
const handleDelete = useCallback(() => { const handleDelete = useCallback(() => {
node.delete(); node.delete();
mixpanel.track('FolderDeleted', { track.$.navigationPanel.organize.deleteOrganizeItem({
page: 'sidebar', type: 'folder',
module: 'organize',
control: `delete folder`,
}); });
notify.success({ notify.success({
title: t['com.affine.rootAppSidebar.organize.delete.notify-title']({ title: t['com.affine.rootAppSidebar.organize.delete.notify-title']({
@@ -229,23 +227,14 @@ export const ExplorerFolderNodeFolder = ({
return; return;
} }
node.moveHere(data.source.data.entity.id, node.indexAt('before')); node.moveHere(data.source.data.entity.id, node.indexAt('before'));
mixpanel.track('FolderMoved', { track.$.navigationPanel.organize.moveOrganizeItem({ type: 'folder' });
page: 'sidebar',
module: 'organize',
control: 'drop folder at folder',
type: 'folder',
id: data.source.data.entity.id,
});
} else if ( } else if (
data.source.data.from?.at === 'explorer:organize:folder-node' data.source.data.from?.at === 'explorer:organize:folder-node'
) { ) {
node.moveHere(data.source.data.from.nodeId, node.indexAt('before')); node.moveHere(data.source.data.from.nodeId, node.indexAt('before'));
mixpanel.track('FolderLinkMoved', { track.$.navigationPanel.organize.moveOrganizeItem({
page: 'sidebar', type: 'link',
module: 'organize', target: data.source.data.entity?.type,
control: 'drop folder link at folder',
type: data.source.data.entity?.type,
id: data.source.data.entity?.id,
}); });
} else if ( } else if (
data.source.data.entity?.type === 'collection' || data.source.data.entity?.type === 'collection' ||
@@ -257,12 +246,9 @@ export const ExplorerFolderNodeFolder = ({
data.source.data.entity.id, data.source.data.entity.id,
node.indexAt('before') node.indexAt('before')
); );
mixpanel.track('FolderLinkCreated', { track.$.navigationPanel.organize.createOrganizeItem({
page: 'sidebar', type: 'link',
module: 'organize', target: data.source.data.entity?.type,
control: 'drop entity at folder',
type: data.source.data.entity?.type,
id: data.source.data.entity?.id,
}); });
} }
} else { } else {
@@ -312,23 +298,13 @@ export const ExplorerFolderNodeFolder = ({
return; return;
} }
node.moveHere(data.source.data.entity.id, node.indexAt('before')); node.moveHere(data.source.data.entity.id, node.indexAt('before'));
mixpanel.track('FolderMoved', { track.$.navigationPanel.organize.moveOrganizeItem({ type: 'folder' });
page: 'sidebar',
module: 'organize',
control: 'drop folder at folder',
type: 'folder',
id: data.source.data.entity.id,
});
} else if ( } else if (
data.source.data.from?.at === 'explorer:organize:folder-node' data.source.data.from?.at === 'explorer:organize:folder-node'
) { ) {
node.moveHere(data.source.data.from.nodeId, node.indexAt('before')); node.moveHere(data.source.data.from.nodeId, node.indexAt('before'));
mixpanel.track('FolderLinkMoved', { track.$.navigationPanel.organize.moveOrganizeItem({
page: 'sidebar',
module: 'organize',
control: 'drop folder link at folder',
type: data.source.data.entity?.type, type: data.source.data.entity?.type,
id: data.source.data.entity?.id,
}); });
} else if ( } else if (
data.source.data.entity?.type === 'collection' || data.source.data.entity?.type === 'collection' ||
@@ -340,12 +316,9 @@ export const ExplorerFolderNodeFolder = ({
data.source.data.entity.id, data.source.data.entity.id,
node.indexAt('before') node.indexAt('before')
); );
mixpanel.track('FolderLinkCreated', { track.$.navigationPanel.organize.createOrganizeItem({
page: 'sidebar', type: 'link',
module: 'organize', target: data.source.data.entity?.type,
control: 'drop entity at folder',
type: data.source.data.entity?.type,
id: data.source.data.entity?.id,
}); });
} }
}, },
@@ -374,13 +347,7 @@ export const ExplorerFolderNodeFolder = ({
data.source.data.entity.id, data.source.data.entity.id,
node.indexAt(at, dropAtNode.id) node.indexAt(at, dropAtNode.id)
); );
mixpanel.track('FolderMoved', { track.$.navigationPanel.organize.moveOrganizeItem({ type: 'folder' });
page: 'sidebar',
module: 'organize',
control: `drop folder ${at === 'before' ? 'above' : 'below'} node`,
type: 'folder',
id: data.source.data.entity?.id,
});
} else if ( } else if (
data.source.data.from?.at === 'explorer:organize:folder-node' data.source.data.from?.at === 'explorer:organize:folder-node'
) { ) {
@@ -388,12 +355,9 @@ export const ExplorerFolderNodeFolder = ({
data.source.data.from.nodeId, data.source.data.from.nodeId,
node.indexAt(at, dropAtNode.id) node.indexAt(at, dropAtNode.id)
); );
mixpanel.track('FolderLinkMoved', { track.$.navigationPanel.organize.moveOrganizeItem({
page: 'sidebar', type: 'link',
module: 'organize', target: data.source.data.entity?.type,
control: `drop folder link ${at === 'before' ? 'above' : 'below'} node`,
type: data.source.data.entity?.type,
id: data.source.data.entity?.id,
}); });
} else if ( } else if (
data.source.data.entity?.type === 'collection' || data.source.data.entity?.type === 'collection' ||
@@ -405,12 +369,10 @@ export const ExplorerFolderNodeFolder = ({
data.source.data.entity.id, data.source.data.entity.id,
node.indexAt(at, dropAtNode.id) node.indexAt(at, dropAtNode.id)
); );
mixpanel.track('FolderLinkCreated', {
page: 'sidebar', track.$.navigationPanel.organize.createOrganizeItem({
module: 'organize', type: 'link',
control: `drop entity ${at === 'before' ? 'above' : 'below'} node`, target: data.source.data.entity?.type,
type: data.source.data.entity?.type,
id: data.source.data.entity?.id,
}); });
} }
} else if (data.treeInstruction?.type === 'reparent') { } else if (data.treeInstruction?.type === 'reparent') {
@@ -560,17 +522,10 @@ export const ExplorerFolderNodeFolder = ({
const newDoc = docsService.createDoc(); const newDoc = docsService.createDoc();
node.createLink('doc', newDoc.id, node.indexAt('before')); node.createLink('doc', newDoc.id, node.indexAt('before'));
workbenchService.workbench.openDoc(newDoc.id); workbenchService.workbench.openDoc(newDoc.id);
mixpanel.track('DocCreated', { track.$.navigationPanel.folders.createDoc();
page: 'sidebar', track.$.navigationPanel.organize.createOrganizeItem({
module: 'organize', type: 'link',
control: `folder new doc button`, target: 'doc',
});
mixpanel.track('FolderLinkCreated', {
page: 'sidebar',
module: 'organize',
control: `folder new doc button`,
type: 'doc',
id: newDoc.id,
}); });
setCollapsed(false); setCollapsed(false);
}, [docsService, node, workbenchService.workbench]); }, [docsService, node, workbenchService.workbench]);
@@ -580,11 +535,7 @@ export const ExplorerFolderNodeFolder = ({
t['com.affine.rootAppSidebar.organize.new-folders'](), t['com.affine.rootAppSidebar.organize.new-folders'](),
node.indexAt('before') node.indexAt('before')
); );
mixpanel.track('FolderCreated', { track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
page: 'sidebar',
module: 'organize',
control: `create sub folder`,
});
setCollapsed(false); setCollapsed(false);
setNewFolderId(newFolderId); setNewFolderId(newFolderId);
}, [node, t]); }, [node, t]);
@@ -612,13 +563,6 @@ export const ExplorerFolderNodeFolder = ({
newItemIds.forEach(id => { newItemIds.forEach(id => {
node.createLink(type, id, node.indexAt('after')); node.createLink(type, id, node.indexAt('after'));
mixpanel.track('FolderLinkCreated', {
page: 'sidebar',
module: 'organize',
control: `add selector`,
type,
id,
});
}); });
removedItems.forEach(node => node.delete()); removedItems.forEach(node => node.delete());
const updated = newItemIds.length + removedItems.length; const updated = newItemIds.length + removedItems.length;
@@ -627,6 +571,10 @@ export const ExplorerFolderNodeFolder = ({
.catch(err => { .catch(err => {
console.error(`Unexpected error while selecting ${type}`, err); console.error(`Unexpected error while selecting ${type}`, err);
}); });
track.$.navigationPanel.organize.createOrganizeItem({
type: 'link',
target: type,
});
}, },
[ [
children, children,
@@ -763,7 +711,7 @@ export const ExplorerFolderNodeFolder = ({
handleCreateSubfolder, handleCreateSubfolder,
handleDelete, handleDelete,
handleNewDoc, handleNewDoc,
node.id, node,
t, t,
]); ]);
@@ -774,23 +722,6 @@ export const ExplorerFolderNodeFolder = ({
return folderOperations; return folderOperations;
}, [additionalOperations, folderOperations]); }, [additionalOperations, folderOperations]);
const handleDeleteChildren = useCallback((node: FolderNode) => {
node.delete();
if (node.type$.value === 'folder') {
mixpanel.track('FolderDeleted', {
page: 'sidebar',
module: 'organize',
control: 'remove from folder button',
});
} else {
mixpanel.track('FolderLinkDeleted', {
page: 'sidebar',
module: 'organize',
control: 'remove from folder button',
});
}
}, []);
const childrenOperations = useCallback( const childrenOperations = useCallback(
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
(type: string, node: FolderNode) => { (type: string, node: FolderNode) => {
@@ -806,7 +737,9 @@ export const ExplorerFolderNodeFolder = ({
<RemoveFolderIcon /> <RemoveFolderIcon />
</MenuIcon> </MenuIcon>
} }
onClick={() => handleDeleteChildren(node)} data-event-props="$.navigationPanel.organize.deleteOrganizeItem"
data-event-args-type={node.type$.value}
onClick={node.delete}
> >
{t['com.affine.rootAppSidebar.organize.delete-from-folder']()} {t['com.affine.rootAppSidebar.organize.delete-from-folder']()}
</MenuItem> </MenuItem>
@@ -816,7 +749,7 @@ export const ExplorerFolderNodeFolder = ({
} }
return []; return [];
}, },
[handleDeleteChildren, t] [t]
); );
const handleCollapsedChange = useCallback((collapsed: boolean) => { const handleCollapsedChange = useCallback((collapsed: boolean) => {

View File

@@ -3,7 +3,7 @@ import {
type DropTargetOptions, type DropTargetOptions,
toast, toast,
} from '@affine/component'; } from '@affine/component';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import type { Tag } from '@affine/core/modules/tag'; import type { Tag } from '@affine/core/modules/tag';
import { TagService } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
@@ -84,10 +84,8 @@ export const ExplorerTagNode = ({
(newName: string) => { (newName: string) => {
if (tagRecord && tagRecord.value$.value !== newName) { if (tagRecord && tagRecord.value$.value !== newName) {
tagRecord.rename(newName); tagRecord.rename(newName);
mixpanel.track('TagRenamed', { track.$.navigationPanel.organize.renameOrganizeItem({
page: 'sidebar', type: 'tag',
module: 'tag',
control: 'tag rename',
}); });
} }
}, },
@@ -99,10 +97,8 @@ export const ExplorerTagNode = ({
if (data.treeInstruction?.type === 'make-child' && tagRecord) { if (data.treeInstruction?.type === 'make-child' && tagRecord) {
if (data.source.data.entity?.type === 'doc') { if (data.source.data.entity?.type === 'doc') {
tagRecord.tag(data.source.data.entity.id); tagRecord.tag(data.source.data.entity.id);
mixpanel.track('DocTagged', { track.$.navigationPanel.tags.tagDoc({
page: 'sidebar', control: 'drag',
module: 'tag',
control: 'drop doc on tag',
}); });
} else { } else {
toast(t['com.affine.rootAppSidebar.tag.doc-only']()); toast(t['com.affine.rootAppSidebar.tag.doc-only']());

View File

@@ -6,7 +6,7 @@ import {
toast, toast,
} from '@affine/component'; } from '@affine/component';
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper'; import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { FavoriteService } from '@affine/core/modules/favorite'; import { FavoriteService } from '@affine/core/modules/favorite';
import { TagService } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
@@ -51,16 +51,7 @@ export const useExplorerTagNodeOperations = (
if (tagRecord) { if (tagRecord) {
const newDoc = docsService.createDoc(); const newDoc = docsService.createDoc();
tagRecord?.tag(newDoc.id); tagRecord?.tag(newDoc.id);
mixpanel.track('DocCreated', { track.$.navigationPanel.tags.createDoc();
page: 'sidebar',
module: 'tag',
control: 'add doc button',
});
mixpanel.track('DocTagged', {
page: 'sidebar',
module: 'tag',
control: 'add doc button',
});
workbenchService.workbench.openDoc(newDoc.id); workbenchService.workbench.openDoc(newDoc.id);
openNodeCollapsed(); openNodeCollapsed();
} }
@@ -68,11 +59,7 @@ export const useExplorerTagNodeOperations = (
const handleMoveToTrash = useCallback(() => { const handleMoveToTrash = useCallback(() => {
tagService.tagList.deleteTag(tagId); tagService.tagList.deleteTag(tagId);
mixpanel.track('TagDeleted', { track.$.navigationPanel.organize.deleteOrganizeItem({ type: 'tag' });
page: 'sidebar',
module: 'tag',
control: 'remove tag button',
});
toast(t['com.affine.tags.delete-tags.toast']()); toast(t['com.affine.tags.delete-tags.toast']());
}, [t, tagId, tagService.tagList]); }, [t, tagId, tagService.tagList]);
@@ -80,21 +67,13 @@ export const useExplorerTagNodeOperations = (
workbenchService.workbench.openTag(tagId, { workbenchService.workbench.openTag(tagId, {
at: 'beside', at: 'beside',
}); });
mixpanel.track('OpenInSplitView', { track.$.navigationPanel.organize.openInSplitView({ type: 'tag' });
page: 'sidebar',
module: 'tag',
control: 'open in split view button',
});
}, [tagId, workbenchService]); }, [tagId, workbenchService]);
const handleToggleFavoriteTag = useCallback(() => { const handleToggleFavoriteTag = useCallback(() => {
favoriteService.favoriteList.toggle('tag', tagId); favoriteService.favoriteList.toggle('tag', tagId);
mixpanel.track('ToggleFavorite', { track.$.navigationPanel.organize.toggleFavorite({
page: 'sidebar',
module: 'tag',
control: 'toggle favorite tag button',
type: 'tag', type: 'tag',
id: tagId,
}); });
}, [favoriteService, tagId]); }, [favoriteService, tagId]);
@@ -102,11 +81,7 @@ export const useExplorerTagNodeOperations = (
workbenchService.workbench.openTag(tagId, { workbenchService.workbench.openTag(tagId, {
at: 'new-tab', at: 'new-tab',
}); });
mixpanel.track('OpenInNewTab', { track.$.navigationPanel.organize.openInNewTab({ type: 'tag' });
page: 'sidebar',
module: 'tag',
control: 'open in new tab button',
});
}, [tagId, workbenchService]); }, [tagId, workbenchService]);
return useMemo( return useMemo(

View File

@@ -1,7 +1,7 @@
import { IconButton } from '@affine/component'; import { IconButton } from '@affine/component';
import { useEditCollectionName } from '@affine/core/components/page-list'; import { useEditCollectionName } from '@affine/core/components/page-list';
import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager'; import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { CollectionService } from '@affine/core/modules/collection'; import { CollectionService } from '@affine/core/modules/collection';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree'; import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
@@ -35,10 +35,8 @@ export const ExplorerCollections = () => {
.then(name => { .then(name => {
const id = nanoid(); const id = nanoid();
collectionService.addCollection(createEmptyCollection(id, { name })); collectionService.addCollection(createEmptyCollection(id, { name }));
mixpanel.track('CollectionCreated', { track.$.navigationPanel.organize.createOrganizeItem({
page: 'sidebar', type: 'collection',
module: 'collections',
control: 'new collection button',
}); });
workbenchService.workbench.openCollection(id); workbenchService.workbench.openCollection(id);
explorerSection.setCollapsed(false); explorerSection.setCollapsed(false);

View File

@@ -4,7 +4,7 @@ import {
IconButton, IconButton,
useDropTarget, useDropTarget,
} from '@affine/component'; } from '@affine/component';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { import {
DropEffect, DropEffect,
type ExplorerTreeNodeDropEffect, type ExplorerTreeNodeDropEffect,
@@ -59,12 +59,9 @@ export const ExplorerFavorites = () => {
data.source.data.entity.id, data.source.data.entity.id,
favoriteService.favoriteList.indexAt('before') favoriteService.favoriteList.indexAt('before')
); );
mixpanel.track('AddFavorite', { track.$.navigationPanel.organize.toggleFavorite({
page: 'sidebar',
module: 'favorite',
control: 'drop entity to favorite',
type: data.source.data.entity.type, type: data.source.data.entity.type,
id: data.source.data.entity.id, on: true,
}); });
explorerSection.setCollapsed(false); explorerSection.setCollapsed(false);
} }
@@ -98,18 +95,6 @@ export const ExplorerFavorites = () => {
newDoc.id, newDoc.id,
favoriteService.favoriteList.indexAt('before') favoriteService.favoriteList.indexAt('before')
); );
mixpanel.track('DocCreated', {
page: 'sidebar',
module: 'favorites',
control: 'new favorite doc button',
});
mixpanel.track('AddFavorite', {
page: 'sidebar',
module: 'favorite',
control: 'new favorite doc button',
type: 'doc',
id: newDoc.id,
});
workbenchService.workbench.openDoc(newDoc.id); workbenchService.workbench.openDoc(newDoc.id);
explorerSection.setCollapsed(false); explorerSection.setCollapsed(false);
}, [ }, [
@@ -144,12 +129,8 @@ export const ExplorerFavorites = () => {
favorite favorite
) )
); );
mixpanel.track('ReorderFavorite', { track.$.navigationPanel.organize.orderOrganizeItem({
page: 'sidebar',
module: 'favorite',
control: 'drop to reorder favorite',
type: data.source.data.entity.type, type: data.source.data.entity.type,
id: data.source.data.entity.id,
}); });
} else if ( } else if (
data.source.data.entity?.type && data.source.data.entity?.type &&
@@ -165,12 +146,9 @@ export const ExplorerFavorites = () => {
favorite favorite
) )
); );
mixpanel.track('AddFavorite', { track.$.navigationPanel.organize.toggleFavorite({
page: 'sidebar',
module: 'favorite',
control: 'drop entity to favorite',
type: data.source.data.entity.type, type: data.source.data.entity.type,
id: data.source.data.entity.id, on: true,
}); });
} else { } else {
return; // not supported return; // not supported
@@ -238,6 +216,8 @@ export const ExplorerFavorites = () => {
<> <>
<IconButton <IconButton
data-testid="explorer-bar-add-favorite-button" data-testid="explorer-bar-add-favorite-button"
data-event-props="$.navigationPanel.favorites.createDoc"
data-event-args-control="addFavorite"
onClick={handleCreateNewFavoriteDoc} onClick={handleCreateNewFavoriteDoc}
size="16" size="16"
tooltip={t[ tooltip={t[

View File

@@ -1,5 +1,5 @@
import { IconButton, useConfirmModal } from '@affine/component'; import { IconButton, useConfirmModal } from '@affine/component';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree'; import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
import { FavoriteItemsAdapter } from '@affine/core/modules/properties'; import { FavoriteItemsAdapter } from '@affine/core/modules/properties';
import { Trans, useI18n } from '@affine/i18n'; import { Trans, useI18n } from '@affine/i18n';
@@ -58,18 +58,8 @@ export const ExplorerMigrationFavorites = () => {
t['com.affine.rootAppSidebar.migration-data.clean-all.cancel'](), t['com.affine.rootAppSidebar.migration-data.clean-all.cancel'](),
onConfirm() { onConfirm() {
favoriteItemsAdapter.clearAll(); favoriteItemsAdapter.clearAll();
mixpanel.track('AllMigrationDataCleared', {
page: 'sidebar',
module: 'migration data',
control: 'clear button',
});
}, },
}); });
mixpanel.track('ClickClearMigrationDataButton', {
page: 'sidebar',
module: 'migration data',
control: 'clear button',
});
}, [favoriteItemsAdapter, openConfirmModal, t]); }, [favoriteItemsAdapter, openConfirmModal, t]);
const handleClickHelp = useCallback(() => { const handleClickHelp = useCallback(() => {
@@ -92,11 +82,7 @@ export const ExplorerMigrationFavorites = () => {
}, },
}, },
}); });
mixpanel.track('OpenMigrationDataHelp', { track.$.navigationPanel.migrationData.openMigrationDataHelp();
page: 'sidebar',
module: 'migration data',
control: 'help button',
});
}, [handleClickClear, openConfirmModal, t]); }, [handleClickClear, openConfirmModal, t]);
if (favorites.length === 0) { if (favorites.length === 0) {

View File

@@ -4,7 +4,7 @@ import {
IconButton, IconButton,
toast, toast,
} from '@affine/component'; } from '@affine/component';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { import {
type ExplorerTreeNodeDropEffect, type ExplorerTreeNodeDropEffect,
ExplorerTreeRoot, ExplorerTreeRoot,
@@ -46,11 +46,7 @@ export const ExplorerOrganize = () => {
'New Folder', 'New Folder',
rootFolder.indexAt('before') rootFolder.indexAt('before')
); );
mixpanel.track('FolderCreated', { track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
page: 'sidebar',
module: 'organize',
control: 'new folder',
});
setNewFolderId(newFolderId); setNewFolderId(newFolderId);
explorerSection.setCollapsed(false); explorerSection.setCollapsed(false);
}, [explorerSection, rootFolder]); }, [explorerSection, rootFolder]);
@@ -71,13 +67,7 @@ export const ExplorerOrganize = () => {
data.source.data.entity.id, data.source.data.entity.id,
rootFolder.indexAt(at, node.id) rootFolder.indexAt(at, node.id)
); );
mixpanel.track('FolderMoved', { track.$.navigationPanel.organize.moveOrganizeItem({ type: 'folder' });
page: 'sidebar',
module: 'organize',
control: 'drop at root',
type: 'folder',
id: node.id,
});
} else { } else {
toast(t['com.affine.rootAppSidebar.organize.root-folder-only']()); toast(t['com.affine.rootAppSidebar.organize.root-folder-only']());
} }

View File

@@ -1,5 +1,5 @@
import { IconButton } from '@affine/component'; import { IconButton } from '@affine/component';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree'; import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
import type { Tag } from '@affine/core/modules/tag'; import type { Tag } from '@affine/core/modules/tag';
import { TagService } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag';
@@ -32,11 +32,7 @@ export const ExplorerTags = () => {
tagService.randomTagColor() tagService.randomTagColor()
); );
setCreatedTag(newTags); setCreatedTag(newTags);
mixpanel.track('TagCreated', { track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
page: 'sidebar',
module: 'tags',
control: 'new tag button',
});
explorerSection.setCollapsed(false); explorerSection.setCollapsed(false);
}, [explorerSection, t, tagService]); }, [explorerSection, t, tagService]);

View File

@@ -8,7 +8,7 @@ import { EMPTY, mergeMap, switchMap } from 'rxjs';
import { generateSubscriptionCallbackLink } from '../hooks/affine/use-subscription-notify'; import { generateSubscriptionCallbackLink } from '../hooks/affine/use-subscription-notify';
import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper'; import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
import { mixpanel } from '../mixpanel'; import { mixpanel, track } from '../mixpanel';
import { AuthService, SubscriptionService } from '../modules/cloud'; import { AuthService, SubscriptionService } from '../modules/cloud';
import { container } from './subscribe.css'; import { container } from './subscribe.css';
@@ -70,9 +70,9 @@ export const Component = () => {
: !!subscriptionService.subscription.pro$.value; : !!subscriptionService.subscription.pro$.value;
if (!subscribed) { if (!subscribed) {
setMessage('Creating checkout...'); setMessage('Creating checkout...');
mixpanel.track('PlanUpgradeStarted', { track.subscriptionLanding.$.$.checkout({
type: plan, plan: receivedPlan,
category: recurring, recurring: receivedRecurring,
}); });
try { try {
const account = authService.session.account$.value; const account = authService.session.account$.value;

View File

@@ -7,7 +7,7 @@ import {
import { Header } from '@affine/core/components/pure/header'; import { Header } from '@affine/core/components/pure/header';
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab'; import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { mixpanel } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import type { Filter } from '@affine/env/filter'; import type { Filter } from '@affine/env/filter';
import { PlusIcon } from '@blocksuite/icons/rc'; import { PlusIcon } from '@blocksuite/icons/rc';
import { useService, WorkspaceService } from '@toeverything/infra'; import { useService, WorkspaceService } from '@toeverything/infra';
@@ -32,21 +32,12 @@ export const AllPageHeader = ({
const onImportFile = useAsyncCallback(async () => { const onImportFile = useAsyncCallback(async () => {
const options = await importFile(); const options = await importFile();
if (options.isWorkspaceFile) { if (options.isWorkspaceFile) {
mixpanel.track('WorkspaceCreated', { track.allDocs.header.actions.createWorkspace({
page: 'doc library', control: 'import',
segment: 'all page',
module: 'doc list header',
control: 'import button',
type: 'imported workspace',
}); });
} else { } else {
mixpanel.track('DocCreated', { track.allDocs.header.actions.createDoc({
page: 'doc library', control: 'import',
segment: 'all page',
module: 'doc list header',
control: 'import button',
type: 'imported doc',
// category
}); });
} }
}, [importFile]); }, [importFile]);