From f1d55f49fa1edeed6ab7399875094f7e052b53f5 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 13 Mar 2024 03:29:03 +0000 Subject: [PATCH 1/5] Send route name with Statsig events (#3194) * Add types to Statsig events * Send route name with events --- src/Navigation.tsx | 11 +++++++++-- src/lib/statsig/events.ts | 5 +++++ src/lib/statsig/statsig.tsx | 27 ++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 src/lib/statsig/events.ts diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 8a9f69b5..77706ce3 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -78,7 +78,7 @@ import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStack import {msg} from '@lingui/macro' import {i18n, MessageDescriptor} from '@lingui/core' import HashtagScreen from '#/screens/Hashtag' -import {logEvent} from './lib/statsig/statsig' +import {logEvent, attachRouteToLogEvents} from './lib/statsig/statsig' const navigationRef = createNavigationContainerRef() @@ -543,6 +543,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { linking={LINKING} theme={theme} onReady={() => { + attachRouteToLogEvents(getCurrentRouteName) logModuleInitTime() onReady() }}> @@ -551,6 +552,10 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { ) } +function getCurrentRouteName() { + return navigationRef.getCurrentRoute()?.name +} + /** * These helpers can be used from outside of the RoutesContainer * (eg in the state models). @@ -656,7 +661,9 @@ function logModuleInitTime() { performance.now() - global.__BUNDLE_START_TIME__, ) console.log(`Time to first paint: ${initMs} ms`) - logEvent('init', initMs) + logEvent('init', { + initMs, + }) if (__DEV__) { // This log is noisy, so keep false committed diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts new file mode 100644 index 00000000..bc647710 --- /dev/null +++ b/src/lib/statsig/events.ts @@ -0,0 +1,5 @@ +export type Events = { + init: { + initMs: number + } +} diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index 6d9ebeb0..a46cef4d 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -6,6 +6,7 @@ import { } from 'statsig-react-native-expo' import {useSession} from '../../state/session' import {sha256} from 'js-sha256' +import {Events} from './events' const statsigOptions = { environment: { @@ -17,12 +18,28 @@ const statsigOptions = { initTimeoutMs: 1, } -export function logEvent( - eventName: string, - value?: string | number | null, - metadata?: Record | null, +type FlatJSONRecord = Record< + string, + string | number | boolean | null | undefined +> + +let getCurrentRouteName: () => string | null | undefined = () => null + +export function attachRouteToLogEvents( + getRouteName: () => string | null | undefined, ) { - Statsig.logEvent(eventName, value, metadata) + getCurrentRouteName = getRouteName +} + +export function logEvent( + eventName: E & string, + rawMetadata?: Events[E] & FlatJSONRecord, +) { + const fullMetadata = { + ...rawMetadata, + } as Record // Statsig typings are unnecessarily strict here. + fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)' + Statsig.logEvent(eventName, null, fullMetadata) } export function useGate(gateName: string) { From 8c7f813d878cf76aa5e750db68f8bf365dbebfa5 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 13 Mar 2024 17:38:37 +0000 Subject: [PATCH 2/5] filter out files with non-image mime types --- src/lib/media/picker.shared.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/lib/media/picker.shared.ts b/src/lib/media/picker.shared.ts index 8bade34e..96e82e4c 100644 --- a/src/lib/media/picker.shared.ts +++ b/src/lib/media/picker.shared.ts @@ -18,11 +18,18 @@ export async function openPicker(opts?: ImagePickerOptions) { Toast.show('You may only select up to 4 images') } - return (response.assets ?? []).slice(0, 4).map(image => ({ - mime: 'image/jpeg', - height: image.height, - width: image.width, - path: image.uri, - size: getDataUriSize(image.uri), - })) + return (response.assets ?? []) + .slice(0, 4) + .filter(asset => { + if (asset.mimeType?.startsWith('image/')) return true + Toast.show('Only image files are supported') + return false + }) + .map(image => ({ + mime: 'image/jpeg', + height: image.height, + width: image.width, + path: image.uri, + size: getDataUriSize(image.uri), + })) } From db79c918b2e929c1317cbb91918eb00252fdc136 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 13 Mar 2024 12:06:03 -0700 Subject: [PATCH 3/5] 1.73 Version Bump (#3200) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6e17fcb0..89620182 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsky.app", - "version": "1.72.0", + "version": "1.73.0", "private": true, "engines": { "node": ">=18" From 7eaa573b57c6fb3a37abe105d4a8de10e9b9f893 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 13 Mar 2024 22:31:51 +0000 Subject: [PATCH 4/5] [Statsig] Track likes, reposts, follows (#3195) * [Statsig] Track likes * Move tracking to intent * Track repost/unrepost * Track profile follows/unfollows * Less copy paste * Reorder --- src/lib/statsig/events.ts | 32 ++++++++++++- src/lib/statsig/statsig.tsx | 8 ++-- src/state/queries/post.ts | 47 ++++++++++++++----- src/state/queries/profile.ts | 17 +++++-- .../onboarding/RecommendedFollowsItem.tsx | 7 ++- .../com/post-thread/PostThreadFollowBtn.tsx | 5 +- src/view/com/post-thread/PostThreadItem.tsx | 2 + src/view/com/post/Post.tsx | 1 + src/view/com/posts/FeedItem.tsx | 1 + src/view/com/profile/FollowButton.tsx | 7 ++- src/view/com/profile/ProfileCard.tsx | 4 +- src/view/com/profile/ProfileHeader.tsx | 5 +- .../profile/ProfileHeaderSuggestedFollows.tsx | 5 +- src/view/com/profile/ProfileMenu.tsx | 5 +- src/view/com/util/post-ctrls/PostCtrls.tsx | 9 +++- 15 files changed, 125 insertions(+), 30 deletions(-) diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index bc647710..321a0f68 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -1,5 +1,35 @@ -export type Events = { +export type LogEvents = { init: { initMs: number } + 'post:like': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'post:repost': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'post:unlike': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'post:unrepost': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'profile:follow': { + logContext: + | 'RecommendedFollowsItem' + | 'PostThreadItem' + | 'ProfileCard' + | 'ProfileHeader' + | 'ProfileHeaderSuggestedFollows' + | 'ProfileMenu' + } + 'profile:unfollow': { + logContext: + | 'RecommendedFollowsItem' + | 'PostThreadItem' + | 'ProfileCard' + | 'ProfileHeader' + | 'ProfileHeaderSuggestedFollows' + | 'ProfileMenu' + } } diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index a46cef4d..5745d204 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -6,7 +6,9 @@ import { } from 'statsig-react-native-expo' import {useSession} from '../../state/session' import {sha256} from 'js-sha256' -import {Events} from './events' +import {LogEvents} from './events' + +export type {LogEvents} const statsigOptions = { environment: { @@ -31,9 +33,9 @@ export function attachRouteToLogEvents( getCurrentRouteName = getRouteName } -export function logEvent( +export function logEvent( eventName: E & string, - rawMetadata?: Events[E] & FlatJSONRecord, + rawMetadata: LogEvents[E] & FlatJSONRecord, ) { const fullMetadata = { ...rawMetadata, diff --git a/src/state/queries/post.ts b/src/state/queries/post.ts index eb59f7da..e3682e30 100644 --- a/src/state/queries/post.ts +++ b/src/state/queries/post.ts @@ -5,6 +5,7 @@ import {Shadow} from '#/state/cache/types' import {getAgent} from '#/state/session' import {updatePostShadow} from '#/state/cache/post-shadow' import {track} from '#/lib/analytics/analytics' +import {logEvent, LogEvents} from '#/lib/statsig/statsig' import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue' export const RQKEY = (postUri: string) => ['post', postUri] @@ -58,12 +59,14 @@ export function useGetPost() { export function usePostLikeMutationQueue( post: Shadow, + logContext: LogEvents['post:like']['logContext'] & + LogEvents['post:unlike']['logContext'], ) { const postUri = post.uri const postCid = post.cid const initialLikeUri = post.viewer?.like - const likeMutation = usePostLikeMutation() - const unlikeMutation = usePostUnlikeMutation() + const likeMutation = usePostLikeMutation(logContext) + const unlikeMutation = usePostUnlikeMutation(logContext) const queueToggle = useToggleMutationQueue({ initialState: initialLikeUri, @@ -111,22 +114,30 @@ export function usePostLikeMutationQueue( return [queueLike, queueUnlike] } -function usePostLikeMutation() { +function usePostLikeMutation(logContext: LogEvents['post:like']['logContext']) { return useMutation< {uri: string}, // responds with the uri of the like Error, {uri: string; cid: string} // the post's uri and cid >({ - mutationFn: post => getAgent().like(post.uri, post.cid), + mutationFn: post => { + logEvent('post:like', {logContext}) + return getAgent().like(post.uri, post.cid) + }, onSuccess() { track('Post:Like') }, }) } -function usePostUnlikeMutation() { +function usePostUnlikeMutation( + logContext: LogEvents['post:unlike']['logContext'], +) { return useMutation({ - mutationFn: ({likeUri}) => getAgent().deleteLike(likeUri), + mutationFn: ({likeUri}) => { + logEvent('post:unlike', {logContext}) + return getAgent().deleteLike(likeUri) + }, onSuccess() { track('Post:Unlike') }, @@ -135,12 +146,14 @@ function usePostUnlikeMutation() { export function usePostRepostMutationQueue( post: Shadow, + logContext: LogEvents['post:repost']['logContext'] & + LogEvents['post:unrepost']['logContext'], ) { const postUri = post.uri const postCid = post.cid const initialRepostUri = post.viewer?.repost - const repostMutation = usePostRepostMutation() - const unrepostMutation = usePostUnrepostMutation() + const repostMutation = usePostRepostMutation(logContext) + const unrepostMutation = usePostUnrepostMutation(logContext) const queueToggle = useToggleMutationQueue({ initialState: initialRepostUri, @@ -188,22 +201,32 @@ export function usePostRepostMutationQueue( return [queueRepost, queueUnrepost] } -function usePostRepostMutation() { +function usePostRepostMutation( + logContext: LogEvents['post:repost']['logContext'], +) { return useMutation< {uri: string}, // responds with the uri of the repost Error, {uri: string; cid: string} // the post's uri and cid >({ - mutationFn: post => getAgent().repost(post.uri, post.cid), + mutationFn: post => { + logEvent('post:repost', {logContext}) + return getAgent().repost(post.uri, post.cid) + }, onSuccess() { track('Post:Repost') }, }) } -function usePostUnrepostMutation() { +function usePostUnrepostMutation( + logContext: LogEvents['post:unrepost']['logContext'], +) { return useMutation({ - mutationFn: ({repostUri}) => getAgent().deleteRepost(repostUri), + mutationFn: ({repostUri}) => { + logEvent('post:unrepost', {logContext}) + return getAgent().deleteRepost(repostUri) + }, onSuccess() { track('Post:Unrepost') }, diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index e81ea0f3..3c9e3e41 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -26,6 +26,7 @@ import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts' import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts' import {STALE} from '#/state/queries' import {track} from '#/lib/analytics/analytics' +import {logEvent, LogEvents} from '#/lib/statsig/statsig' import {ThreadNode} from './post-thread' export const RQKEY = (did: string) => ['profile', did] @@ -186,11 +187,13 @@ export function useProfileUpdateMutation() { export function useProfileFollowMutationQueue( profile: Shadow, + logContext: LogEvents['profile:follow']['logContext'] & + LogEvents['profile:unfollow']['logContext'], ) { const did = profile.did const initialFollowingUri = profile.viewer?.following - const followMutation = useProfileFollowMutation() - const unfollowMutation = useProfileUnfollowMutation() + const followMutation = useProfileFollowMutation(logContext) + const unfollowMutation = useProfileUnfollowMutation(logContext) const queueToggle = useToggleMutationQueue({ initialState: initialFollowingUri, @@ -237,9 +240,12 @@ export function useProfileFollowMutationQueue( return [queueFollow, queueUnfollow] } -function useProfileFollowMutation() { +function useProfileFollowMutation( + logContext: LogEvents['profile:follow']['logContext'], +) { return useMutation<{uri: string; cid: string}, Error, {did: string}>({ mutationFn: async ({did}) => { + logEvent('profile:follow', {logContext}) return await getAgent().follow(did) }, onSuccess(data, variables) { @@ -248,9 +254,12 @@ function useProfileFollowMutation() { }) } -function useProfileUnfollowMutation() { +function useProfileUnfollowMutation( + logContext: LogEvents['profile:unfollow']['logContext'], +) { return useMutation({ mutationFn: async ({followUri}) => { + logEvent('profile:unfollow', {logContext}) track('Profile:Unfollow', {username: followUri}) return await getAgent().deleteFollow(followUri) }, diff --git a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx index 07001068..5f81a4d6 100644 --- a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx +++ b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx @@ -56,7 +56,7 @@ export function RecommendedFollowsItem({ ) } -export function ProfileCard({ +function ProfileCard({ profile, onFollowStateChange, moderation, @@ -72,7 +72,10 @@ export function ProfileCard({ const pal = usePalette('default') const [addingMoreSuggestions, setAddingMoreSuggestions] = React.useState(false) - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'RecommendedFollowsItem', + ) const onToggleFollow = React.useCallback(async () => { try { diff --git a/src/view/com/post-thread/PostThreadFollowBtn.tsx b/src/view/com/post-thread/PostThreadFollowBtn.tsx index e5b747cc..45c3771f 100644 --- a/src/view/com/post-thread/PostThreadFollowBtn.tsx +++ b/src/view/com/post-thread/PostThreadFollowBtn.tsx @@ -42,7 +42,10 @@ function PostThreadFollowBtnLoaded({ const {isTabletOrDesktop} = useWebMediaQueries() const profile: Shadow = useProfileShadow(profileUnshadowed) - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'PostThreadItem', + ) const requireAuth = useRequireAuth() const isFollowing = !!profile.viewer?.following diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 9522ea6a..7efd535f 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -407,6 +407,7 @@ let PostThreadItemLoaded = ({ record={record} richText={richText} onPressReply={onPressReply} + logContext="PostThreadItem" /> @@ -560,6 +561,7 @@ let PostThreadItemLoaded = ({ record={record} richText={richText} onPressReply={onPressReply} + logContext="PostThreadItem" /> diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 5fa4da84..0fe3420b 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -220,6 +220,7 @@ function PostInner({ record={record} richText={richText} onPressReply={onPressReply} + logContext="Post" /> diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 7d29703e..4b78dce7 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -310,6 +310,7 @@ let FeedItemInner = ({ showAppealLabelItem={ post.author.did === currentAccount?.did && isModeratedPost } + logContext="FeedItem" /> diff --git a/src/view/com/profile/FollowButton.tsx b/src/view/com/profile/FollowButton.tsx index 9cc635b6..7b090ffe 100644 --- a/src/view/com/profile/FollowButton.tsx +++ b/src/view/com/profile/FollowButton.tsx @@ -13,13 +13,18 @@ export function FollowButton({ followedType = 'default', profile, labelStyle, + logContext, }: { unfollowedType?: ButtonType followedType?: ButtonType profile: Shadow labelStyle?: StyleProp + logContext: 'ProfileCard' }) { - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + logContext, + ) const {_} = useLingui() const onPressFollow = async () => { diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 266adc51..019e6c10 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -230,7 +230,9 @@ export function ProfileCardWithFollowBtn({ renderButton={ isMe ? undefined - : profileShadow => + : profileShadow => ( + + ) } /> ) diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index 75e06eb9..17dc5ce1 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -103,7 +103,10 @@ let ProfileHeader = ({ const invalidHandle = isInvalidHandle(profile.handle) const {isDesktop} = useWebMediaQueries() const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false) - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'ProfileHeader', + ) const [__, queueUnblock] = useProfileBlockMutationQueue(profile) const unblockPromptControl = Prompt.usePromptControl() const moderation = useMemo( diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx index 6edc61fc..8f2c8949 100644 --- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx +++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx @@ -170,7 +170,10 @@ function SuggestedFollow({ const pal = usePalette('default') const moderationOpts = useModerationOpts() const profile = useProfileShadow(profileUnshadowed) - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'ProfileHeaderSuggestedFollows', + ) const onPressFollow = React.useCallback(async () => { try { diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index 4153b819..7c6db1ed 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -52,7 +52,10 @@ let ProfileMenu = ({ const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile) const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) - const [, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'ProfileMenu', + ) const blockPromptControl = Prompt.usePromptControl() diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 1e26eecc..c96954a1 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -44,6 +44,7 @@ let PostCtrls = ({ showAppealLabelItem, style, onPressReply, + logContext, }: { big?: boolean post: Shadow @@ -52,13 +53,17 @@ let PostCtrls = ({ showAppealLabelItem?: boolean style?: StyleProp onPressReply: () => void + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' }): React.ReactNode => { const theme = useTheme() const {_} = useLingui() const {openComposer} = useComposerControls() const {closeModal} = useModalControls() - const [queueLike, queueUnlike] = usePostLikeMutationQueue(post) - const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(post) + const [queueLike, queueUnlike] = usePostLikeMutationQueue(post, logContext) + const [queueRepost, queueUnrepost] = usePostRepostMutationQueue( + post, + logContext, + ) const requireAuth = useRequireAuth() const defaultCtrlColor = React.useMemo( From 1c25c76645564c4581530ec604b7862bb3b8fdda Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 14 Mar 2024 00:21:42 +0000 Subject: [PATCH 5/5] [Statsig] Track posting, end reached (#3206) * Track post create * Track feed endReached --- src/lib/statsig/events.ts | 12 ++++++++++++ src/view/com/composer/Composer.tsx | 11 +++++++++++ src/view/com/posts/Feed.tsx | 16 +++++++++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index 321a0f68..fa7e597f 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -2,6 +2,18 @@ export type LogEvents = { init: { initMs: number } + 'feed:endReached': { + feedType: string + itemCount: number + } + 'post:create': { + imageCount: number + isReply: boolean + hasLink: boolean + hasQuote: boolean + langs: string + logContext: 'Composer' + } 'post:like': { logContext: 'FeedItem' | 'PostThreadItem' | 'Post' } diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index ef965b27..97f8e519 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -65,6 +65,7 @@ import {logger} from '#/logger' import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo' import * as Prompt from '#/components/Prompt' import {useDialogStateControlContext} from 'state/dialogs' +import {logEvent} from '#/lib/statsig/statsig' type Props = ComposerOpts export const ComposePost = observer(function ComposePost({ @@ -255,6 +256,16 @@ export const ComposePost = observer(function ComposePost({ setIsProcessing(false) return } finally { + if (postUri) { + logEvent('post:create', { + imageCount: gallery.size, + isReply: replyTo != null, + hasLink: extLink != null, + hasQuote: quote != null, + langs: langPrefs.postLanguage, + logContext: 'Composer', + }) + } track('Create Post', { imageCount: gallery.size, }) diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index cd3e9878..b86646a4 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -33,6 +33,7 @@ import {useLingui} from '@lingui/react' import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home' import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' +import {logEvent} from '#/lib/statsig/statsig' const LOADING_ITEM = {_reactKey: '__loading__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -223,16 +224,29 @@ let Feed = ({ setIsPTRing(false) }, [refetch, track, setIsPTRing, onHasNew]) + const feedType = feed.split('|')[0] const onEndReached = React.useCallback(async () => { if (isFetching || !hasNextPage || isError) return + logEvent('feed:endReached', { + feedType: feedType, + itemCount: feedItems.length, + }) track('Feed:onEndReached') try { await fetchNextPage() } catch (err) { logger.error('Failed to load more posts', {message: err}) } - }, [isFetching, hasNextPage, isError, fetchNextPage, track]) + }, [ + isFetching, + hasNextPage, + isError, + fetchNextPage, + track, + feedType, + feedItems.length, + ]) const onPressTryAgain = React.useCallback(() => { refetch()