Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upThread reactions #3370
Conversation
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
spectrum-bot
bot
Jun 20, 2018
| Warnings | |
|---|---|
|
These modified files do not have Flow enabled:
|
Generated by
spectrum-bot
bot
commented
Jun 20, 2018
•
Generated by |
spectrum-bot
bot
added
the
WIP: Building
label
Jun 20, 2018
brianlovin
and others
added some commits
Jun 21, 2018
mxstbr
reviewed
Jun 26, 2018
Overall LGTM, only concern is the Thread.reactions schema as noted below. Let's get something better in there that's not like the Message.reactions one, then I'm good with the backend side of things here apart from the nits!
| +import { events } from 'shared/analytics'; | ||
| +import { trackQueue } from 'shared/bull/queues'; | ||
| + | ||
| +type ReactionType = 'like'; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + }, | ||
| + }); | ||
| + | ||
| + sendThreadReactionNotificationQueue.add({ threadReaction: thisReaction, userId }); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mxstbr
Jun 26, 2018
Member
This is probably not ideal? If I'm reading this correctly, it would mean that if I spam-click the thread reaction button you'll be sent millions of notifications, one for each click. We should only ever send one of 'em?
mxstbr
Jun 26, 2018
Member
This is probably not ideal? If I'm reading this correctly, it would mean that if I spam-click the thread reaction button you'll be sent millions of notifications, one for each click. We should only ever send one of 'em?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brianlovin
Jun 26, 2018
Member
This is handled in athena. It works by looking up a previous notification of the same type on the same entity in the previous 30mins, then will de-duplicate actors. So the result is that it will mark the notification as "unread" for the thread author, but will not actually insert multiple notifications unless there is a 30 min gap in between.
brianlovin
Jun 26, 2018
Member
This is handled in athena. It works by looking up a previous notification of the same type on the same entity in the previous 30mins, then will de-duplicate actors. So the result is that it will mark the notification as "unread" for the thread author, but will not actually insert multiple notifications unless there is a 30 min gap in between.
| @@ -97,13 +98,24 @@ const Thread = /* GraphQL */ ` | ||
| filesToUpload: [Upload] | ||
| } | ||
| + input AddThreadReactionInput { | ||
| + threadId: ID! | ||
| + type: ReactionTypes |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + debug('get thread'); | ||
| + | ||
| + // make sure that the person who left the original message still has notification permissions in this thread. We have to check the threadtype to determine if the reaction was left in a story thread or a direct message thread | ||
| + // TODO: In the future we'll want reactions in direct message threads to trigger push notifications, but for now it introduces too much complexity so we just say false |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + const thread = await getThreadById(updatedNotification.context.id); | ||
| + | ||
| + // make sure that the person who left the original message still has notification permissions in this thread. We have to check the threadtype to determine if the reaction was left in a story thread or a direct message thread | ||
| + // TODO: In the future we'll want reactions in direct message threads to trigger push notifications, but for now it introduces too much complexity so we just say false |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| @@ -54,6 +54,7 @@ const Thread = /* GraphQL */ ` | ||
| attachments: [Attachment] | ||
| watercooler: Boolean | ||
| currentUserLastSeen: Date @cost(complexity: 1) | ||
| + reactions: ReactionData @cost(complexity: 1) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mxstbr
Jun 26, 2018
Member
I'm going to veto using the same schema as the message reactions because it doesn't let us show which users liked the thing. (you only see it in the notifications, but nobody else can see/check) Let's change the return type here to include that information somehow, (open to ideas) so we can in the future show who liked a thread or not in the UI.
mxstbr
Jun 26, 2018
Member
I'm going to veto using the same schema as the message reactions because it doesn't let us show which users liked the thing. (you only see it in the notifications, but nobody else can see/check) Let's change the return type here to include that information somehow, (open to ideas) so we can in the future show who liked a thread or not in the UI.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brianlovin
Jun 26, 2018
Member
Going to change this to be a separate type ThreadReactions which will expose:
count: Int
hasReacted: Boolean
reactors: [User]
Or something like that - we can actually extend the message reactions to have the same data model, but let's keep message reactions and thread reactions as separately defined types in case they diverge in the future.
brianlovin
Jun 26, 2018
•
Member
Going to change this to be a separate type ThreadReactions which will expose:
count: Int
hasReacted: Boolean
reactors: [User]
Or something like that - we can actually extend the message reactions to have the same data model, but let's keep message reactions and thread reactions as separately defined types in case they diverge in the future.
brianlovin
and others
added some commits
Jun 26, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brianlovin
Jun 27, 2018
Member
Took a while, but finally got this working. @mxstbr the one trouble I ran into was debouncing the handleScroll method in that last commit - ideally we would debounce or throttle it so we aren't putting a ton of load on the browser just to determine whether or not to show the header (I found a 100ms debounce is probably a good threshold). But I ran into some issues with React removing synthetic events from the event pool. I tried using event.persist() from the docs (https://reactjs.org/docs/events.html) but couldn't get it to work - React kept throwing the error:
This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the method `target` on a released/nullified synthetic event. This is a no-op function. If you must keep the original synthetic event around, use event.persist(). See https://facebook.github.io/react/docs/events.html#event-pooling for more information.
Any chance you could take a look? Without the debounce people's machines are gonna get real real hot.
|
Took a while, but finally got this working. @mxstbr the one trouble I ran into was debouncing the
Any chance you could take a look? Without the debounce people's machines are gonna get real real hot. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brianlovin
Jun 27, 2018
Member
Also @uberbryn @mxstbr - if you want to pull this and try it out, let me know what you think. In my opinion it's very very distracting to have this header jump in and out every time you change the thread in the inbox view. But it seems to be quite a challenge to work around that since we're doing so much scroll position handling every time a thread is changed.
|
Also @uberbryn @mxstbr - if you want to pull this and try it out, let me know what you think. In my opinion it's very very distracting to have this header jump in and out every time you change the thread in the inbox view. But it seems to be quite a challenge to work around that since we're doing so much scroll position handling every time a thread is changed. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
uberbryn
Jun 27, 2018
Contributor
Would be cool for it to be more chill when we jump your scroll position as you mentioned, but I also think it's fine as is - it really wouldn't have bothered me other than that you mentioned it.
Other solution I could think of would be transitioning it from 0 opacity to 1 so the motion isn't jumpy, but I really don't think it's that big of a deal. Would prefer shipping and refining later.
Great work, bud! Thanks for grabbing that!
|
Would be cool for it to be more chill when we jump your scroll position as you mentioned, but I also think it's fine as is - it really wouldn't have bothered me other than that you mentioned it. Other solution I could think of would be transitioning it from 0 opacity to 1 so the motion isn't jumpy, but I really don't think it's that big of a deal. Would prefer shipping and refining later. Great work, bud! Thanks for grabbing that! |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Will take a look! |
| + // enable the `bannerIsVisible` state to slide the thread context banner | ||
| + // in from the top of the screen | ||
| + const scrollOffset = e.target.scrollTop; | ||
| + this.setState({ scrollOffset }, () => { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mxstbr
Jun 27, 2018
Member
We shouldn't be putting the scroll offset (which changes every single frame you scroll) into the state, this is what's causing the issues. This isn't even necessary, just don't put it into the state, compare scrollOffset against threadDetailHeight and only set the state if it changes. That way you don't even need to debounce which is kinda annoying for this behavior
I'm taking a look at refactoring this!
mxstbr
Jun 27, 2018
Member
We shouldn't be putting the scroll offset (which changes every single frame you scroll) into the state, this is what's causing the issues. This isn't even necessary, just don't put it into the state, compare scrollOffset against threadDetailHeight and only set the state if it changes. That way you don't even need to debounce which is kinda annoying for this behavior
I'm taking a look at refactoring this!
mxstbr
requested changes
Jun 27, 2018
Needs an optimistic update on the like click—feels bad rn on slow networks/with the remote server overhead. Will also tackle.
| + type ThreadReactions { | ||
| + count: Int! | ||
| + hasReacted: Boolean | ||
| + } |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mxstbr
Jun 27, 2018
Member
Even though you split 'em up, this is still the same schema as the message reactions? I thought we agreed that we should do this one right from the start (to one that let's us show who liked a thread) so we wouldn't have to migrate to a different one in the future?
mxstbr
Jun 27, 2018
Member
Even though you split 'em up, this is still the same schema as the message reactions? I thought we agreed that we should do this one right from the start (to one that let's us show who liked a thread) so we wouldn't have to migrate to a different one in the future?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mxstbr
Jun 27, 2018
Member
Needs an optimistic update on the like click—feels bad rn on slow networks/with the remote server overhead. Will also tackle.
Nevermind, played around with this a bit and it's a pain in the butt. Let's not do this for now
Nevermind, played around with this a bit and it's a pain in the butt. Let's not do this for now |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brianlovin
Jun 27, 2018
Member
Hm, the schema will be:
type ThreadReactions {
count: Int!
hasReacted: Boolean
users: [User]
}
Right? We just add a field inside that type. users could follow the connection spec, or just be an array of user data (obviously will be problematic when we get to hundreds of reactions on a post). So then it would be:
type ReactionUsersConnection {
pageInfo: PageInfo
edges: [ReactionUserEdge]
}
type ReactionUserEdge {
node: User
cursor: String
}
type ThreadReactions {
count: Int!
hasReacted: Boolean
users: ReactionUsersConnection
}
But again, all of this can be added later. Querying would not change except adding the field to the graphql query and extracting it on the frontend with thread.reactions.users.edges.map(({ node: user }) => ...)
|
Hm, the schema will be:
Right? We just add a field inside that type.
But again, all of this can be added later. Querying would not change except adding the field to the graphql query and extracting it on the frontend with |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Let me see if I can handle optimistic update real quick |
brianlovin
added some commits
Jun 27, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brianlovin
Jun 27, 2018
Member
Optimistic updating is pushed, @mxstbr this is ready for a final review!
|
Optimistic updating is pushed, @mxstbr this is ready for a final review! |
mxstbr
approved these changes
Jun 27, 2018
That schema actually makes a lot of sense. This LGTM, let's ship it?!
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brianlovin
Jun 27, 2018
Member
LET'S SHIP IT! Gonna put it on alpha, run the migration, test, then do a prod cut.
|
LET'S SHIP IT! Gonna put it on alpha, run the migration, test, then do a prod cut. |
uberbryn commentedJun 20, 2018
Status
Deploy after merge (delete what needn't be deployed)
Run database migrations (delete if no migration was added)
YES
Release notes for users (delete if codebase-only change)
Related issues (delete if you don't know of any)
Closes #