diff --git a/.eslintignore b/.eslintignore index 21af2df92..fc0214dd2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -25,5 +25,6 @@ plugins/* !plugins/talk-plugin-moderation-actions !plugins/talk-plugin-toxic-comments !plugins/talk-plugin-remember-sort +!plugins/talk-plugin-deep-reply-count node_modules diff --git a/.gitignore b/.gitignore index bba75a377..bcc7c89b1 100644 --- a/.gitignore +++ b/.gitignore @@ -42,5 +42,6 @@ plugins/* !plugins/talk-plugin-moderation-actions !plugins/talk-plugin-toxic-comments !plugins/talk-plugin-remember-sort +!plugins/talk-plugin-deep-reply-count **/node_modules/* diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 916c0ade5..1c7f349d8 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -369,7 +369,8 @@ type Comment { # the replies that were made to the comment. replies(query: RepliesQuery = {}): CommentConnection! - # The count of replies on a comment. + # replyCount is the number of replies with a depth of 1. Only direct replies + # to this comment are counted. replyCount: Int # Actions completed on the parent. Requires the `ADMIN` role. diff --git a/plugins/talk-plugin-deep-reply-count/index.js b/plugins/talk-plugin-deep-reply-count/index.js new file mode 100644 index 000000000..92c7a06ef --- /dev/null +++ b/plugins/talk-plugin-deep-reply-count/index.js @@ -0,0 +1,74 @@ +const _ = require('lodash'); +const DataLoader = require('dataloader'); + +const CommentModel = require('../../models/comment'); + +console.warn('Enabling the talk-plugin-deep-reply-count plugin introduces a signifigant performance impact on larger sites, use with care.'); + +// genDeepCommentCount will return the deep comment count for a given parent id. +const genDeepCommentCount = async (context, parent_ids) => { + + // Get all the replies to the parent comments. + const replies = await CommentModel + .find({ + parent_id: { + $in: _.uniq(parent_ids), + }, + }, { + id: 1, + reply_count: 1, + parent_id: 1, + }); + + // Get all the replies that have comments on them. + const commentedOnReplies = replies.filter(({reply_count}) => { + return reply_count && reply_count > 0; + }); + + let deepReplyCount = []; + + // And if there were any.. + if (commentedOnReplies.length > 0) { + + // Load the reply count for each of them. + deepReplyCount = await context.loaders.Comments.getDeepCount.loadMany(_.uniq(commentedOnReplies.map(({id}) => { + return id; + }))); + } + + // Get all the direct replies to the parent comments. + const allDirectReplies = _.groupBy(replies, 'parent_id'); + + // Collect all the ancestor replies. + const allAncestorReplies = _.groupBy(_.zip(commentedOnReplies, deepReplyCount), ([{parent_id}]) => { + return parent_id; + }); + + // Return the replies in an array matching that of the input parent_ids array. + return parent_ids.map((parent_id) => { + + // Get the direct replies to this comment. + const directReplies = parent_id in allDirectReplies ? allDirectReplies[parent_id] : []; + const ancestorReplies = parent_id in allAncestorReplies ? allAncestorReplies[parent_id] : []; + + // Reduce this array. + return ancestorReplies.reduce((acc, [, count]) => { + return acc + count; + }, directReplies.length); + }); +}; + +module.exports = { + loaders: (context) => ({ + Comments: { + getDeepCount: new DataLoader((parent_ids) => genDeepCommentCount(context, parent_ids)), + } + }), + resolvers: { + Comment: { + replyCount({id}, args, {loaders: {Comments}}) { + return Comments.getDeepCount.load(id); + } + } + } +};