fix: improve comments performance (#3816)

* fix: improve comments performance
* fix: discussion stores
This commit is contained in:
Mário Nunes
2024-08-06 16:53:03 +08:00
committed by GitHub
parent e595f4bd19
commit bbf6c9e3d0
7 changed files with 91 additions and 55 deletions

View File

@@ -50,7 +50,9 @@ export const CreateComment = (props: Props) => {
}, },
}} }}
> >
{isLoggedIn ? ( {!isLoggedIn ? (
<LoginPrompt />
) : (
<> <>
<Textarea <Textarea
value={comment} value={comment}
@@ -83,22 +85,6 @@ export const CreateComment = (props: Props) => {
{comment.length}/{maxLength} {comment.length}/{maxLength}
</Text> </Text>
</> </>
) : (
<Box sx={{ padding: [3, 4] }}>
<Text data-cy="comments-login-prompt">
Hi there!{' '}
<Link
to="/sign-in"
style={{
textDecoration: 'underline',
color: 'inherit',
}}
>
Login
</Link>{' '}
to leave a comment
</Text>
</Box>
)} )}
</Box> </Box>
</Flex> </Flex>
@@ -121,3 +107,23 @@ export const CreateComment = (props: Props) => {
</Flex> </Flex>
) )
} }
const LoginPrompt = () => {
return (
<Box sx={{ padding: [3, 4] }}>
<Text data-cy="comments-login-prompt">
Hi there!{' '}
<Link
to="/sign-in"
style={{
textDecoration: 'underline',
color: 'inherit',
}}
>
Login
</Link>{' '}
to leave a comment
</Text>
</Box>
)
}

View File

@@ -31,6 +31,7 @@ export const Default: Story = {
onMoreComments={() => null} onMoreComments={() => null}
onSubmit={() => null} onSubmit={() => null}
onSubmitReply={() => Promise.resolve()} onSubmitReply={() => Promise.resolve()}
isSubmitting={false}
isLoggedIn={false} isLoggedIn={false}
/> />
) )
@@ -51,6 +52,7 @@ export const NoComments: Story = {
onMoreComments={() => null} onMoreComments={() => null}
onSubmit={() => null} onSubmit={() => null}
onSubmitReply={() => Promise.resolve()} onSubmitReply={() => Promise.resolve()}
isSubmitting={false}
isLoggedIn={false} isLoggedIn={false}
/> />
) )
@@ -73,6 +75,7 @@ export const LoggedIn: Story = {
onMoreComments={() => null} onMoreComments={() => null}
onSubmit={() => null} onSubmit={() => null}
onSubmitReply={() => Promise.resolve()} onSubmitReply={() => Promise.resolve()}
isSubmitting={false}
isLoggedIn={true} isLoggedIn={true}
/> />
) )
@@ -95,6 +98,7 @@ export const Expandable: Story = {
onMoreComments={() => null} onMoreComments={() => null}
onSubmit={() => null} onSubmit={() => null}
onSubmitReply={() => Promise.resolve()} onSubmitReply={() => Promise.resolve()}
isSubmitting={false}
isLoggedIn={true} isLoggedIn={true}
/> />
) )
@@ -121,6 +125,7 @@ export const WithReplies: Story = {
onMoreComments={() => null} onMoreComments={() => null}
onSubmit={() => null} onSubmit={() => null}
isLoggedIn={true} isLoggedIn={true}
isSubmitting={false}
onSubmitReply={async (commentId, comment) => onSubmitReply={async (commentId, comment) =>
alert(`reply to commentId: ${commentId} with comment: ${comment}`) alert(`reply to commentId: ${commentId} with comment: ${comment}`)
} }

View File

@@ -21,6 +21,7 @@ export interface IProps {
onMoreComments: () => void onMoreComments: () => void
onSubmit: (comment: string) => void onSubmit: (comment: string) => void
onSubmitReply: (_id: string, reply: string) => Promise<void> onSubmitReply: (_id: string, reply: string) => Promise<void>
isSubmitting: boolean
supportReplies?: boolean supportReplies?: boolean
} }
@@ -38,11 +39,12 @@ export const DiscussionContainer = (props: IProps) => {
onMoreComments, onMoreComments,
onSubmit, onSubmit,
isLoggedIn, isLoggedIn,
isSubmitting,
supportReplies = false, supportReplies = false,
} = props } = props
const [commentBeingRepliedTo, setCommentBeingRepliedTo] = useState< const [commentBeingRepliedTo, setCommentBeingRepliedTo] = useState<
null | string string | null
>(null) >(null)
const structuredComments = useMemo( const structuredComments = useMemo(
() => transformToTree(comments), () => transformToTree(comments),
@@ -51,7 +53,8 @@ export const DiscussionContainer = (props: IProps) => {
const handleSetCommentBeingRepliedTo = (commentId: string | null): void => { const handleSetCommentBeingRepliedTo = (commentId: string | null): void => {
if (commentId === commentBeingRepliedTo) { if (commentId === commentBeingRepliedTo) {
return setCommentBeingRepliedTo(null) setCommentBeingRepliedTo(null)
return
} }
setCommentBeingRepliedTo(commentId) setCommentBeingRepliedTo(commentId)
} }
@@ -85,6 +88,7 @@ export const DiscussionContainer = (props: IProps) => {
}} }}
> >
<CreateComment <CreateComment
isLoading={isSubmitting}
maxLength={maxLength} maxLength={maxLength}
comment={comment} comment={comment}
onChange={onChange} onChange={onChange}

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { observer } from 'mobx-react'
import { DiscussionContainer, Loader } from 'oa-components' import { DiscussionContainer, Loader } from 'oa-components'
import { useCommonStores } from 'src/common/hooks/useCommonStores' import { useCommonStores } from 'src/common/hooks/useCommonStores'
import { transformToUserComments } from 'src/common/transformToUserComments' import { transformToUserComments } from 'src/common/transformToUserComments'
@@ -18,9 +19,9 @@ const LOADING_LABEL = 'Loading the awesome discussion'
interface IProps { interface IProps {
sourceType: IDiscussion['sourceType'] sourceType: IDiscussion['sourceType']
sourceId: string sourceId: string
setTotalCommentsCount: (number) => void setTotalCommentsCount: (number: number) => void
canHideComments?: boolean
showComments?: boolean showComments?: boolean
canHideComments?: boolean
primaryContentId?: string | undefined primaryContentId?: string | undefined
} }
@@ -32,7 +33,7 @@ const getHighlightedCommentId = () => {
return filterOutResearchUpdate.replace('#comment:', '') return filterOutResearchUpdate.replace('#comment:', '')
} }
export const DiscussionWrapper = (props: IProps) => { export const DiscussionWrapper = observer((props: IProps) => {
const { const {
canHideComments, canHideComments,
primaryContentId, primaryContentId,
@@ -45,6 +46,7 @@ export const DiscussionWrapper = (props: IProps) => {
const [comment, setComment] = useState('') const [comment, setComment] = useState('')
const [discussion, setDiscussion] = useState<IDiscussion | null>(null) const [discussion, setDiscussion] = useState<IDiscussion | null>(null)
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [isSubmitting, setIsSubmitting] = useState(false)
const { discussionStore } = useCommonStores().stores const { discussionStore } = useCommonStores().stores
const highlightedCommentId = getHighlightedCommentId() const highlightedCommentId = getHighlightedCommentId()
@@ -86,6 +88,7 @@ export const DiscussionWrapper = (props: IProps) => {
const handleEdit = async (_id: string, comment: string) => { const handleEdit = async (_id: string, comment: string) => {
if (!discussion) return if (!discussion) return
setIsSubmitting(true)
const updatedDiscussion = await discussionStore.editComment( const updatedDiscussion = await discussionStore.editComment(
discussion, discussion,
_id, _id,
@@ -96,6 +99,8 @@ export const DiscussionWrapper = (props: IProps) => {
if (updatedDiscussion) { if (updatedDiscussion) {
transformComments(updatedDiscussion) transformComments(updatedDiscussion)
} }
setIsSubmitting(false)
} }
const handleEditRequest = async () => { const handleEditRequest = async () => {
@@ -105,6 +110,7 @@ export const DiscussionWrapper = (props: IProps) => {
const handleDelete = async (_id: string) => { const handleDelete = async (_id: string) => {
if (!discussion) return if (!discussion) return
setIsSubmitting(true)
const updatedDiscussion = await discussionStore.deleteComment( const updatedDiscussion = await discussionStore.deleteComment(
discussion, discussion,
_id, _id,
@@ -114,11 +120,15 @@ export const DiscussionWrapper = (props: IProps) => {
if (updatedDiscussion) { if (updatedDiscussion) {
transformComments(updatedDiscussion) transformComments(updatedDiscussion)
} }
setIsSubmitting(false)
} }
const onSubmit = async (comment: string) => { const onSubmit = async (comment: string) => {
if (!comment || !discussion) return if (!comment || !discussion) return
setIsSubmitting(true)
const updatedDiscussion = await discussionStore.addComment( const updatedDiscussion = await discussionStore.addComment(
discussion, discussion,
comment, comment,
@@ -131,11 +141,15 @@ export const DiscussionWrapper = (props: IProps) => {
if (updatedDiscussion) { if (updatedDiscussion) {
setComment('') setComment('')
} }
setIsSubmitting(false)
} }
const handleSubmitReply = async (commentId: string, reply) => { const handleSubmitReply = async (commentId: string, reply: string) => {
if (!discussion) return if (!discussion) return
setIsSubmitting(true)
const updatedDiscussion = await discussionStore.addComment( const updatedDiscussion = await discussionStore.addComment(
discussion, discussion,
reply, reply,
@@ -146,6 +160,8 @@ export const DiscussionWrapper = (props: IProps) => {
if (updatedDiscussion) { if (updatedDiscussion) {
transformComments(updatedDiscussion) transformComments(updatedDiscussion)
} }
setIsSubmitting(false)
} }
const discussionProps = { const discussionProps = {
@@ -161,6 +177,7 @@ export const DiscussionWrapper = (props: IProps) => {
highlightedCommentId, highlightedCommentId,
onSubmit, onSubmit,
onSubmitReply: handleSubmitReply, onSubmitReply: handleSubmitReply,
isSubmitting,
isLoggedIn: discussionStore?.activeUser isLoggedIn: discussionStore?.activeUser
? !!discussionStore.activeUser ? !!discussionStore.activeUser
: false, : false,
@@ -183,4 +200,4 @@ export const DiscussionWrapper = (props: IProps) => {
)} )}
</> </>
) )
} })

View File

@@ -1,10 +1,10 @@
import { useState } from 'react' import React, { useMemo, useState } from 'react'
import { Button } from 'oa-components' import { Button } from 'oa-components'
import { Box } from 'theme-ui' import { Box } from 'theme-ui'
interface IProps { interface IProps {
children
commentCount: number commentCount: number
children: React.ReactNode | React.ReactNode[]
showComments?: boolean showComments?: boolean
} }
@@ -13,13 +13,9 @@ export const HideDiscussionContainer = ({
commentCount, commentCount,
showComments, showComments,
}: IProps) => { }: IProps) => {
const [viewComments, setViewComments] = useState(showComments || false) const [viewComments, setViewComments] = useState(() => showComments || false)
const onButtonClick = () => { const buttonText = useMemo(() => {
setViewComments(!viewComments)
}
const setButtonText = () => {
if (!viewComments) { if (!viewComments) {
switch (commentCount) { switch (commentCount) {
case 0: case 0:
@@ -32,7 +28,7 @@ export const HideDiscussionContainer = ({
} }
return 'Collapse Comments' return 'Collapse Comments'
} }, [viewComments])
return ( return (
<Box <Box
@@ -53,14 +49,14 @@ export const HideDiscussionContainer = ({
display: 'block', display: 'block',
marginBottom: viewComments ? 2 : 0, marginBottom: viewComments ? 2 : 0,
}} }}
onClick={onButtonClick} onClick={() => setViewComments((prev) => !prev)}
backgroundColor={viewComments ? '#c2daf0' : '#e2edf7'} backgroundColor={viewComments ? '#c2daf0' : '#e2edf7'}
className={viewComments ? 'viewComments' : ''} className={viewComments ? 'viewComments' : ''}
data-cy={`HideDiscussionContainer: button ${ data-cy={`HideDiscussionContainer: button ${
!viewComments && 'open-comments' !viewComments && 'open-comments'
}`} }`}
> >
<>{setButtonText()}</> {buttonText}
</Button> </Button>
{viewComments && children} {viewComments && children}
</Box> </Box>

View File

@@ -74,7 +74,9 @@ export const updateDiscussionMetadata = async (
const researchRef = db.collection('research').doc(primaryContentId) const researchRef = db.collection('research').doc(primaryContentId)
const research = toJS(await researchRef.get('server')) as IResearch.Item const research = toJS(await researchRef.get('server')) as IResearch.Item
if (!research || !research.updates) return if (!research || !research.updates || research.updates.length === 0) {
return
}
const updates = research.updates.map((update) => { const updates = research.updates.map((update) => {
return update._id === sourceId ? { ...update, commentCount } : update return update._id === sourceId ? { ...update, commentCount } : update

View File

@@ -22,11 +22,11 @@ import type {
import type { DocReference } from '../databaseV2/DocReference' import type { DocReference } from '../databaseV2/DocReference'
import type { IRootStore } from '../RootStore' import type { IRootStore } from '../RootStore'
const COLLECTION_NAME = 'discussions' const DISCUSSIONS_COLLECTION = 'discussions'
export class DiscussionStore extends ModuleStore { export class DiscussionStore extends ModuleStore {
constructor(rootStore: IRootStore) { constructor(rootStore: IRootStore) {
super(rootStore, COLLECTION_NAME) super(rootStore, DISCUSSIONS_COLLECTION)
} }
public async fetchOrCreateDiscussionBySource( public async fetchOrCreateDiscussionBySource(
@@ -37,7 +37,7 @@ export class DiscussionStore extends ModuleStore {
const foundDiscussion = const foundDiscussion =
toJS( toJS(
await this.db await this.db
.collection<IDiscussion>(COLLECTION_NAME) .collection<IDiscussion>(DISCUSSIONS_COLLECTION)
.getWhere('sourceId', '==', sourceId), .getWhere('sourceId', '==', sourceId),
)[0] || null )[0] || null
@@ -74,7 +74,7 @@ export class DiscussionStore extends ModuleStore {
} }
const dbRef = this.db const dbRef = this.db
.collection<IDiscussion>(COLLECTION_NAME) .collection<IDiscussion>(DISCUSSIONS_COLLECTION)
.doc(newDiscussion._id) .doc(newDiscussion._id)
return await this._updateDiscussion(dbRef, newDiscussion) return await this._updateDiscussion(dbRef, newDiscussion)
@@ -91,7 +91,7 @@ export class DiscussionStore extends ModuleStore {
if (user && comment) { if (user && comment) {
const dbRef = this.db const dbRef = this.db
.collection<IDiscussion>(COLLECTION_NAME) .collection<IDiscussion>(DISCUSSIONS_COLLECTION)
.doc(discussion._id) .doc(discussion._id)
const currentDiscussion = toJS(await dbRef.get('server')) const currentDiscussion = toJS(await dbRef.get('server'))
@@ -120,7 +120,8 @@ export class DiscussionStore extends ModuleStore {
newComment, newComment,
) )
await this._addNotifications(newComment, currentDiscussion) // Do not await so it doesn't block adding a comment
this._addNotifications(newComment, currentDiscussion)
return this._updateDiscussion(dbRef, currentDiscussion) return this._updateDiscussion(dbRef, currentDiscussion)
} }
@@ -143,7 +144,7 @@ export class DiscussionStore extends ModuleStore {
if (user && comment) { if (user && comment) {
const dbRef = this.db const dbRef = this.db
.collection<IDiscussion>(COLLECTION_NAME) .collection<IDiscussion>(DISCUSSIONS_COLLECTION)
.doc(discussion._id) .doc(discussion._id)
const currentDiscussion = toJS(await dbRef.get('server')) const currentDiscussion = toJS(await dbRef.get('server'))
@@ -185,7 +186,7 @@ export class DiscussionStore extends ModuleStore {
if (user) { if (user) {
const dbRef = this.db const dbRef = this.db
.collection<IDiscussion>(COLLECTION_NAME) .collection<IDiscussion>(DISCUSSIONS_COLLECTION)
.doc(discussion._id) .doc(discussion._id)
const currentDiscussion = toJS(await dbRef.get('server')) const currentDiscussion = toJS(await dbRef.get('server'))
@@ -271,14 +272,16 @@ export class DiscussionStore extends ModuleStore {
) )
if (update.collaborators) { if (update.collaborators) {
await update.collaborators.map((collaborator) => { await Promise.all(
this.userNotificationsStore.triggerNotification( update.collaborators.map((collaborator) => {
'new_comment_discussion', this.userNotificationsStore.triggerNotification(
collaborator, 'new_comment_discussion',
url, collaborator,
research.title, url,
) research.title,
}) )
}),
)
} }
} }
return return
@@ -344,7 +347,10 @@ export class DiscussionStore extends ModuleStore {
discussion: IDiscussion, discussion: IDiscussion,
): Promise<IDiscussionDB | null> { ): Promise<IDiscussionDB | null> {
await dbRef.set({ ...cloneDeep(discussion) }) await dbRef.set({ ...cloneDeep(discussion) })
await updateDiscussionMetadata(this.db, discussion)
// Do not await so it doesn't block adding a comment
updateDiscussionMetadata(this.db, discussion)
const updatedDiscussion = toJS(await dbRef.get('server')) const updatedDiscussion = toJS(await dbRef.get('server'))
return updatedDiscussion ? updatedDiscussion : null return updatedDiscussion ? updatedDiscussion : null
@@ -361,8 +367,8 @@ export class DiscussionStore extends ModuleStore {
) )
return discussion return discussion
const dbRef = await this.db const dbRef = this.db
.collection<IDiscussion>(COLLECTION_NAME) .collection<IDiscussion>(DISCUSSIONS_COLLECTION)
.doc(discussion._id) .doc(discussion._id)
return await this._updateDiscussion(dbRef, { return await this._updateDiscussion(dbRef, {