Performance fixes with new getPosts (#525)

* Update notifications to fetch in a batch using getPosts

* Improve search perf with getPosts

* Bump @atproto/api@0.2.9

* Just use post uri for key
zio/stable
Paul Frazee 2023-04-24 17:02:58 -05:00 committed by GitHub
parent da8af38dcc
commit 1b356556c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 81 deletions

View File

@ -22,7 +22,7 @@
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all"
},
"dependencies": {
"@atproto/api": "0.2.8",
"@atproto/api": "0.2.9",
"@bam.tech/react-native-image-resizer": "^3.0.4",
"@braintree/sanitize-url": "^6.0.2",
"@expo/webpack-config": "^18.0.1",

View File

@ -11,13 +11,6 @@ import * as apilib from 'lib/api/index'
import {cleanError} from 'lib/strings/errors'
import {updateDataOptimistically} from 'lib/async/revertible'
function* reactKeyGenerator(): Generator<string> {
let counter = 0
while (true) {
yield `item-${counter++}`
}
}
export class PostThreadItemModel {
// ui state
_reactKey: string = ''
@ -55,10 +48,9 @@ export class PostThreadItemModel {
constructor(
public rootStore: RootStoreModel,
reactKey: string,
v: AppBskyFeedDefs.ThreadViewPost,
) {
this._reactKey = reactKey
this._reactKey = `thread-${v.post.uri}`
this.post = v.post
if (FeedPost.isRecord(this.post.record)) {
const valid = FeedPost.validateRecord(this.post.record)
@ -82,7 +74,6 @@ export class PostThreadItemModel {
}
assignTreeModels(
keyGen: Generator<string>,
v: AppBskyFeedDefs.ThreadViewPost,
higlightedPostUri: string,
includeParent = true,
@ -91,22 +82,12 @@ export class PostThreadItemModel {
// parents
if (includeParent && v.parent) {
if (AppBskyFeedDefs.isThreadViewPost(v.parent)) {
const parentModel = new PostThreadItemModel(
this.rootStore,
keyGen.next().value,
v.parent,
)
const parentModel = new PostThreadItemModel(this.rootStore, v.parent)
parentModel._depth = this._depth - 1
parentModel._showChildReplyLine = true
if (v.parent.parent) {
parentModel._showParentReplyLine = true //parentModel.uri !== higlightedPostUri
parentModel.assignTreeModels(
keyGen,
v.parent,
higlightedPostUri,
true,
false,
)
parentModel.assignTreeModels(v.parent, higlightedPostUri, true, false)
}
this.parent = parentModel
} else if (AppBskyFeedDefs.isNotFoundPost(v.parent)) {
@ -118,23 +99,13 @@ export class PostThreadItemModel {
const replies = []
for (const item of v.replies) {
if (AppBskyFeedDefs.isThreadViewPost(item)) {
const itemModel = new PostThreadItemModel(
this.rootStore,
keyGen.next().value,
item,
)
const itemModel = new PostThreadItemModel(this.rootStore, item)
itemModel._depth = this._depth + 1
itemModel._showParentReplyLine =
itemModel.parentUri !== higlightedPostUri
if (item.replies?.length) {
itemModel._showChildReplyLine = true
itemModel.assignTreeModels(
keyGen,
item,
higlightedPostUri,
false,
true,
)
itemModel.assignTreeModels(item, higlightedPostUri, false, true)
}
replies.push(itemModel)
} else if (AppBskyFeedDefs.isNotFoundPost(item)) {
@ -241,6 +212,19 @@ export class PostThreadModel {
this.params = params
}
static fromPostView(
rootStore: RootStoreModel,
postView: AppBskyFeedDefs.PostView,
) {
const model = new PostThreadModel(rootStore, {uri: postView.uri})
model.resolvedUri = postView.uri
model.hasLoaded = true
model.thread = new PostThreadItemModel(rootStore, {
post: postView,
})
return model
}
get hasContent() {
return typeof this.thread !== 'undefined'
}
@ -360,6 +344,9 @@ export class PostThreadModel {
}
async _load(isRefreshing = false) {
if (this.hasLoaded && !isRefreshing) {
return
}
this._xLoading(isRefreshing)
try {
const res = await this.rootStore.agent.getPostThread(
@ -374,15 +361,12 @@ export class PostThreadModel {
_replaceAll(res: GetPostThread.Response) {
sortThread(res.data.thread)
const keyGen = reactKeyGenerator()
const thread = new PostThreadItemModel(
this.rootStore,
keyGen.next().value,
res.data.thread as AppBskyFeedDefs.ThreadViewPost,
)
thread._isHighlightedPost = true
thread.assignTreeModels(
keyGen,
res.data.thread as AppBskyFeedDefs.ThreadViewPost,
thread.uri,
)

View File

@ -2,6 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx'
import {
AppBskyNotificationListNotifications as ListNotifications,
AppBskyActorDefs,
AppBskyFeedDefs,
AppBskyFeedPost,
AppBskyFeedRepost,
AppBskyFeedLike,
@ -146,6 +147,14 @@ export class NotificationsFeedItemModel {
return false
}
get additionaDataUri(): string | undefined {
if (this.isReply || this.isQuote || this.isMention) {
return this.uri
} else if (this.isLike || this.isRepost) {
return this.subjectUri
}
}
get subjectUri(): string {
if (this.reasonSubject) {
return this.reasonSubject
@ -193,28 +202,11 @@ export class NotificationsFeedItemModel {
)
}
async fetchAdditionalData() {
if (!this.needsAdditionalData) {
return
}
let postUri
if (this.isReply || this.isQuote || this.isMention) {
postUri = this.uri
} else if (this.isLike || this.isRepost) {
postUri = this.subjectUri
}
if (postUri) {
this.additionalPost = new PostThreadModel(this.rootStore, {
uri: postUri,
depth: 0,
})
await this.additionalPost.setup().catch(e => {
this.rootStore.log.error(
'Failed to load post needed by notification',
e,
)
})
}
setAdditionalData(additionalPost: AppBskyFeedDefs.PostView) {
this.additionalPost = PostThreadModel.fromPostView(
this.rootStore,
additionalPost,
)
}
}
@ -464,7 +456,13 @@ export class NotificationsFeedModel {
'mostRecent',
res.data.notifications[0],
)
await notif.fetchAdditionalData()
const addedUri = notif.additionaDataUri
if (addedUri) {
const postsRes = await this.rootStore.agent.app.bsky.feed.getPosts({
uris: [addedUri],
})
notif.setAdditionalData(postsRes.data.posts[0])
}
const filtered = this._filterNotifications([notif])
return filtered[0]
}
@ -536,25 +534,39 @@ export class NotificationsFeedModel {
async _fetchItemModels(
items: ListNotifications.Notification[],
): Promise<NotificationsFeedItemModel[]> {
const promises = []
// construct item models and track who needs more data
const itemModels: NotificationsFeedItemModel[] = []
const addedPostMap = new Map<string, NotificationsFeedItemModel[]>()
for (const item of items) {
const itemModel = new NotificationsFeedItemModel(
this.rootStore,
`item-${_idCounter++}`,
item,
)
if (itemModel.needsAdditionalData) {
promises.push(itemModel.fetchAdditionalData())
const uri = itemModel.additionaDataUri
if (uri) {
const models = addedPostMap.get(uri) || []
models.push(itemModel)
addedPostMap.set(uri, models)
}
itemModels.push(itemModel)
}
await Promise.all(promises).catch(e => {
this.rootStore.log.error(
'Uncaught failure during notifications _processNotifications()',
e,
)
})
// fetch additional data
if (addedPostMap.size > 0) {
const postsRes = await this.rootStore.agent.app.bsky.feed.getPosts({
uris: Array.from(addedPostMap.keys()),
})
for (const post of postsRes.data.posts) {
const models = addedPostMap.get(post.uri)
if (models?.length) {
for (const model of models) {
model.setAdditionalData(post)
}
}
}
}
return itemModels
}

View File

@ -1,13 +1,14 @@
import {makeAutoObservable, runInAction} from 'mobx'
import {searchProfiles, searchPosts} from 'lib/api/search'
import {AppBskyActorDefs} from '@atproto/api'
import {PostThreadModel} from '../content/post-thread'
import {AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api'
import {RootStoreModel} from '../root-store'
export class SearchUIModel {
isPostsLoading = false
isProfilesLoading = false
query: string = ''
postUris: string[] = []
posts: PostThreadModel[] = []
profiles: AppBskyActorDefs.ProfileView[] = []
constructor(public rootStore: RootStoreModel) {
@ -15,7 +16,7 @@ export class SearchUIModel {
}
async fetch(q: string) {
this.postUris = []
this.posts = []
this.profiles = []
this.query = q
if (!q.trim()) {
@ -29,8 +30,22 @@ export class SearchUIModel {
searchPosts(q).catch(_e => []),
searchProfiles(q).catch(_e => []),
])
let posts: AppBskyFeedDefs.PostView[] = []
if (postsSearch?.length) {
do {
const res = await this.rootStore.agent.app.bsky.feed.getPosts({
uris: postsSearch
.splice(0, 25)
.map(p => `at://${p.user.did}/${p.tid}`),
})
posts = posts.concat(res.data.posts)
} while (postsSearch.length)
}
runInAction(() => {
this.postUris = postsSearch?.map(p => `at://${p.user.did}/${p.tid}`) || []
this.posts = posts.map(post =>
PostThreadModel.fromPostView(this.rootStore, post),
)
this.isPostsLoading = false
})

View File

@ -49,7 +49,7 @@ const PostResults = observer(({model}: {model: SearchUIModel}) => {
)
}
if (model.postUris.length === 0) {
if (model.posts.length === 0) {
return (
<CenteredView>
<Text type="xl" style={[styles.empty, pal.text]}>
@ -61,8 +61,13 @@ const PostResults = observer(({model}: {model: SearchUIModel}) => {
return (
<ScrollView style={pal.view}>
{model.postUris.map(uri => (
<Post key={uri} uri={uri} hideError />
{model.posts.map(post => (
<Post
key={post.resolvedUri}
uri={post.resolvedUri}
initView={post}
hideError
/>
))}
<View style={s.footerSpacer} />
<View style={s.footerSpacer} />

View File

@ -30,10 +30,10 @@
tlds "^1.234.0"
typed-emitter "^2.1.0"
"@atproto/api@0.2.8":
version "0.2.8"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.2.8.tgz#92ed413804ecb43aaa45ec18afc93d6f2b28a689"
integrity sha512-LfPgtf3UNg2W/AxHkJMJrLNT9QAD6bi16Sw5Zt3mgANrDnHWGygA7gRpeNdgVI+kFEhQfrIItemJvWLIB9BJDQ==
"@atproto/api@0.2.9":
version "0.2.9"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.2.9.tgz#08e29da66d1a9001d9d3ce427548c1760d805e99"
integrity sha512-r00IqidX2YF3VUEa4MUO2Vxqp3+QhI1cSNcWgzT4LsANapzrwdDTM+rY2Ejp9na3F+unO4SWRW3o434cVmG5gw==
dependencies:
"@atproto/common-web" "*"
"@atproto/uri" "*"