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 keyzio/stable
parent
da8af38dcc
commit
1b356556c9
|
@ -22,7 +22,7 @@
|
||||||
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all"
|
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "0.2.8",
|
"@atproto/api": "0.2.9",
|
||||||
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@expo/webpack-config": "^18.0.1",
|
"@expo/webpack-config": "^18.0.1",
|
||||||
|
|
|
@ -11,13 +11,6 @@ import * as apilib from 'lib/api/index'
|
||||||
import {cleanError} from 'lib/strings/errors'
|
import {cleanError} from 'lib/strings/errors'
|
||||||
import {updateDataOptimistically} from 'lib/async/revertible'
|
import {updateDataOptimistically} from 'lib/async/revertible'
|
||||||
|
|
||||||
function* reactKeyGenerator(): Generator<string> {
|
|
||||||
let counter = 0
|
|
||||||
while (true) {
|
|
||||||
yield `item-${counter++}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PostThreadItemModel {
|
export class PostThreadItemModel {
|
||||||
// ui state
|
// ui state
|
||||||
_reactKey: string = ''
|
_reactKey: string = ''
|
||||||
|
@ -55,10 +48,9 @@ export class PostThreadItemModel {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public rootStore: RootStoreModel,
|
public rootStore: RootStoreModel,
|
||||||
reactKey: string,
|
|
||||||
v: AppBskyFeedDefs.ThreadViewPost,
|
v: AppBskyFeedDefs.ThreadViewPost,
|
||||||
) {
|
) {
|
||||||
this._reactKey = reactKey
|
this._reactKey = `thread-${v.post.uri}`
|
||||||
this.post = v.post
|
this.post = v.post
|
||||||
if (FeedPost.isRecord(this.post.record)) {
|
if (FeedPost.isRecord(this.post.record)) {
|
||||||
const valid = FeedPost.validateRecord(this.post.record)
|
const valid = FeedPost.validateRecord(this.post.record)
|
||||||
|
@ -82,7 +74,6 @@ export class PostThreadItemModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
assignTreeModels(
|
assignTreeModels(
|
||||||
keyGen: Generator<string>,
|
|
||||||
v: AppBskyFeedDefs.ThreadViewPost,
|
v: AppBskyFeedDefs.ThreadViewPost,
|
||||||
higlightedPostUri: string,
|
higlightedPostUri: string,
|
||||||
includeParent = true,
|
includeParent = true,
|
||||||
|
@ -91,22 +82,12 @@ export class PostThreadItemModel {
|
||||||
// parents
|
// parents
|
||||||
if (includeParent && v.parent) {
|
if (includeParent && v.parent) {
|
||||||
if (AppBskyFeedDefs.isThreadViewPost(v.parent)) {
|
if (AppBskyFeedDefs.isThreadViewPost(v.parent)) {
|
||||||
const parentModel = new PostThreadItemModel(
|
const parentModel = new PostThreadItemModel(this.rootStore, v.parent)
|
||||||
this.rootStore,
|
|
||||||
keyGen.next().value,
|
|
||||||
v.parent,
|
|
||||||
)
|
|
||||||
parentModel._depth = this._depth - 1
|
parentModel._depth = this._depth - 1
|
||||||
parentModel._showChildReplyLine = true
|
parentModel._showChildReplyLine = true
|
||||||
if (v.parent.parent) {
|
if (v.parent.parent) {
|
||||||
parentModel._showParentReplyLine = true //parentModel.uri !== higlightedPostUri
|
parentModel._showParentReplyLine = true //parentModel.uri !== higlightedPostUri
|
||||||
parentModel.assignTreeModels(
|
parentModel.assignTreeModels(v.parent, higlightedPostUri, true, false)
|
||||||
keyGen,
|
|
||||||
v.parent,
|
|
||||||
higlightedPostUri,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
this.parent = parentModel
|
this.parent = parentModel
|
||||||
} else if (AppBskyFeedDefs.isNotFoundPost(v.parent)) {
|
} else if (AppBskyFeedDefs.isNotFoundPost(v.parent)) {
|
||||||
|
@ -118,23 +99,13 @@ export class PostThreadItemModel {
|
||||||
const replies = []
|
const replies = []
|
||||||
for (const item of v.replies) {
|
for (const item of v.replies) {
|
||||||
if (AppBskyFeedDefs.isThreadViewPost(item)) {
|
if (AppBskyFeedDefs.isThreadViewPost(item)) {
|
||||||
const itemModel = new PostThreadItemModel(
|
const itemModel = new PostThreadItemModel(this.rootStore, item)
|
||||||
this.rootStore,
|
|
||||||
keyGen.next().value,
|
|
||||||
item,
|
|
||||||
)
|
|
||||||
itemModel._depth = this._depth + 1
|
itemModel._depth = this._depth + 1
|
||||||
itemModel._showParentReplyLine =
|
itemModel._showParentReplyLine =
|
||||||
itemModel.parentUri !== higlightedPostUri
|
itemModel.parentUri !== higlightedPostUri
|
||||||
if (item.replies?.length) {
|
if (item.replies?.length) {
|
||||||
itemModel._showChildReplyLine = true
|
itemModel._showChildReplyLine = true
|
||||||
itemModel.assignTreeModels(
|
itemModel.assignTreeModels(item, higlightedPostUri, false, true)
|
||||||
keyGen,
|
|
||||||
item,
|
|
||||||
higlightedPostUri,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
replies.push(itemModel)
|
replies.push(itemModel)
|
||||||
} else if (AppBskyFeedDefs.isNotFoundPost(item)) {
|
} else if (AppBskyFeedDefs.isNotFoundPost(item)) {
|
||||||
|
@ -241,6 +212,19 @@ export class PostThreadModel {
|
||||||
this.params = params
|
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() {
|
get hasContent() {
|
||||||
return typeof this.thread !== 'undefined'
|
return typeof this.thread !== 'undefined'
|
||||||
}
|
}
|
||||||
|
@ -360,6 +344,9 @@ export class PostThreadModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _load(isRefreshing = false) {
|
async _load(isRefreshing = false) {
|
||||||
|
if (this.hasLoaded && !isRefreshing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this._xLoading(isRefreshing)
|
this._xLoading(isRefreshing)
|
||||||
try {
|
try {
|
||||||
const res = await this.rootStore.agent.getPostThread(
|
const res = await this.rootStore.agent.getPostThread(
|
||||||
|
@ -374,15 +361,12 @@ export class PostThreadModel {
|
||||||
|
|
||||||
_replaceAll(res: GetPostThread.Response) {
|
_replaceAll(res: GetPostThread.Response) {
|
||||||
sortThread(res.data.thread)
|
sortThread(res.data.thread)
|
||||||
const keyGen = reactKeyGenerator()
|
|
||||||
const thread = new PostThreadItemModel(
|
const thread = new PostThreadItemModel(
|
||||||
this.rootStore,
|
this.rootStore,
|
||||||
keyGen.next().value,
|
|
||||||
res.data.thread as AppBskyFeedDefs.ThreadViewPost,
|
res.data.thread as AppBskyFeedDefs.ThreadViewPost,
|
||||||
)
|
)
|
||||||
thread._isHighlightedPost = true
|
thread._isHighlightedPost = true
|
||||||
thread.assignTreeModels(
|
thread.assignTreeModels(
|
||||||
keyGen,
|
|
||||||
res.data.thread as AppBskyFeedDefs.ThreadViewPost,
|
res.data.thread as AppBskyFeedDefs.ThreadViewPost,
|
||||||
thread.uri,
|
thread.uri,
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx'
|
||||||
import {
|
import {
|
||||||
AppBskyNotificationListNotifications as ListNotifications,
|
AppBskyNotificationListNotifications as ListNotifications,
|
||||||
AppBskyActorDefs,
|
AppBskyActorDefs,
|
||||||
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
AppBskyFeedRepost,
|
AppBskyFeedRepost,
|
||||||
AppBskyFeedLike,
|
AppBskyFeedLike,
|
||||||
|
@ -146,6 +147,14 @@ export class NotificationsFeedItemModel {
|
||||||
return false
|
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 {
|
get subjectUri(): string {
|
||||||
if (this.reasonSubject) {
|
if (this.reasonSubject) {
|
||||||
return this.reasonSubject
|
return this.reasonSubject
|
||||||
|
@ -193,28 +202,11 @@ export class NotificationsFeedItemModel {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchAdditionalData() {
|
setAdditionalData(additionalPost: AppBskyFeedDefs.PostView) {
|
||||||
if (!this.needsAdditionalData) {
|
this.additionalPost = PostThreadModel.fromPostView(
|
||||||
return
|
this.rootStore,
|
||||||
}
|
additionalPost,
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,7 +456,13 @@ export class NotificationsFeedModel {
|
||||||
'mostRecent',
|
'mostRecent',
|
||||||
res.data.notifications[0],
|
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])
|
const filtered = this._filterNotifications([notif])
|
||||||
return filtered[0]
|
return filtered[0]
|
||||||
}
|
}
|
||||||
|
@ -536,25 +534,39 @@ export class NotificationsFeedModel {
|
||||||
async _fetchItemModels(
|
async _fetchItemModels(
|
||||||
items: ListNotifications.Notification[],
|
items: ListNotifications.Notification[],
|
||||||
): Promise<NotificationsFeedItemModel[]> {
|
): Promise<NotificationsFeedItemModel[]> {
|
||||||
const promises = []
|
// construct item models and track who needs more data
|
||||||
const itemModels: NotificationsFeedItemModel[] = []
|
const itemModels: NotificationsFeedItemModel[] = []
|
||||||
|
const addedPostMap = new Map<string, NotificationsFeedItemModel[]>()
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const itemModel = new NotificationsFeedItemModel(
|
const itemModel = new NotificationsFeedItemModel(
|
||||||
this.rootStore,
|
this.rootStore,
|
||||||
`item-${_idCounter++}`,
|
`item-${_idCounter++}`,
|
||||||
item,
|
item,
|
||||||
)
|
)
|
||||||
if (itemModel.needsAdditionalData) {
|
const uri = itemModel.additionaDataUri
|
||||||
promises.push(itemModel.fetchAdditionalData())
|
if (uri) {
|
||||||
|
const models = addedPostMap.get(uri) || []
|
||||||
|
models.push(itemModel)
|
||||||
|
addedPostMap.set(uri, models)
|
||||||
}
|
}
|
||||||
itemModels.push(itemModel)
|
itemModels.push(itemModel)
|
||||||
}
|
}
|
||||||
await Promise.all(promises).catch(e => {
|
|
||||||
this.rootStore.log.error(
|
// fetch additional data
|
||||||
'Uncaught failure during notifications _processNotifications()',
|
if (addedPostMap.size > 0) {
|
||||||
e,
|
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
|
return itemModels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import {makeAutoObservable, runInAction} from 'mobx'
|
import {makeAutoObservable, runInAction} from 'mobx'
|
||||||
import {searchProfiles, searchPosts} from 'lib/api/search'
|
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'
|
import {RootStoreModel} from '../root-store'
|
||||||
|
|
||||||
export class SearchUIModel {
|
export class SearchUIModel {
|
||||||
isPostsLoading = false
|
isPostsLoading = false
|
||||||
isProfilesLoading = false
|
isProfilesLoading = false
|
||||||
query: string = ''
|
query: string = ''
|
||||||
postUris: string[] = []
|
posts: PostThreadModel[] = []
|
||||||
profiles: AppBskyActorDefs.ProfileView[] = []
|
profiles: AppBskyActorDefs.ProfileView[] = []
|
||||||
|
|
||||||
constructor(public rootStore: RootStoreModel) {
|
constructor(public rootStore: RootStoreModel) {
|
||||||
|
@ -15,7 +16,7 @@ export class SearchUIModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch(q: string) {
|
async fetch(q: string) {
|
||||||
this.postUris = []
|
this.posts = []
|
||||||
this.profiles = []
|
this.profiles = []
|
||||||
this.query = q
|
this.query = q
|
||||||
if (!q.trim()) {
|
if (!q.trim()) {
|
||||||
|
@ -29,8 +30,22 @@ export class SearchUIModel {
|
||||||
searchPosts(q).catch(_e => []),
|
searchPosts(q).catch(_e => []),
|
||||||
searchProfiles(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(() => {
|
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
|
this.isPostsLoading = false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ const PostResults = observer(({model}: {model: SearchUIModel}) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.postUris.length === 0) {
|
if (model.posts.length === 0) {
|
||||||
return (
|
return (
|
||||||
<CenteredView>
|
<CenteredView>
|
||||||
<Text type="xl" style={[styles.empty, pal.text]}>
|
<Text type="xl" style={[styles.empty, pal.text]}>
|
||||||
|
@ -61,8 +61,13 @@ const PostResults = observer(({model}: {model: SearchUIModel}) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView style={pal.view}>
|
<ScrollView style={pal.view}>
|
||||||
{model.postUris.map(uri => (
|
{model.posts.map(post => (
|
||||||
<Post key={uri} uri={uri} hideError />
|
<Post
|
||||||
|
key={post.resolvedUri}
|
||||||
|
uri={post.resolvedUri}
|
||||||
|
initView={post}
|
||||||
|
hideError
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<View style={s.footerSpacer} />
|
<View style={s.footerSpacer} />
|
||||||
<View style={s.footerSpacer} />
|
<View style={s.footerSpacer} />
|
||||||
|
|
|
@ -30,10 +30,10 @@
|
||||||
tlds "^1.234.0"
|
tlds "^1.234.0"
|
||||||
typed-emitter "^2.1.0"
|
typed-emitter "^2.1.0"
|
||||||
|
|
||||||
"@atproto/api@0.2.8":
|
"@atproto/api@0.2.9":
|
||||||
version "0.2.8"
|
version "0.2.9"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.2.8.tgz#92ed413804ecb43aaa45ec18afc93d6f2b28a689"
|
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.2.9.tgz#08e29da66d1a9001d9d3ce427548c1760d805e99"
|
||||||
integrity sha512-LfPgtf3UNg2W/AxHkJMJrLNT9QAD6bi16Sw5Zt3mgANrDnHWGygA7gRpeNdgVI+kFEhQfrIItemJvWLIB9BJDQ==
|
integrity sha512-r00IqidX2YF3VUEa4MUO2Vxqp3+QhI1cSNcWgzT4LsANapzrwdDTM+rY2Ejp9na3F+unO4SWRW3o434cVmG5gw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/common-web" "*"
|
"@atproto/common-web" "*"
|
||||||
"@atproto/uri" "*"
|
"@atproto/uri" "*"
|
||||||
|
|
Loading…
Reference in New Issue