my fork of the bluesky client
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[Statsig] Remove client downsampling (#6153)

authored by hailey.at and committed by

GitHub 3bd14371 400c4322

+57 -99
+1 -1
src/Navigation.tsx
··· 696 696 onStateChange={() => { 697 697 const routeName = getCurrentRouteName() 698 698 if (routeName === 'Notifications') { 699 - logEvent('router:navigate:notifications:sampled', {}) 699 + logEvent('router:navigate:notifications', {}) 700 700 } 701 701 }} 702 702 onReady={() => {
+2 -2
src/components/ProfileCard.tsx
··· 283 283 export type FollowButtonProps = { 284 284 profile: AppBskyActorDefs.ProfileViewBasic 285 285 moderationOpts: ModerationOpts 286 - logContext: LogEvents['profile:follow:sampled']['logContext'] & 287 - LogEvents['profile:unfollow:sampled']['logContext'] 286 + logContext: LogEvents['profile:follow']['logContext'] & 287 + LogEvents['profile:unfollow']['logContext'] 288 288 } & Partial<ButtonProps> 289 289 290 290 export function FollowButton(props: FollowButtonProps) {
+2 -2
src/components/hooks/useFollowMethods.ts
··· 15 15 logContext, 16 16 }: { 17 17 profile: Shadow<AppBskyActorDefs.ProfileViewBasic> 18 - logContext: LogEvents['profile:follow:sampled']['logContext'] & 19 - LogEvents['profile:unfollow:sampled']['logContext'] 18 + logContext: LogEvents['profile:follow']['logContext'] & 19 + LogEvents['profile:unfollow']['logContext'] 20 20 }) { 21 21 const {_} = useLingui() 22 22 const requireAuth = useRequireAuth()
+15 -15
src/lib/statsig/events.ts
··· 21 21 context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home' 22 22 status: 'granted' | 'denied' | 'undetermined' 23 23 } 24 - 'state:background:sampled': { 24 + 'state:background': { 25 25 secondsActive: number 26 26 } 27 - 'state:foreground:sampled': {} 28 - 'router:navigate:notifications:sampled': {} 27 + 'state:foreground': {} 28 + 'router:navigate:notifications': {} 29 29 'deepLink:referrerReceived': { 30 30 to: string 31 31 referrer: string ··· 76 76 'onboarding:finished:avatarResult': { 77 77 avatarResult: 'default' | 'created' | 'uploaded' 78 78 } 79 - 'home:feedDisplayed:sampled': { 79 + 'home:feedDisplayed': { 80 80 feedUrl: string 81 81 feedType: string 82 82 index: number ··· 87 87 | 'desktop-sidebar-click' 88 88 | 'starter-pack-initial-feed' 89 89 } 90 - 'feed:endReached:sampled': { 90 + 'feed:endReached': { 91 91 feedUrl: string 92 92 feedType: string 93 93 itemCount: number 94 94 } 95 - 'feed:refresh:sampled': { 95 + 'feed:refresh': { 96 96 feedUrl: string 97 97 feedType: string 98 98 reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest' ··· 103 103 'discover:showLess': { 104 104 feedContext: string 105 105 } 106 - 'discover:clickthrough:sampled': { 106 + 'discover:clickthrough': { 107 107 count: number 108 108 } 109 - 'discover:engaged:sampled': { 109 + 'discover:engaged': { 110 110 count: number 111 111 } 112 - 'discover:seen:sampled': { 112 + 'discover:seen': { 113 113 count: number 114 114 } 115 115 ··· 132 132 postCount: number 133 133 isReply: boolean 134 134 } 135 - 'post:like:sampled': { 135 + 'post:like': { 136 136 doesLikerFollowPoster: boolean | undefined 137 137 doesPosterFollowLiker: boolean | undefined 138 138 likerClout: number | undefined 139 139 postClout: number | undefined 140 140 logContext: 'FeedItem' | 'PostThreadItem' | 'Post' 141 141 } 142 - 'post:repost:sampled': { 142 + 'post:repost': { 143 143 logContext: 'FeedItem' | 'PostThreadItem' | 'Post' 144 144 } 145 - 'post:unlike:sampled': { 145 + 'post:unlike': { 146 146 logContext: 'FeedItem' | 'PostThreadItem' | 'Post' 147 147 } 148 - 'post:unrepost:sampled': { 148 + 'post:unrepost': { 149 149 logContext: 'FeedItem' | 'PostThreadItem' | 'Post' 150 150 } 151 151 'post:mute': {} 152 152 'post:unmute': {} 153 153 'post:pin': {} 154 154 'post:unpin': {} 155 - 'profile:follow:sampled': { 155 + 'profile:follow': { 156 156 didBecomeMutual: boolean | undefined 157 157 followeeClout: number | undefined 158 158 followerClout: number | undefined ··· 169 169 | 'FeedInterstitial' 170 170 | 'ProfileHeaderSuggestedFollows' 171 171 } 172 - 'profile:unfollow:sampled': { 172 + 'profile:unfollow': { 173 173 logContext: 174 174 | 'RecommendedFollowsItem' 175 175 | 'PostThreadItem'
+3 -39
src/lib/statsig/statsig.tsx
··· 59 59 initTimeoutMs: 1, 60 60 // Get fresh flags for other accounts as well, if any. 61 61 prefetchUsers, 62 + api: 'https://events.bsky.app/v2', 62 63 } 63 64 } 64 65 ··· 89 90 } 90 91 } 91 92 92 - const DOWNSAMPLE_RATE = 0.99 // 99% likely 93 - const DOWNSAMPLED_EVENTS: Set<keyof LogEvents> = new Set([ 94 - 'router:navigate:notifications:sampled', 95 - 'state:background:sampled', 96 - 'state:foreground:sampled', 97 - 'home:feedDisplayed:sampled', 98 - 'feed:endReached:sampled', 99 - 'feed:refresh:sampled', 100 - 'discover:clickthrough:sampled', 101 - 'discover:engaged:sampled', 102 - 'discover:seen:sampled', 103 - 'post:like:sampled', 104 - 'post:unlike:sampled', 105 - 'post:repost:sampled', 106 - 'post:unrepost:sampled', 107 - 'profile:follow:sampled', 108 - 'profile:unfollow:sampled', 109 - ]) 110 - const isDownsampledSession = Math.random() < DOWNSAMPLE_RATE 111 - 112 93 export function logEvent<E extends keyof LogEvents>( 113 94 eventName: E & string, 114 95 rawMetadata: LogEvents[E] & FlatJSONRecord, 115 96 ) { 116 97 try { 117 - if ( 118 - process.env.NODE_ENV === 'development' && 119 - eventName.endsWith(':sampled') && 120 - !DOWNSAMPLED_EVENTS.has(eventName) 121 - ) { 122 - logger.error( 123 - 'Did you forget to add ' + eventName + ' to DOWNSAMPLED_EVENTS?', 124 - ) 125 - } 126 - 127 - const isDownsampledEvent = DOWNSAMPLED_EVENTS.has(eventName) 128 - if (isDownsampledSession && isDownsampledEvent) { 129 - return 130 - } 131 98 const fullMetadata = { 132 99 ...rawMetadata, 133 100 } as Record<string, string> // Statsig typings are unnecessarily strict here. 134 - if (isDownsampledEvent) { 135 - fullMetadata.downsampleRate = DOWNSAMPLE_RATE.toString() 136 - } 137 101 fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)' 138 102 if (Statsig.initializeCalled()) { 139 103 Statsig.logEvent(eventName, null, fullMetadata) ··· 232 196 lastState = state 233 197 if (state === 'active') { 234 198 lastActive = performance.now() 235 - logEvent('state:foreground:sampled', {}) 199 + logEvent('state:foreground', {}) 236 200 } else { 237 201 let secondsActive = 0 238 202 if (lastActive != null) { 239 203 secondsActive = Math.round((performance.now() - lastActive) / 1e3) 240 204 lastActive = null 241 - logEvent('state:background:sampled', { 205 + logEvent('state:background', { 242 206 secondsActive, 243 207 }) 244 208 }
+3 -3
src/state/feed-feedback.tsx
··· 234 234 } 235 235 236 236 if (stats.clickthroughCount > 0) { 237 - logEvent('discover:clickthrough:sampled', { 237 + logEvent('discover:clickthrough', { 238 238 count: stats.clickthroughCount, 239 239 }) 240 240 stats.clickthroughCount = 0 241 241 } 242 242 243 243 if (stats.engagedCount > 0) { 244 - logEvent('discover:engaged:sampled', { 244 + logEvent('discover:engaged', { 245 245 count: stats.engagedCount, 246 246 }) 247 247 stats.engagedCount = 0 248 248 } 249 249 250 250 if (stats.seenCount > 0) { 251 - logEvent('discover:seen:sampled', { 251 + logEvent('discover:seen', { 252 252 count: stats.seenCount, 253 253 }) 254 254 stats.seenCount = 0
+12 -12
src/state/queries/post.ts
··· 98 98 99 99 export function usePostLikeMutationQueue( 100 100 post: Shadow<AppBskyFeedDefs.PostView>, 101 - logContext: LogEvents['post:like:sampled']['logContext'] & 102 - LogEvents['post:unlike:sampled']['logContext'], 101 + logContext: LogEvents['post:like']['logContext'] & 102 + LogEvents['post:unlike']['logContext'], 103 103 ) { 104 104 const queryClient = useQueryClient() 105 105 const postUri = post.uri ··· 157 157 } 158 158 159 159 function usePostLikeMutation( 160 - logContext: LogEvents['post:like:sampled']['logContext'], 160 + logContext: LogEvents['post:like']['logContext'], 161 161 post: Shadow<AppBskyFeedDefs.PostView>, 162 162 ) { 163 163 const {currentAccount} = useSession() ··· 174 174 if (currentAccount) { 175 175 ownProfile = findProfileQueryData(queryClient, currentAccount.did) 176 176 } 177 - logEvent('post:like:sampled', { 177 + logEvent('post:like', { 178 178 logContext, 179 179 doesPosterFollowLiker: postAuthor.viewer 180 180 ? Boolean(postAuthor.viewer.followedBy) ··· 196 196 } 197 197 198 198 function usePostUnlikeMutation( 199 - logContext: LogEvents['post:unlike:sampled']['logContext'], 199 + logContext: LogEvents['post:unlike']['logContext'], 200 200 ) { 201 201 const agent = useAgent() 202 202 return useMutation<void, Error, {postUri: string; likeUri: string}>({ 203 203 mutationFn: ({likeUri}) => { 204 - logEvent('post:unlike:sampled', {logContext}) 204 + logEvent('post:unlike', {logContext}) 205 205 return agent.deleteLike(likeUri) 206 206 }, 207 207 }) ··· 209 209 210 210 export function usePostRepostMutationQueue( 211 211 post: Shadow<AppBskyFeedDefs.PostView>, 212 - logContext: LogEvents['post:repost:sampled']['logContext'] & 213 - LogEvents['post:unrepost:sampled']['logContext'], 212 + logContext: LogEvents['post:repost']['logContext'] & 213 + LogEvents['post:unrepost']['logContext'], 214 214 ) { 215 215 const queryClient = useQueryClient() 216 216 const postUri = post.uri ··· 266 266 } 267 267 268 268 function usePostRepostMutation( 269 - logContext: LogEvents['post:repost:sampled']['logContext'], 269 + logContext: LogEvents['post:repost']['logContext'], 270 270 ) { 271 271 const agent = useAgent() 272 272 return useMutation< ··· 275 275 {uri: string; cid: string} // the post's uri and cid 276 276 >({ 277 277 mutationFn: post => { 278 - logEvent('post:repost:sampled', {logContext}) 278 + logEvent('post:repost', {logContext}) 279 279 return agent.repost(post.uri, post.cid) 280 280 }, 281 281 }) 282 282 } 283 283 284 284 function usePostUnrepostMutation( 285 - logContext: LogEvents['post:unrepost:sampled']['logContext'], 285 + logContext: LogEvents['post:unrepost']['logContext'], 286 286 ) { 287 287 const agent = useAgent() 288 288 return useMutation<void, Error, {postUri: string; repostUri: string}>({ 289 289 mutationFn: ({repostUri}) => { 290 - logEvent('post:unrepost:sampled', {logContext}) 290 + logEvent('post:unrepost', {logContext}) 291 291 return agent.deleteRepost(repostUri) 292 292 }, 293 293 })
+6 -6
src/state/queries/profile.ts
··· 221 221 222 222 export function useProfileFollowMutationQueue( 223 223 profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, 224 - logContext: LogEvents['profile:follow:sampled']['logContext'] & 225 - LogEvents['profile:follow:sampled']['logContext'], 224 + logContext: LogEvents['profile:follow']['logContext'] & 225 + LogEvents['profile:follow']['logContext'], 226 226 ) { 227 227 const agent = useAgent() 228 228 const queryClient = useQueryClient() ··· 293 293 } 294 294 295 295 function useProfileFollowMutation( 296 - logContext: LogEvents['profile:follow:sampled']['logContext'], 296 + logContext: LogEvents['profile:follow']['logContext'], 297 297 profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, 298 298 ) { 299 299 const {currentAccount} = useSession() ··· 308 308 ownProfile = findProfileQueryData(queryClient, currentAccount.did) 309 309 } 310 310 captureAction(ProgressGuideAction.Follow) 311 - logEvent('profile:follow:sampled', { 311 + logEvent('profile:follow', { 312 312 logContext, 313 313 didBecomeMutual: profile.viewer 314 314 ? Boolean(profile.viewer.followedBy) ··· 322 322 } 323 323 324 324 function useProfileUnfollowMutation( 325 - logContext: LogEvents['profile:unfollow:sampled']['logContext'], 325 + logContext: LogEvents['profile:unfollow']['logContext'], 326 326 ) { 327 327 const agent = useAgent() 328 328 return useMutation<void, Error, {did: string; followUri: string}>({ 329 329 mutationFn: async ({followUri}) => { 330 - logEvent('profile:unfollow:sampled', {logContext}) 330 + logEvent('profile:unfollow', {logContext}) 331 331 return await agent.deleteFollow(followUri) 332 332 }, 333 333 })
+2 -2
src/view/com/feeds/FeedPage.tsx
··· 74 74 scrollToTop() 75 75 truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) 76 76 setHasNew(false) 77 - logEvent('feed:refresh:sampled', { 77 + logEvent('feed:refresh', { 78 78 feedType: feed.split('|')[0], 79 79 feedUrl: feed, 80 80 reason: 'soft-reset', ··· 98 98 scrollToTop() 99 99 truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) 100 100 setHasNew(false) 101 - logEvent('feed:refresh:sampled', { 101 + logEvent('feed:refresh', { 102 102 feedType: feed.split('|')[0], 103 103 feedUrl: feed, 104 104 reason: 'load-latest',
+3 -3
src/view/com/pager/Pager.tsx
··· 15 15 export interface PagerRef { 16 16 setPage: ( 17 17 index: number, 18 - reason: LogEvents['home:feedDisplayed:sampled']['reason'], 18 + reason: LogEvents['home:feedDisplayed']['reason'], 19 19 ) => void 20 20 } 21 21 ··· 32 32 onPageSelected?: (index: number) => void 33 33 onPageSelecting?: ( 34 34 index: number, 35 - reason: LogEvents['home:feedDisplayed:sampled']['reason'], 35 + reason: LogEvents['home:feedDisplayed']['reason'], 36 36 ) => void 37 37 onPageScrollStateChanged?: ( 38 38 scrollState: 'idle' | 'dragging' | 'settling', ··· 61 61 React.useImperativeHandle(ref, () => ({ 62 62 setPage: ( 63 63 index: number, 64 - reason: LogEvents['home:feedDisplayed:sampled']['reason'], 64 + reason: LogEvents['home:feedDisplayed']['reason'], 65 65 ) => { 66 66 pagerView.current?.setPage(index) 67 67 onPageSelecting?.(index, reason)
+3 -6
src/view/com/pager/Pager.web.tsx
··· 18 18 onPageSelected?: (index: number) => void 19 19 onPageSelecting?: ( 20 20 index: number, 21 - reason: LogEvents['home:feedDisplayed:sampled']['reason'], 21 + reason: LogEvents['home:feedDisplayed']['reason'], 22 22 ) => void 23 23 } 24 24 export const Pager = React.forwardRef(function PagerImpl( ··· 38 38 React.useImperativeHandle(ref, () => ({ 39 39 setPage: ( 40 40 index: number, 41 - reason: LogEvents['home:feedDisplayed:sampled']['reason'], 41 + reason: LogEvents['home:feedDisplayed']['reason'], 42 42 ) => { 43 43 onTabBarSelect(index, reason) 44 44 }, 45 45 })) 46 46 47 47 const onTabBarSelect = React.useCallback( 48 - ( 49 - index: number, 50 - reason: LogEvents['home:feedDisplayed:sampled']['reason'], 51 - ) => { 48 + (index: number, reason: LogEvents['home:feedDisplayed']['reason']) => { 52 49 const scrollY = window.scrollY 53 50 // We want to determine if the tabbar is already "sticking" at the top (in which 54 51 // case we should preserve and restore scroll), or if it is somewhere below in the
+2 -2
src/view/com/posts/Feed.tsx
··· 403 403 // = 404 404 405 405 const onRefresh = React.useCallback(async () => { 406 - logEvent('feed:refresh:sampled', { 406 + logEvent('feed:refresh', { 407 407 feedType: feedType, 408 408 feedUrl: feed, 409 409 reason: 'pull-to-refresh', ··· 421 421 const onEndReached = React.useCallback(async () => { 422 422 if (isFetching || !hasNextPage || isError) return 423 423 424 - logEvent('feed:endReached:sampled', { 424 + logEvent('feed:endReached', { 425 425 feedType: feedType, 426 426 feedUrl: feed, 427 427 itemCount: feedItems.length,
+3 -6
src/view/screens/Home.tsx
··· 141 141 useFocusEffect( 142 142 useNonReactiveCallback(() => { 143 143 if (selectedFeed) { 144 - logEvent('home:feedDisplayed:sampled', { 144 + logEvent('home:feedDisplayed', { 145 145 index: selectedIndex, 146 146 feedType: selectedFeed.split('|')[0], 147 147 feedUrl: selectedFeed, ··· 163 163 ) 164 164 165 165 const onPageSelecting = React.useCallback( 166 - ( 167 - index: number, 168 - reason: LogEvents['home:feedDisplayed:sampled']['reason'], 169 - ) => { 166 + (index: number, reason: LogEvents['home:feedDisplayed']['reason']) => { 170 167 const feed = allFeeds[index] 171 - logEvent('home:feedDisplayed:sampled', { 168 + logEvent('home:feedDisplayed', { 172 169 index, 173 170 feedType: feed.split('|')[0], 174 171 feedUrl: feed,