[D1X] Pull out follow-backs for higher signal (#4719)
* Pull out follow-backs for higher signal * Gate it * Fix early gate check --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>zio/stable
parent
0ed99b840d
commit
4f02da96c8
|
@ -2,11 +2,12 @@ export type Gate =
|
||||||
// Keep this alphabetic please.
|
// Keep this alphabetic please.
|
||||||
| 'debug_show_feedcontext'
|
| 'debug_show_feedcontext'
|
||||||
| 'native_pwi_disabled'
|
| 'native_pwi_disabled'
|
||||||
|
| 'new_user_guided_tour'
|
||||||
|
| 'new_user_progress_guide'
|
||||||
| 'onboarding_minimum_interests'
|
| 'onboarding_minimum_interests'
|
||||||
| 'request_notifications_permission_after_onboarding_v2'
|
| 'request_notifications_permission_after_onboarding_v2'
|
||||||
| 'show_avi_follow_button'
|
| 'show_avi_follow_button'
|
||||||
| 'show_follow_back_label_v2'
|
| 'show_follow_back_label_v2'
|
||||||
| 'new_user_guided_tour'
|
|
||||||
| 'new_user_progress_guide'
|
|
||||||
| 'suggested_feeds_interstitial'
|
| 'suggested_feeds_interstitial'
|
||||||
| 'suggested_follows_interstitial'
|
| 'suggested_follows_interstitial'
|
||||||
|
| 'ungroup_follow_backs'
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
import {useModerationOpts} from '../../preferences/moderation-opts'
|
import {useModerationOpts} from '../../preferences/moderation-opts'
|
||||||
import {STALE} from '..'
|
import {STALE} from '..'
|
||||||
|
@ -56,6 +57,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
|
||||||
const unreads = useUnreadNotificationsApi()
|
const unreads = useUnreadNotificationsApi()
|
||||||
const enabled = opts?.enabled !== false
|
const enabled = opts?.enabled !== false
|
||||||
const lastPageCountRef = useRef(0)
|
const lastPageCountRef = useRef(0)
|
||||||
|
const gate = useGate()
|
||||||
|
|
||||||
const query = useInfiniteQuery<
|
const query = useInfiniteQuery<
|
||||||
FeedPage,
|
FeedPage,
|
||||||
|
@ -81,6 +83,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
|
||||||
queryClient,
|
queryClient,
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
fetchAdditionalData: true,
|
fetchAdditionalData: true,
|
||||||
|
shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'),
|
||||||
})
|
})
|
||||||
).page
|
).page
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||||
import EventEmitter from 'eventemitter3'
|
import EventEmitter from 'eventemitter3'
|
||||||
|
|
||||||
import BroadcastChannel from '#/lib/broadcast'
|
import BroadcastChannel from '#/lib/broadcast'
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {useAgent, useSession} from '#/state/session'
|
||||||
import {resetBadgeCount} from 'lib/notifications/notifications'
|
import {resetBadgeCount} from 'lib/notifications/notifications'
|
||||||
|
@ -47,6 +48,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
|
const gate = useGate()
|
||||||
|
|
||||||
const [numUnread, setNumUnread] = React.useState('')
|
const [numUnread, setNumUnread] = React.useState('')
|
||||||
|
|
||||||
|
@ -149,6 +151,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
// only fetch subjects when the page is going to be used
|
// only fetch subjects when the page is going to be used
|
||||||
// in the notifications query, otherwise skip it
|
// in the notifications query, otherwise skip it
|
||||||
fetchAdditionalData: !!invalidate,
|
fetchAdditionalData: !!invalidate,
|
||||||
|
shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'),
|
||||||
})
|
})
|
||||||
const unreadCount = countUnread(page)
|
const unreadCount = countUnread(page)
|
||||||
const unreadCountStr =
|
const unreadCountStr =
|
||||||
|
@ -189,7 +192,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [setNumUnread, queryClient, moderationOpts, agent])
|
}, [setNumUnread, queryClient, moderationOpts, agent, gate])
|
||||||
checkUnreadRef.current = api.checkUnread
|
checkUnreadRef.current = api.checkUnread
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -30,6 +30,7 @@ export async function fetchPage({
|
||||||
queryClient,
|
queryClient,
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
fetchAdditionalData,
|
fetchAdditionalData,
|
||||||
|
shouldUngroupFollowBacks,
|
||||||
}: {
|
}: {
|
||||||
agent: BskyAgent
|
agent: BskyAgent
|
||||||
cursor: string | undefined
|
cursor: string | undefined
|
||||||
|
@ -37,6 +38,7 @@ export async function fetchPage({
|
||||||
queryClient: QueryClient
|
queryClient: QueryClient
|
||||||
moderationOpts: ModerationOpts | undefined
|
moderationOpts: ModerationOpts | undefined
|
||||||
fetchAdditionalData: boolean
|
fetchAdditionalData: boolean
|
||||||
|
shouldUngroupFollowBacks?: () => boolean
|
||||||
}): Promise<{page: FeedPage; indexedAt: string | undefined}> {
|
}): Promise<{page: FeedPage; indexedAt: string | undefined}> {
|
||||||
const res = await agent.listNotifications({
|
const res = await agent.listNotifications({
|
||||||
limit,
|
limit,
|
||||||
|
@ -51,7 +53,7 @@ export async function fetchPage({
|
||||||
)
|
)
|
||||||
|
|
||||||
// group notifications which are essentially similar (follows, likes on a post)
|
// group notifications which are essentially similar (follows, likes on a post)
|
||||||
let notifsGrouped = groupNotifications(notifs)
|
let notifsGrouped = groupNotifications(notifs, {shouldUngroupFollowBacks})
|
||||||
|
|
||||||
// we fetch subjects of notifications (usually posts) now instead of lazily
|
// we fetch subjects of notifications (usually posts) now instead of lazily
|
||||||
// in the UI to avoid relayouts
|
// in the UI to avoid relayouts
|
||||||
|
@ -109,6 +111,7 @@ export function shouldFilterNotif(
|
||||||
|
|
||||||
export function groupNotifications(
|
export function groupNotifications(
|
||||||
notifs: AppBskyNotificationListNotifications.Notification[],
|
notifs: AppBskyNotificationListNotifications.Notification[],
|
||||||
|
options?: {shouldUngroupFollowBacks?: () => boolean},
|
||||||
): FeedNotification[] {
|
): FeedNotification[] {
|
||||||
const groupedNotifs: FeedNotification[] = []
|
const groupedNotifs: FeedNotification[] = []
|
||||||
for (const notif of notifs) {
|
for (const notif of notifs) {
|
||||||
|
@ -123,6 +126,15 @@ export function groupNotifications(
|
||||||
notif.reasonSubject === groupedNotif.notification.reasonSubject &&
|
notif.reasonSubject === groupedNotif.notification.reasonSubject &&
|
||||||
notif.author.did !== groupedNotif.notification.author.did
|
notif.author.did !== groupedNotif.notification.author.did
|
||||||
) {
|
) {
|
||||||
|
const nextIsFollowBack =
|
||||||
|
notif.reason === 'follow' && notif.author.viewer?.following
|
||||||
|
const prevIsFollowBack =
|
||||||
|
groupedNotif.notification.reason === 'follow' &&
|
||||||
|
groupedNotif.notification.author.viewer?.following
|
||||||
|
const shouldUngroup =
|
||||||
|
(nextIsFollowBack || prevIsFollowBack) &&
|
||||||
|
options?.shouldUngroupFollowBacks?.()
|
||||||
|
if (!shouldUngroup) {
|
||||||
groupedNotif.additional = groupedNotif.additional || []
|
groupedNotif.additional = groupedNotif.additional || []
|
||||||
groupedNotif.additional.push(notif)
|
groupedNotif.additional.push(notif)
|
||||||
grouped = true
|
grouped = true
|
||||||
|
@ -130,6 +142,7 @@ export function groupNotifications(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!grouped) {
|
if (!grouped) {
|
||||||
const type = toKnownType(notif)
|
const type = toKnownType(notif)
|
||||||
if (type !== 'starterpack-joined') {
|
if (type !== 'starterpack-joined') {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {msg, plural, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {FeedNotification} from '#/state/queries/notifications/feed'
|
import {FeedNotification} from '#/state/queries/notifications/feed'
|
||||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
@ -86,6 +87,7 @@ let FeedItem = ({
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
const gate = useGate()
|
||||||
const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false)
|
const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false)
|
||||||
const itemHref = useMemo(() => {
|
const itemHref = useMemo(() => {
|
||||||
if (item.type === 'post-like' || item.type === 'repost') {
|
if (item.type === 'post-like' || item.type === 'repost') {
|
||||||
|
@ -168,6 +170,7 @@ let FeedItem = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isFollowBack = false
|
||||||
let action = ''
|
let action = ''
|
||||||
let icon = (
|
let icon = (
|
||||||
<HeartIconFilled
|
<HeartIconFilled
|
||||||
|
@ -184,7 +187,15 @@ let FeedItem = ({
|
||||||
action = _(msg`reposted your post`)
|
action = _(msg`reposted your post`)
|
||||||
icon = <RepostIcon size="xl" style={{color: t.palette.positive_600}} />
|
icon = <RepostIcon size="xl" style={{color: t.palette.positive_600}} />
|
||||||
} else if (item.type === 'follow') {
|
} else if (item.type === 'follow') {
|
||||||
|
if (
|
||||||
|
item.notification.author.viewer?.following &&
|
||||||
|
gate('ungroup_follow_backs')
|
||||||
|
) {
|
||||||
|
isFollowBack = true
|
||||||
|
action = _(msg`followed you back`)
|
||||||
|
} else {
|
||||||
action = _(msg`followed you`)
|
action = _(msg`followed you`)
|
||||||
|
}
|
||||||
icon = <PersonPlusIcon size="xl" style={{color: t.palette.primary_500}} />
|
icon = <PersonPlusIcon size="xl" style={{color: t.palette.primary_500}} />
|
||||||
} else if (item.type === 'feedgen-like') {
|
} else if (item.type === 'feedgen-like') {
|
||||||
action = _(msg`liked your custom feed`)
|
action = _(msg`liked your custom feed`)
|
||||||
|
@ -260,7 +271,7 @@ let FeedItem = ({
|
||||||
visible={!isAuthorsExpanded}
|
visible={!isAuthorsExpanded}
|
||||||
authors={authors}
|
authors={authors}
|
||||||
onToggleAuthorsExpanded={onToggleAuthorsExpanded}
|
onToggleAuthorsExpanded={onToggleAuthorsExpanded}
|
||||||
showDmButton={item.type === 'starterpack-joined'}
|
showDmButton={item.type === 'starterpack-joined' || isFollowBack}
|
||||||
/>
|
/>
|
||||||
<ExpandedAuthorsList visible={isAuthorsExpanded} authors={authors} />
|
<ExpandedAuthorsList visible={isAuthorsExpanded} authors={authors} />
|
||||||
<Text style={styles.meta}>
|
<Text style={styles.meta}>
|
||||||
|
|
Loading…
Reference in New Issue