Prefilter the mergefeed to ensure a better mix of following and custom feeds (#1498)
* Prefilter the mergefeed to ensure a better mix of following and custom feeds * Test suite improvements & tests for the mergefeed (#1499) * Disable invite codes test for now * Update test sim to latest iphone * Introduce TestCtrls driver * Add mergefeed tests
This commit is contained in:
parent
68dd3210d1
commit
5a945c2024
30 changed files with 518 additions and 164 deletions
|
@ -128,23 +128,32 @@ export class FeedTuner {
|
|||
tune(
|
||||
feed: FeedViewPost[],
|
||||
tunerFns: FeedTunerFn[] = [],
|
||||
{dryRun}: {dryRun: boolean} = {dryRun: false},
|
||||
{dryRun, maintainOrder}: {dryRun: boolean; maintainOrder: boolean} = {
|
||||
dryRun: false,
|
||||
maintainOrder: false,
|
||||
},
|
||||
): FeedViewPostsSlice[] {
|
||||
let slices: FeedViewPostsSlice[] = []
|
||||
|
||||
// arrange the posts into thread slices
|
||||
for (let i = feed.length - 1; i >= 0; i--) {
|
||||
const item = feed[i]
|
||||
if (maintainOrder) {
|
||||
slices = feed.map(item => new FeedViewPostsSlice([item]))
|
||||
} else {
|
||||
// arrange the posts into thread slices
|
||||
for (let i = feed.length - 1; i >= 0; i--) {
|
||||
const item = feed[i]
|
||||
|
||||
const selfReplyUri = getSelfReplyUri(item)
|
||||
if (selfReplyUri) {
|
||||
const parent = slices.find(item2 => item2.isNextInThread(selfReplyUri))
|
||||
if (parent) {
|
||||
parent.insert(item)
|
||||
continue
|
||||
const selfReplyUri = getSelfReplyUri(item)
|
||||
if (selfReplyUri) {
|
||||
const parent = slices.find(item2 =>
|
||||
item2.isNextInThread(selfReplyUri),
|
||||
)
|
||||
if (parent) {
|
||||
parent.insert(item)
|
||||
continue
|
||||
}
|
||||
}
|
||||
slices.unshift(new FeedViewPostsSlice([item]))
|
||||
}
|
||||
slices.unshift(new FeedViewPostsSlice([item]))
|
||||
}
|
||||
|
||||
// run the custom tuners
|
||||
|
|
|
@ -4,6 +4,7 @@ import {RootStoreModel} from 'state/index'
|
|||
import {timeout} from 'lib/async/timeout'
|
||||
import {bundleAsync} from 'lib/async/bundle'
|
||||
import {feedUriToHref} from 'lib/strings/url-helpers'
|
||||
import {FeedTuner} from '../feed-manip'
|
||||
import {FeedAPI, FeedAPIResponse, FeedSourceInfo} from './types'
|
||||
|
||||
const REQUEST_WAIT_MS = 500 // 500ms
|
||||
|
@ -43,7 +44,7 @@ export class MergeFeedAPI implements FeedAPI {
|
|||
|
||||
// always keep following topped up
|
||||
if (this.following.numReady < limit) {
|
||||
promises.push(this.following.fetchNext(30))
|
||||
promises.push(this.following.fetchNext(60))
|
||||
}
|
||||
|
||||
// pick the next feeds to sample from
|
||||
|
@ -84,7 +85,8 @@ export class MergeFeedAPI implements FeedAPI {
|
|||
const i = this.itemCursor++
|
||||
const candidateFeeds = this.customFeeds.filter(f => f.numReady > 0)
|
||||
const canSample = candidateFeeds.length > 0
|
||||
const hasFollows = this.following.numReady > 0
|
||||
const hasFollows = this.following.hasMore
|
||||
const hasFollowsReady = this.following.numReady > 0
|
||||
|
||||
// this condition establishes the frequency that custom feeds are woven into follows
|
||||
const shouldSample =
|
||||
|
@ -98,7 +100,11 @@ export class MergeFeedAPI implements FeedAPI {
|
|||
// time to sample, or the user isnt following anybody
|
||||
return candidateFeeds[this.sampleCursor++ % candidateFeeds.length].take(1)
|
||||
}
|
||||
// not time to sample
|
||||
if (!hasFollowsReady) {
|
||||
// stop here so more follows can be fetched
|
||||
return []
|
||||
}
|
||||
// provide follow
|
||||
return this.following.take(1)
|
||||
}
|
||||
|
||||
|
@ -174,6 +180,13 @@ class MergeFeedSource {
|
|||
}
|
||||
|
||||
class MergeFeedSource_Following extends MergeFeedSource {
|
||||
tuner = new FeedTuner()
|
||||
|
||||
reset() {
|
||||
super.reset()
|
||||
this.tuner.reset()
|
||||
}
|
||||
|
||||
async fetchNext(n: number) {
|
||||
return this._fetchNextInner(n)
|
||||
}
|
||||
|
@ -183,10 +196,16 @@ class MergeFeedSource_Following extends MergeFeedSource {
|
|||
limit: number,
|
||||
): Promise<AppBskyFeedGetTimeline.Response> {
|
||||
const res = await this.rootStore.agent.getTimeline({cursor, limit})
|
||||
// filter out mutes pre-emptively to ensure better mixing
|
||||
res.data.feed = res.data.feed.filter(
|
||||
post => !post.post.author.viewer?.muted,
|
||||
// run the tuner pre-emptively to ensure better mixing
|
||||
const slices = this.tuner.tune(
|
||||
res.data.feed,
|
||||
this.rootStore.preferences.getFeedTuners('home'),
|
||||
{
|
||||
dryRun: false,
|
||||
maintainOrder: true,
|
||||
},
|
||||
)
|
||||
res.data.feed = slices.map(slice => slice.rootItem)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,8 +83,14 @@ export async function DEFAULT_FEEDS(
|
|||
// local dev
|
||||
const aliceDid = await resolveHandle('alice.test')
|
||||
return {
|
||||
pinned: [`at://${aliceDid}/app.bsky.feed.generator/alice-favs`],
|
||||
saved: [`at://${aliceDid}/app.bsky.feed.generator/alice-favs`],
|
||||
pinned: [
|
||||
`at://${aliceDid}/app.bsky.feed.generator/alice-favs`,
|
||||
`at://${aliceDid}/app.bsky.feed.generator/alice-favs2`,
|
||||
],
|
||||
saved: [
|
||||
`at://${aliceDid}/app.bsky.feed.generator/alice-favs`,
|
||||
`at://${aliceDid}/app.bsky.feed.generator/alice-favs2`,
|
||||
],
|
||||
}
|
||||
} else if (IS_STAGING(serviceUrl)) {
|
||||
// staging
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue