[Statsig] Remove client downsampling (#6153)

This commit is contained in:
Hailey
2024-11-15 11:40:43 -08:00
committed by GitHub
parent 400c432283
commit 3bd1437133
13 changed files with 57 additions and 99 deletions
+1 -1
View File
@@ -696,7 +696,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
onStateChange={() => { onStateChange={() => {
const routeName = getCurrentRouteName() const routeName = getCurrentRouteName()
if (routeName === 'Notifications') { if (routeName === 'Notifications') {
logEvent('router:navigate:notifications:sampled', {}) logEvent('router:navigate:notifications', {})
} }
}} }}
onReady={() => { onReady={() => {
+2 -2
View File
@@ -283,8 +283,8 @@ export function DescriptionPlaceholder({
export type FollowButtonProps = { export type FollowButtonProps = {
profile: AppBskyActorDefs.ProfileViewBasic profile: AppBskyActorDefs.ProfileViewBasic
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
logContext: LogEvents['profile:follow:sampled']['logContext'] & logContext: LogEvents['profile:follow']['logContext'] &
LogEvents['profile:unfollow:sampled']['logContext'] LogEvents['profile:unfollow']['logContext']
} & Partial<ButtonProps> } & Partial<ButtonProps>
export function FollowButton(props: FollowButtonProps) { export function FollowButton(props: FollowButtonProps) {
+2 -2
View File
@@ -15,8 +15,8 @@ export function useFollowMethods({
logContext, logContext,
}: { }: {
profile: Shadow<AppBskyActorDefs.ProfileViewBasic> profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
logContext: LogEvents['profile:follow:sampled']['logContext'] & logContext: LogEvents['profile:follow']['logContext'] &
LogEvents['profile:unfollow:sampled']['logContext'] LogEvents['profile:unfollow']['logContext']
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const requireAuth = useRequireAuth() const requireAuth = useRequireAuth()
+15 -15
View File
@@ -21,11 +21,11 @@ export type LogEvents = {
context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home' context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home'
status: 'granted' | 'denied' | 'undetermined' status: 'granted' | 'denied' | 'undetermined'
} }
'state:background:sampled': { 'state:background': {
secondsActive: number secondsActive: number
} }
'state:foreground:sampled': {} 'state:foreground': {}
'router:navigate:notifications:sampled': {} 'router:navigate:notifications': {}
'deepLink:referrerReceived': { 'deepLink:referrerReceived': {
to: string to: string
referrer: string referrer: string
@@ -76,7 +76,7 @@ export type LogEvents = {
'onboarding:finished:avatarResult': { 'onboarding:finished:avatarResult': {
avatarResult: 'default' | 'created' | 'uploaded' avatarResult: 'default' | 'created' | 'uploaded'
} }
'home:feedDisplayed:sampled': { 'home:feedDisplayed': {
feedUrl: string feedUrl: string
feedType: string feedType: string
index: number index: number
@@ -87,12 +87,12 @@ export type LogEvents = {
| 'desktop-sidebar-click' | 'desktop-sidebar-click'
| 'starter-pack-initial-feed' | 'starter-pack-initial-feed'
} }
'feed:endReached:sampled': { 'feed:endReached': {
feedUrl: string feedUrl: string
feedType: string feedType: string
itemCount: number itemCount: number
} }
'feed:refresh:sampled': { 'feed:refresh': {
feedUrl: string feedUrl: string
feedType: string feedType: string
reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest' reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest'
@@ -103,13 +103,13 @@ export type LogEvents = {
'discover:showLess': { 'discover:showLess': {
feedContext: string feedContext: string
} }
'discover:clickthrough:sampled': { 'discover:clickthrough': {
count: number count: number
} }
'discover:engaged:sampled': { 'discover:engaged': {
count: number count: number
} }
'discover:seen:sampled': { 'discover:seen': {
count: number count: number
} }
@@ -132,27 +132,27 @@ export type LogEvents = {
postCount: number postCount: number
isReply: boolean isReply: boolean
} }
'post:like:sampled': { 'post:like': {
doesLikerFollowPoster: boolean | undefined doesLikerFollowPoster: boolean | undefined
doesPosterFollowLiker: boolean | undefined doesPosterFollowLiker: boolean | undefined
likerClout: number | undefined likerClout: number | undefined
postClout: number | undefined postClout: number | undefined
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
} }
'post:repost:sampled': { 'post:repost': {
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
} }
'post:unlike:sampled': { 'post:unlike': {
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
} }
'post:unrepost:sampled': { 'post:unrepost': {
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
} }
'post:mute': {} 'post:mute': {}
'post:unmute': {} 'post:unmute': {}
'post:pin': {} 'post:pin': {}
'post:unpin': {} 'post:unpin': {}
'profile:follow:sampled': { 'profile:follow': {
didBecomeMutual: boolean | undefined didBecomeMutual: boolean | undefined
followeeClout: number | undefined followeeClout: number | undefined
followerClout: number | undefined followerClout: number | undefined
@@ -169,7 +169,7 @@ export type LogEvents = {
| 'FeedInterstitial' | 'FeedInterstitial'
| 'ProfileHeaderSuggestedFollows' | 'ProfileHeaderSuggestedFollows'
} }
'profile:unfollow:sampled': { 'profile:unfollow': {
logContext: logContext:
| 'RecommendedFollowsItem' | 'RecommendedFollowsItem'
| 'PostThreadItem' | 'PostThreadItem'
+3 -39
View File
@@ -59,6 +59,7 @@ function createStatsigOptions(prefetchUsers: StatsigUser[]) {
initTimeoutMs: 1, initTimeoutMs: 1,
// Get fresh flags for other accounts as well, if any. // Get fresh flags for other accounts as well, if any.
prefetchUsers, prefetchUsers,
api: 'https://events.bsky.app/v2',
} }
} }
@@ -89,51 +90,14 @@ export function toClout(n: number | null | undefined): number | undefined {
} }
} }
const DOWNSAMPLE_RATE = 0.99 // 99% likely
const DOWNSAMPLED_EVENTS: Set<keyof LogEvents> = new Set([
'router:navigate:notifications:sampled',
'state:background:sampled',
'state:foreground:sampled',
'home:feedDisplayed:sampled',
'feed:endReached:sampled',
'feed:refresh:sampled',
'discover:clickthrough:sampled',
'discover:engaged:sampled',
'discover:seen:sampled',
'post:like:sampled',
'post:unlike:sampled',
'post:repost:sampled',
'post:unrepost:sampled',
'profile:follow:sampled',
'profile:unfollow:sampled',
])
const isDownsampledSession = Math.random() < DOWNSAMPLE_RATE
export function logEvent<E extends keyof LogEvents>( export function logEvent<E extends keyof LogEvents>(
eventName: E & string, eventName: E & string,
rawMetadata: LogEvents[E] & FlatJSONRecord, rawMetadata: LogEvents[E] & FlatJSONRecord,
) { ) {
try { try {
if (
process.env.NODE_ENV === 'development' &&
eventName.endsWith(':sampled') &&
!DOWNSAMPLED_EVENTS.has(eventName)
) {
logger.error(
'Did you forget to add ' + eventName + ' to DOWNSAMPLED_EVENTS?',
)
}
const isDownsampledEvent = DOWNSAMPLED_EVENTS.has(eventName)
if (isDownsampledSession && isDownsampledEvent) {
return
}
const fullMetadata = { const fullMetadata = {
...rawMetadata, ...rawMetadata,
} as Record<string, string> // Statsig typings are unnecessarily strict here. } as Record<string, string> // Statsig typings are unnecessarily strict here.
if (isDownsampledEvent) {
fullMetadata.downsampleRate = DOWNSAMPLE_RATE.toString()
}
fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)' fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)'
if (Statsig.initializeCalled()) { if (Statsig.initializeCalled()) {
Statsig.logEvent(eventName, null, fullMetadata) Statsig.logEvent(eventName, null, fullMetadata)
@@ -232,13 +196,13 @@ AppState.addEventListener('change', (state: AppStateStatus) => {
lastState = state lastState = state
if (state === 'active') { if (state === 'active') {
lastActive = performance.now() lastActive = performance.now()
logEvent('state:foreground:sampled', {}) logEvent('state:foreground', {})
} else { } else {
let secondsActive = 0 let secondsActive = 0
if (lastActive != null) { if (lastActive != null) {
secondsActive = Math.round((performance.now() - lastActive) / 1e3) secondsActive = Math.round((performance.now() - lastActive) / 1e3)
lastActive = null lastActive = null
logEvent('state:background:sampled', { logEvent('state:background', {
secondsActive, secondsActive,
}) })
} }
+3 -3
View File
@@ -234,21 +234,21 @@ function flushToStatsig(stats: AggregatedStats | null) {
} }
if (stats.clickthroughCount > 0) { if (stats.clickthroughCount > 0) {
logEvent('discover:clickthrough:sampled', { logEvent('discover:clickthrough', {
count: stats.clickthroughCount, count: stats.clickthroughCount,
}) })
stats.clickthroughCount = 0 stats.clickthroughCount = 0
} }
if (stats.engagedCount > 0) { if (stats.engagedCount > 0) {
logEvent('discover:engaged:sampled', { logEvent('discover:engaged', {
count: stats.engagedCount, count: stats.engagedCount,
}) })
stats.engagedCount = 0 stats.engagedCount = 0
} }
if (stats.seenCount > 0) { if (stats.seenCount > 0) {
logEvent('discover:seen:sampled', { logEvent('discover:seen', {
count: stats.seenCount, count: stats.seenCount,
}) })
stats.seenCount = 0 stats.seenCount = 0
+12 -12
View File
@@ -98,8 +98,8 @@ export function useGetPosts() {
export function usePostLikeMutationQueue( export function usePostLikeMutationQueue(
post: Shadow<AppBskyFeedDefs.PostView>, post: Shadow<AppBskyFeedDefs.PostView>,
logContext: LogEvents['post:like:sampled']['logContext'] & logContext: LogEvents['post:like']['logContext'] &
LogEvents['post:unlike:sampled']['logContext'], LogEvents['post:unlike']['logContext'],
) { ) {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const postUri = post.uri const postUri = post.uri
@@ -157,7 +157,7 @@ export function usePostLikeMutationQueue(
} }
function usePostLikeMutation( function usePostLikeMutation(
logContext: LogEvents['post:like:sampled']['logContext'], logContext: LogEvents['post:like']['logContext'],
post: Shadow<AppBskyFeedDefs.PostView>, post: Shadow<AppBskyFeedDefs.PostView>,
) { ) {
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -174,7 +174,7 @@ function usePostLikeMutation(
if (currentAccount) { if (currentAccount) {
ownProfile = findProfileQueryData(queryClient, currentAccount.did) ownProfile = findProfileQueryData(queryClient, currentAccount.did)
} }
logEvent('post:like:sampled', { logEvent('post:like', {
logContext, logContext,
doesPosterFollowLiker: postAuthor.viewer doesPosterFollowLiker: postAuthor.viewer
? Boolean(postAuthor.viewer.followedBy) ? Boolean(postAuthor.viewer.followedBy)
@@ -196,12 +196,12 @@ function usePostLikeMutation(
} }
function usePostUnlikeMutation( function usePostUnlikeMutation(
logContext: LogEvents['post:unlike:sampled']['logContext'], logContext: LogEvents['post:unlike']['logContext'],
) { ) {
const agent = useAgent() const agent = useAgent()
return useMutation<void, Error, {postUri: string; likeUri: string}>({ return useMutation<void, Error, {postUri: string; likeUri: string}>({
mutationFn: ({likeUri}) => { mutationFn: ({likeUri}) => {
logEvent('post:unlike:sampled', {logContext}) logEvent('post:unlike', {logContext})
return agent.deleteLike(likeUri) return agent.deleteLike(likeUri)
}, },
}) })
@@ -209,8 +209,8 @@ function usePostUnlikeMutation(
export function usePostRepostMutationQueue( export function usePostRepostMutationQueue(
post: Shadow<AppBskyFeedDefs.PostView>, post: Shadow<AppBskyFeedDefs.PostView>,
logContext: LogEvents['post:repost:sampled']['logContext'] & logContext: LogEvents['post:repost']['logContext'] &
LogEvents['post:unrepost:sampled']['logContext'], LogEvents['post:unrepost']['logContext'],
) { ) {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const postUri = post.uri const postUri = post.uri
@@ -266,7 +266,7 @@ export function usePostRepostMutationQueue(
} }
function usePostRepostMutation( function usePostRepostMutation(
logContext: LogEvents['post:repost:sampled']['logContext'], logContext: LogEvents['post:repost']['logContext'],
) { ) {
const agent = useAgent() const agent = useAgent()
return useMutation< return useMutation<
@@ -275,19 +275,19 @@ function usePostRepostMutation(
{uri: string; cid: string} // the post's uri and cid {uri: string; cid: string} // the post's uri and cid
>({ >({
mutationFn: post => { mutationFn: post => {
logEvent('post:repost:sampled', {logContext}) logEvent('post:repost', {logContext})
return agent.repost(post.uri, post.cid) return agent.repost(post.uri, post.cid)
}, },
}) })
} }
function usePostUnrepostMutation( function usePostUnrepostMutation(
logContext: LogEvents['post:unrepost:sampled']['logContext'], logContext: LogEvents['post:unrepost']['logContext'],
) { ) {
const agent = useAgent() const agent = useAgent()
return useMutation<void, Error, {postUri: string; repostUri: string}>({ return useMutation<void, Error, {postUri: string; repostUri: string}>({
mutationFn: ({repostUri}) => { mutationFn: ({repostUri}) => {
logEvent('post:unrepost:sampled', {logContext}) logEvent('post:unrepost', {logContext})
return agent.deleteRepost(repostUri) return agent.deleteRepost(repostUri)
}, },
}) })
+6 -6
View File
@@ -221,8 +221,8 @@ export function useProfileUpdateMutation() {
export function useProfileFollowMutationQueue( export function useProfileFollowMutationQueue(
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
logContext: LogEvents['profile:follow:sampled']['logContext'] & logContext: LogEvents['profile:follow']['logContext'] &
LogEvents['profile:follow:sampled']['logContext'], LogEvents['profile:follow']['logContext'],
) { ) {
const agent = useAgent() const agent = useAgent()
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -293,7 +293,7 @@ export function useProfileFollowMutationQueue(
} }
function useProfileFollowMutation( function useProfileFollowMutation(
logContext: LogEvents['profile:follow:sampled']['logContext'], logContext: LogEvents['profile:follow']['logContext'],
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
) { ) {
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -308,7 +308,7 @@ function useProfileFollowMutation(
ownProfile = findProfileQueryData(queryClient, currentAccount.did) ownProfile = findProfileQueryData(queryClient, currentAccount.did)
} }
captureAction(ProgressGuideAction.Follow) captureAction(ProgressGuideAction.Follow)
logEvent('profile:follow:sampled', { logEvent('profile:follow', {
logContext, logContext,
didBecomeMutual: profile.viewer didBecomeMutual: profile.viewer
? Boolean(profile.viewer.followedBy) ? Boolean(profile.viewer.followedBy)
@@ -322,12 +322,12 @@ function useProfileFollowMutation(
} }
function useProfileUnfollowMutation( function useProfileUnfollowMutation(
logContext: LogEvents['profile:unfollow:sampled']['logContext'], logContext: LogEvents['profile:unfollow']['logContext'],
) { ) {
const agent = useAgent() const agent = useAgent()
return useMutation<void, Error, {did: string; followUri: string}>({ return useMutation<void, Error, {did: string; followUri: string}>({
mutationFn: async ({followUri}) => { mutationFn: async ({followUri}) => {
logEvent('profile:unfollow:sampled', {logContext}) logEvent('profile:unfollow', {logContext})
return await agent.deleteFollow(followUri) return await agent.deleteFollow(followUri)
}, },
}) })
+2 -2
View File
@@ -74,7 +74,7 @@ export function FeedPage({
scrollToTop() scrollToTop()
truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
setHasNew(false) setHasNew(false)
logEvent('feed:refresh:sampled', { logEvent('feed:refresh', {
feedType: feed.split('|')[0], feedType: feed.split('|')[0],
feedUrl: feed, feedUrl: feed,
reason: 'soft-reset', reason: 'soft-reset',
@@ -98,7 +98,7 @@ export function FeedPage({
scrollToTop() scrollToTop()
truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
setHasNew(false) setHasNew(false)
logEvent('feed:refresh:sampled', { logEvent('feed:refresh', {
feedType: feed.split('|')[0], feedType: feed.split('|')[0],
feedUrl: feed, feedUrl: feed,
reason: 'load-latest', reason: 'load-latest',
+3 -3
View File
@@ -15,7 +15,7 @@ const AnimatedPagerView = Animated.createAnimatedComponent(PagerView)
export interface PagerRef { export interface PagerRef {
setPage: ( setPage: (
index: number, index: number,
reason: LogEvents['home:feedDisplayed:sampled']['reason'], reason: LogEvents['home:feedDisplayed']['reason'],
) => void ) => void
} }
@@ -32,7 +32,7 @@ interface Props {
onPageSelected?: (index: number) => void onPageSelected?: (index: number) => void
onPageSelecting?: ( onPageSelecting?: (
index: number, index: number,
reason: LogEvents['home:feedDisplayed:sampled']['reason'], reason: LogEvents['home:feedDisplayed']['reason'],
) => void ) => void
onPageScrollStateChanged?: ( onPageScrollStateChanged?: (
scrollState: 'idle' | 'dragging' | 'settling', scrollState: 'idle' | 'dragging' | 'settling',
@@ -61,7 +61,7 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
setPage: ( setPage: (
index: number, index: number,
reason: LogEvents['home:feedDisplayed:sampled']['reason'], reason: LogEvents['home:feedDisplayed']['reason'],
) => { ) => {
pagerView.current?.setPage(index) pagerView.current?.setPage(index)
onPageSelecting?.(index, reason) onPageSelecting?.(index, reason)
+3 -6
View File
@@ -18,7 +18,7 @@ interface Props {
onPageSelected?: (index: number) => void onPageSelected?: (index: number) => void
onPageSelecting?: ( onPageSelecting?: (
index: number, index: number,
reason: LogEvents['home:feedDisplayed:sampled']['reason'], reason: LogEvents['home:feedDisplayed']['reason'],
) => void ) => void
} }
export const Pager = React.forwardRef(function PagerImpl( export const Pager = React.forwardRef(function PagerImpl(
@@ -38,17 +38,14 @@ export const Pager = React.forwardRef(function PagerImpl(
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
setPage: ( setPage: (
index: number, index: number,
reason: LogEvents['home:feedDisplayed:sampled']['reason'], reason: LogEvents['home:feedDisplayed']['reason'],
) => { ) => {
onTabBarSelect(index, reason) onTabBarSelect(index, reason)
}, },
})) }))
const onTabBarSelect = React.useCallback( const onTabBarSelect = React.useCallback(
( (index: number, reason: LogEvents['home:feedDisplayed']['reason']) => {
index: number,
reason: LogEvents['home:feedDisplayed:sampled']['reason'],
) => {
const scrollY = window.scrollY const scrollY = window.scrollY
// We want to determine if the tabbar is already "sticking" at the top (in which // We want to determine if the tabbar is already "sticking" at the top (in which
// case we should preserve and restore scroll), or if it is somewhere below in the // case we should preserve and restore scroll), or if it is somewhere below in the
+2 -2
View File
@@ -403,7 +403,7 @@ let Feed = ({
// = // =
const onRefresh = React.useCallback(async () => { const onRefresh = React.useCallback(async () => {
logEvent('feed:refresh:sampled', { logEvent('feed:refresh', {
feedType: feedType, feedType: feedType,
feedUrl: feed, feedUrl: feed,
reason: 'pull-to-refresh', reason: 'pull-to-refresh',
@@ -421,7 +421,7 @@ let Feed = ({
const onEndReached = React.useCallback(async () => { const onEndReached = React.useCallback(async () => {
if (isFetching || !hasNextPage || isError) return if (isFetching || !hasNextPage || isError) return
logEvent('feed:endReached:sampled', { logEvent('feed:endReached', {
feedType: feedType, feedType: feedType,
feedUrl: feed, feedUrl: feed,
itemCount: feedItems.length, itemCount: feedItems.length,
+3 -6
View File
@@ -141,7 +141,7 @@ function HomeScreenReady({
useFocusEffect( useFocusEffect(
useNonReactiveCallback(() => { useNonReactiveCallback(() => {
if (selectedFeed) { if (selectedFeed) {
logEvent('home:feedDisplayed:sampled', { logEvent('home:feedDisplayed', {
index: selectedIndex, index: selectedIndex,
feedType: selectedFeed.split('|')[0], feedType: selectedFeed.split('|')[0],
feedUrl: selectedFeed, feedUrl: selectedFeed,
@@ -163,12 +163,9 @@ function HomeScreenReady({
) )
const onPageSelecting = React.useCallback( const onPageSelecting = React.useCallback(
( (index: number, reason: LogEvents['home:feedDisplayed']['reason']) => {
index: number,
reason: LogEvents['home:feedDisplayed:sampled']['reason'],
) => {
const feed = allFeeds[index] const feed = allFeeds[index]
logEvent('home:feedDisplayed:sampled', { logEvent('home:feedDisplayed', {
index, index,
feedType: feed.split('|')[0], feedType: feed.split('|')[0],
feedUrl: feed, feedUrl: feed,