+3
src/components/PostControls/index.tsx
+3
src/components/PostControls/index.tsx
···
69
69
const {_, i18n} = useLingui()
70
70
const {gtMobile} = useBreakpoints()
71
71
const {openComposer} = useOpenComposer()
72
+
const {feedDescriptor} = useFeedFeedbackContext()
72
73
const [queueLike, queueUnlike] = usePostLikeMutationQueue(
73
74
post,
74
75
viaRepost,
76
+
feedDescriptor,
75
77
logContext,
76
78
)
77
79
const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(
78
80
post,
79
81
viaRepost,
82
+
feedDescriptor,
80
83
logContext,
81
84
)
82
85
const requireAuth = useRequireAuth()
+25
src/logger/metrics.ts
+25
src/logger/metrics.ts
···
130
130
feedType: string
131
131
reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest'
132
132
}
133
+
'feed:save': {
134
+
feedUrl: string
135
+
}
136
+
'feed:unsave': {
137
+
feedUrl: string
138
+
}
139
+
'feed:pin': {
140
+
feedUrl: string
141
+
}
142
+
'feed:unpin': {
143
+
feedUrl: string
144
+
}
145
+
'feed:like': {
146
+
feedUrl: string
147
+
}
148
+
'feed:unlike': {
149
+
feedUrl: string
150
+
}
151
+
'feed:share': {
152
+
feedUrl: string
153
+
}
133
154
'discover:showMore': {
134
155
feedContext: string
135
156
}
···
175
196
likerClout: number | undefined
176
197
postClout: number | undefined
177
198
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
199
+
feedDescriptor?: string
178
200
}
179
201
'post:repost': {
180
202
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
203
+
feedDescriptor?: string
181
204
}
182
205
'post:unlike': {
183
206
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
207
+
feedDescriptor?: string
184
208
}
185
209
'post:unrepost': {
186
210
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
211
+
feedDescriptor?: string
187
212
}
188
213
'post:mute': {}
189
214
'post:unmute': {}
+14
-6
src/screens/Profile/components/ProfileFeedHeader.tsx
+14
-6
src/screens/Profile/components/ProfileFeedHeader.tsx
···
113
113
const isSaved = Boolean(savedFeedConfig)
114
114
const isPinned = Boolean(savedFeedConfig?.pinned)
115
115
116
-
const onToggleSaved = React.useCallback(async () => {
116
+
const onToggleSaved = async () => {
117
117
try {
118
118
playHaptic()
119
119
120
120
if (savedFeedConfig) {
121
121
await removeFeed(savedFeedConfig)
122
122
Toast.show(_(msg`Removed from your feeds`))
123
+
logger.metric('feed:unsave', {feedUrl: info.uri})
123
124
} else {
124
125
await addSavedFeeds([
125
126
{
···
129
130
},
130
131
])
131
132
Toast.show(_(msg`Saved to your feeds`))
133
+
logger.metric('feed:save', {feedUrl: info.uri})
132
134
}
133
135
} catch (err) {
134
136
Toast.show(
···
139
141
)
140
142
logger.error('Failed to update feeds', {message: err})
141
143
}
142
-
}, [_, playHaptic, info, removeFeed, addSavedFeeds, savedFeedConfig])
144
+
}
143
145
144
-
const onTogglePinned = React.useCallback(async () => {
146
+
const onTogglePinned = async () => {
145
147
try {
146
148
playHaptic()
147
149
···
156
158
157
159
if (pinned) {
158
160
Toast.show(_(msg`Pinned ${info.displayName} to Home`))
161
+
logger.metric('feed:pin', {feedUrl: info.uri})
159
162
} else {
160
163
Toast.show(_(msg`Unpinned ${info.displayName} from Home`))
164
+
logger.metric('feed:unpin', {feedUrl: info.uri})
161
165
}
162
166
} else {
163
167
await addSavedFeeds([
···
168
172
},
169
173
])
170
174
Toast.show(_(msg`Pinned ${info.displayName} to Home`))
175
+
logger.metric('feed:pin', {feedUrl: info.uri})
171
176
}
172
177
} catch (e) {
173
178
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
174
179
logger.error('Failed to toggle pinned feed', {message: e})
175
180
}
176
-
}, [playHaptic, info, _, savedFeedConfig, updateSavedFeeds, addSavedFeeds])
181
+
}
177
182
178
183
return (
179
184
<>
···
394
399
const isLiked = !!likeUri
395
400
const feedRkey = React.useMemo(() => new AtUri(info.uri).rkey, [info.uri])
396
401
397
-
const onToggleLiked = React.useCallback(async () => {
402
+
const onToggleLiked = async () => {
398
403
try {
399
404
playHaptic()
400
405
401
406
if (isLiked && likeUri) {
402
407
await unlikeFeed({uri: likeUri})
403
408
setLikeUri('')
409
+
logger.metric('feed:unlike', {feedUrl: info.uri})
404
410
} else {
405
411
const res = await likeFeed({uri: info.uri, cid: info.cid})
406
412
setLikeUri(res.uri)
413
+
logger.metric('feed:like', {feedUrl: info.uri})
407
414
}
408
415
} catch (err) {
409
416
Toast.show(
···
414
421
)
415
422
logger.error('Failed to toggle like', {message: err})
416
423
}
417
-
}, [playHaptic, isLiked, likeUri, unlikeFeed, setLikeUri, likeFeed, info, _])
424
+
}
418
425
419
426
const onPressShare = React.useCallback(() => {
420
427
playHaptic()
421
428
const url = toShareUrl(info.route.href)
422
429
shareUrl(url)
430
+
logger.metric('feed:share', {feedUrl: info.uri})
423
431
}, [info, playHaptic])
424
432
425
433
const onPressReport = React.useCallback(() => {
+1
src/screens/VideoFeed/index.tsx
+1
src/screens/VideoFeed/index.tsx
+19
-11
src/state/queries/post.ts
+19
-11
src/state/queries/post.ts
···
3
3
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
4
4
5
5
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
6
-
import {logEvent, type LogEvents, toClout} from '#/lib/statsig/statsig'
6
+
import {type LogEvents, toClout} from '#/lib/statsig/statsig'
7
+
import {logger} from '#/logger'
7
8
import {updatePostShadow} from '#/state/cache/post-shadow'
8
9
import {type Shadow} from '#/state/cache/types'
9
10
import {useAgent, useSession} from '#/state/session'
···
99
100
export function usePostLikeMutationQueue(
100
101
post: Shadow<AppBskyFeedDefs.PostView>,
101
102
viaRepost: {uri: string; cid: string} | undefined,
103
+
feedDescriptor: string | undefined,
102
104
logContext: LogEvents['post:like']['logContext'] &
103
105
LogEvents['post:unlike']['logContext'],
104
106
) {
···
106
108
const postUri = post.uri
107
109
const postCid = post.cid
108
110
const initialLikeUri = post.viewer?.like
109
-
const likeMutation = usePostLikeMutation(logContext, post)
110
-
const unlikeMutation = usePostUnlikeMutation(logContext)
111
+
const likeMutation = usePostLikeMutation(feedDescriptor, logContext, post)
112
+
const unlikeMutation = usePostUnlikeMutation(feedDescriptor, logContext)
111
113
112
114
const queueToggle = useToggleMutationQueue({
113
115
initialState: initialLikeUri,
···
159
161
}
160
162
161
163
function usePostLikeMutation(
164
+
feedDescriptor: string | undefined,
162
165
logContext: LogEvents['post:like']['logContext'],
163
166
post: Shadow<AppBskyFeedDefs.PostView>,
164
167
) {
···
176
179
if (currentAccount) {
177
180
ownProfile = findProfileQueryData(queryClient, currentAccount.did)
178
181
}
179
-
logEvent('post:like', {
182
+
logger.metric('post:like', {
180
183
logContext,
181
184
doesPosterFollowLiker: postAuthor.viewer
182
185
? Boolean(postAuthor.viewer.followedBy)
···
191
194
post.replyCount != null
192
195
? toClout(post.likeCount + post.repostCount + post.replyCount)
193
196
: undefined,
197
+
feedDescriptor: feedDescriptor,
194
198
})
195
199
return agent.like(uri, cid, via)
196
200
},
···
198
202
}
199
203
200
204
function usePostUnlikeMutation(
205
+
feedDescriptor: string | undefined,
201
206
logContext: LogEvents['post:unlike']['logContext'],
202
207
) {
203
208
const agent = useAgent()
204
209
return useMutation<void, Error, {postUri: string; likeUri: string}>({
205
210
mutationFn: ({likeUri}) => {
206
-
logEvent('post:unlike', {logContext})
211
+
logger.metric('post:unlike', {logContext, feedDescriptor})
207
212
return agent.deleteLike(likeUri)
208
213
},
209
214
})
···
212
217
export function usePostRepostMutationQueue(
213
218
post: Shadow<AppBskyFeedDefs.PostView>,
214
219
viaRepost: {uri: string; cid: string} | undefined,
220
+
feedDescriptor: string | undefined,
215
221
logContext: LogEvents['post:repost']['logContext'] &
216
222
LogEvents['post:unrepost']['logContext'],
217
223
) {
···
219
225
const postUri = post.uri
220
226
const postCid = post.cid
221
227
const initialRepostUri = post.viewer?.repost
222
-
const repostMutation = usePostRepostMutation(logContext)
223
-
const unrepostMutation = usePostUnrepostMutation(logContext)
228
+
const repostMutation = usePostRepostMutation(feedDescriptor, logContext)
229
+
const unrepostMutation = usePostUnrepostMutation(feedDescriptor, logContext)
224
230
225
231
const queueToggle = useToggleMutationQueue({
226
232
initialState: initialRepostUri,
···
270
276
}
271
277
272
278
function usePostRepostMutation(
279
+
feedDescriptor: string | undefined,
273
280
logContext: LogEvents['post:repost']['logContext'],
274
281
) {
275
282
const agent = useAgent()
···
279
286
{uri: string; cid: string; via?: {uri: string; cid: string}} // the post's uri and cid, and the repost uri/cid if present
280
287
>({
281
288
mutationFn: ({uri, cid, via}) => {
282
-
logEvent('post:repost', {logContext})
289
+
logger.metric('post:repost', {logContext, feedDescriptor})
283
290
return agent.repost(uri, cid, via)
284
291
},
285
292
})
286
293
}
287
294
288
295
function usePostUnrepostMutation(
296
+
feedDescriptor: string | undefined,
289
297
logContext: LogEvents['post:unrepost']['logContext'],
290
298
) {
291
299
const agent = useAgent()
292
300
return useMutation<void, Error, {postUri: string; repostUri: string}>({
293
301
mutationFn: ({repostUri}) => {
294
-
logEvent('post:unrepost', {logContext})
302
+
logger.metric('post:unrepost', {logContext, feedDescriptor})
295
303
return agent.deleteRepost(repostUri)
296
304
},
297
305
})
···
363
371
{uri: string} // the root post's uri
364
372
>({
365
373
mutationFn: ({uri}) => {
366
-
logEvent('post:mute', {})
374
+
logger.metric('post:mute', {})
367
375
return agent.api.app.bsky.graph.muteThread({root: uri})
368
376
},
369
377
})
···
373
381
const agent = useAgent()
374
382
return useMutation<{}, Error, {uri: string}>({
375
383
mutationFn: ({uri}) => {
376
-
logEvent('post:unmute', {})
384
+
logger.metric('post:unmute', {})
377
385
return agent.api.app.bsky.graph.unmuteThread({root: uri})
378
386
},
379
387
})