From 15ddb7e75e35cb1923edcd528a7e883c156fcfe6 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Fri, 10 Jan 2025 14:25:22 -0500 Subject: [PATCH] add more error and debug logs to invite flow --- packages/app/lib/bootHelpers.ts | 14 +- packages/shared/src/logic/analytics.ts | 3 + packages/shared/src/store/lure.ts | 149 +++++++++++------- .../components/InviteFriendsToTlonButton.tsx | 12 ++ 4 files changed, 119 insertions(+), 59 deletions(-) diff --git a/packages/app/lib/bootHelpers.ts b/packages/app/lib/bootHelpers.ts index 3754cd8c81..51efd358d6 100644 --- a/packages/app/lib/bootHelpers.ts +++ b/packages/app/lib/bootHelpers.ts @@ -1,4 +1,4 @@ -import { AppInvite } from '@tloncorp/shared'; +import { AnalyticsEvent, AppInvite, createDevLogger } from '@tloncorp/shared'; import { getLandscapeAuthCookie } from '@tloncorp/shared/api'; import * as db from '@tloncorp/shared/db'; @@ -6,6 +6,8 @@ import * as hostingApi from '../lib/hostingApi'; import { trackOnboardingAction } from '../utils/posthog'; import { getShipFromCookie, getShipUrl } from '../utils/ship'; +const logger = createDevLogger('bootHelpers', true); + export enum NodeBootPhase { IDLE = 1, RESERVING = 2, @@ -133,9 +135,13 @@ async function getInvitedGroupAndDm(lureMeta: AppInvite | null): Promise<{ const tlonTeam = `~wittyr-witbes`; const isPersonalInvite = inviteType === 'user'; if (!inviterUserId || (!isPersonalInvite && !invitedGroupId)) { - throw new Error( - `invalid invite metadata: group[${invitedGroupId}] inviter[${inviterUserId}]` - ); + logger.trackEvent(AnalyticsEvent.InviteError, { + message: 'invite is missing metadata', + context: + 'this will prevent the group from being auto-joined, but an invite should still be delivered', + invite: lureMeta, + }); + throw new Error('invite is missing metadata'); } // use api client to see if you have pending DM and group invite const invitedDm = await db.getChannel({ id: inviterUserId }); diff --git a/packages/shared/src/logic/analytics.ts b/packages/shared/src/logic/analytics.ts index 99a4a32066..32c345360f 100644 --- a/packages/shared/src/logic/analytics.ts +++ b/packages/shared/src/logic/analytics.ts @@ -14,4 +14,7 @@ export enum AnalyticsEvent { ChannelTemplateSetup = 'Channel Created from Template', ChannelLoadComplete = 'Channel Load Complete', SessionInitialized = 'Session Initialized', + InviteError = 'Invite Error', + InviteDebug = 'Invite Debug', + InviteButtonShown = 'Invite Button Shown', } diff --git a/packages/shared/src/store/lure.ts b/packages/shared/src/store/lure.ts index 701981b001..6c7d4d37a4 100644 --- a/packages/shared/src/store/lure.ts +++ b/packages/shared/src/store/lure.ts @@ -6,13 +6,12 @@ import create from 'zustand'; import { getCurrentUserId, poke, scry, subscribeOnce } from '../api/urbit'; import * as db from '../db'; import { createDevLogger } from '../debug'; +import { AnalyticsEvent } from '../logic'; import { DeepLinkMetadata, createDeepLink } from '../logic/branch'; import { asyncWithDefault, getFlagParts, withRetry } from '../logic/utils'; import { stringToTa } from '../urbit'; import { GroupMeta } from '../urbit/groups'; -const logger = createDevLogger('lure', true); - interface LureMetadata { tag: string; fields: Record; @@ -73,29 +72,37 @@ export const useLureState = create((set, get) => ({ }); } - await poke({ - app: 'reel', - mark: 'reel-describe', - json: { - token: flag, - metadata: groupsDescribe({ - // legacy keys - title: group?.title ?? '', - description: group?.description ?? '', - cover: group?.coverImage ?? '', - image: group?.iconImage ?? '', - - // new-style metadata keys - inviterUserId: currentUserId, - inviterNickname: user?.nickname ?? '', - inviterAvatarImage: user?.avatarImage ?? '', - invitedGroupId: flag, - invitedGroupTitle: group?.title ?? '', - invitedGroupDescription: group?.description ?? '', - invitedGroupIconImageUrl: group?.iconImage ?? '', - }), - }, - }); + try { + await poke({ + app: 'reel', + mark: 'reel-describe', + json: { + token: flag, + metadata: groupsDescribe({ + // legacy keys + title: group?.title ?? '', + description: group?.description ?? '', + cover: group?.coverImage ?? '', + image: group?.iconImage ?? '', + + // new-style metadata keys + inviterUserId: currentUserId, + inviterNickname: user?.nickname ?? '', + inviterAvatarImage: user?.avatarImage ?? '', + invitedGroupId: flag, + invitedGroupTitle: group?.title ?? '', + invitedGroupDescription: group?.description ?? '', + invitedGroupIconImageUrl: group?.iconImage ?? '', + }), + }, + }); + } catch (e) { + lureLogger.trackError(AnalyticsEvent.InviteError, { + context: 'reel describe failed', + errorMessage: e.message, + errorStack: e.stack, + }); + } }, toggle: async (flag) => { const { name } = getFlagParts(flag); @@ -130,16 +137,24 @@ export const useLureState = create((set, get) => ({ }); }, start: async () => { - const bait = await scry({ - app: 'reel', - path: '/bait', - }); + try { + const bait = await scry({ + app: 'reel', + path: '/bait', + }); - set( - produce((draft: LureState) => { - draft.bait = bait; - }) - ); + set( + produce((draft: LureState) => { + draft.bait = bait; + }) + ); + } catch (e) { + lureLogger.trackEvent(AnalyticsEvent.InviteError, { + context: 'failed to get bait on start', + errorMessage: e.message, + errorStack: e.stack, + }); + } }, fetchLure: async (flag, inviteServiceEndpoint, inviteServiceIsDev) => { const { name } = getFlagParts(flag); @@ -162,7 +177,11 @@ export const useLureState = create((set, get) => ({ return en; }) .catch((e) => { - lureLogger.error(`group-enabled failed`, e); + lureLogger.trackEvent(AnalyticsEvent.InviteError, { + context: `group-enabled failed`, + errorMessage: e.message, + errorStack: e.stack, + }); return prevLure?.enabled; }); }, prevLure?.enabled), @@ -178,7 +197,11 @@ export const useLureState = create((set, get) => ({ return u; }) .catch((e) => { - lureLogger.error(`id-link failed`, e); + lureLogger.trackError(AnalyticsEvent.InviteDebug, { + context: `id-link failed`, + errorMessage: e.message, + errorStack: e.stack, + }); return prevLure?.url; }); }, prevLure?.url), @@ -227,6 +250,14 @@ export const useLureState = create((set, get) => ({ lureLogger.crumb('deepLinkUrl created', deepLinkUrl); } + lureLogger.trackEvent(AnalyticsEvent.InviteDebug, { + context: 'fetchLure result', + flag, + enabled, + url, + deepLinkUrl, + }); + set( produce((draft: LureState) => { draft.lures[flag] = { @@ -351,16 +382,21 @@ export function useLureLinkStatus({ }); const { good, checked } = useLureLinkChecked(url, !!enabled); - lureLogger.crumb('useLureLinkStatus', { - flag, - supported, - fetched, - enabled, - checked, - good, - url, - deepLinkUrl, - }); + const inviteInfo = useMemo( + () => ({ + flag, + supported, + fetched, + enabled, + checked, + good, + url, + deepLinkUrl, + }), + [flag, supported, fetched, enabled, checked, good, url, deepLinkUrl] + ); + + lureLogger.crumb('useLureLinkStatus', inviteInfo); const status = useMemo(() => { if (!supported) { @@ -381,21 +417,24 @@ export function useLureLinkStatus({ } if (checked && !good) { - lureLogger.trackError('useLureLinkStatus has error status', { - flag, - enabled, - checked, - good, - url, - deepLinkUrl, - }); return 'error'; } return 'ready'; }, [supported, fetched, enabled, url, checked, deepLinkUrl, good, flag]); - lureLogger.crumb('url', url, 'deepLinkUrl', deepLinkUrl, 'status', status); + if (status === 'error') { + lureLogger.trackEvent(AnalyticsEvent.InviteError, { + context: 'useLureLinkStatus has error status', + inviteInfo, + }); + } else { + lureLogger.trackEvent(AnalyticsEvent.InviteDebug, { + context: 'useLureLinkStatus log', + status, + inviteInfo, + }); + } return { status, shareUrl: deepLinkUrl, toggle, describe }; } diff --git a/packages/ui/src/components/InviteFriendsToTlonButton.tsx b/packages/ui/src/components/InviteFriendsToTlonButton.tsx index dcb5a13b84..e3cf39efbf 100644 --- a/packages/ui/src/components/InviteFriendsToTlonButton.tsx +++ b/packages/ui/src/components/InviteFriendsToTlonButton.tsx @@ -74,9 +74,17 @@ export function InviteFriendsToTlonButton({ await toggle(); }; if (status === 'disabled' && isGroupAdmin) { + logger.trackEvent(AnalyticsEvent.InviteDebug, { + group: group?.id, + context: 'invite button: disabled and isAdmin, toggling', + }); toggleLink(); } if (status === 'stale') { + logger.trackEvent(AnalyticsEvent.InviteDebug, { + group: group?.id, + context: 'invite button: stale, describing', + }); describe(); } }, [group, toggle, status, isGroupAdmin, describe]); @@ -98,6 +106,10 @@ export function InviteFriendsToTlonButton({ const linkFailed = linkIsDisabled || status === 'error' || status === 'unsupported'; + useEffect(() => { + logger.trackEvent('Invite Button Shown', { group: group?.id }); + }, []); + return (