diff --git a/src/core/server/graph/tenant/loaders/comments.ts b/src/core/server/graph/tenant/loaders/comments.ts index 23b0da93b..c12fe4296 100644 --- a/src/core/server/graph/tenant/loaders/comments.ts +++ b/src/core/server/graph/tenant/loaders/comments.ts @@ -4,12 +4,12 @@ import Context from "talk-server/graph/tenant/context"; import { AssetToCommentsArgs, CommentToRepliesArgs, + GQLActionPresence, GQLCOMMENT_SORT, } from "talk-server/graph/tenant/schema/__generated__/types"; import { ACTION_ITEM_TYPE, - ActionCounts, - retrieveManyAuthoredActionCounts, + retrieveManyAuthoredActionPresence, } from "talk-server/models/actions"; import { retrieveCommentAssetConnection, @@ -21,9 +21,9 @@ export default (ctx: Context) => ({ comment: new DataLoader((ids: string[]) => retrieveManyComments(ctx.mongo, ctx.tenant.id, ids) ), - retrieveAuthoredActionCounts: new DataLoader( + retrieveAuthoredActionPresence: new DataLoader( (itemIDs: string[]) => - retrieveManyAuthoredActionCounts( + retrieveManyAuthoredActionPresence( ctx.mongo, ctx.tenant.id, // This should only ever be accessed when a user is logged in. diff --git a/src/core/server/graph/tenant/resolvers/comment.ts b/src/core/server/graph/tenant/resolvers/comment.ts index ef6dcb2cf..4c552727b 100644 --- a/src/core/server/graph/tenant/resolvers/comment.ts +++ b/src/core/server/graph/tenant/resolvers/comment.ts @@ -19,8 +19,8 @@ const Comment: GQLCommentTypeResolver = { replies: (comment, input, ctx) => ctx.loaders.Comments.forParent(comment.asset_id, comment.id, input), actionCounts: comment => decodeActionCounts(comment.action_counts), - authoredActionCounts: (comment, input, ctx) => - ctx.loaders.Comments.retrieveAuthoredActionCounts.load(comment.id), + authoredActionPresence: (comment, input, ctx) => + ctx.loaders.Comments.retrieveAuthoredActionPresence.load(comment.id), }; export default Comment; diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 5ae9f95e9..4e1a05047 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -112,11 +112,25 @@ enum COMMENT_FLAG_REASON { COMMENT_DETECTED_SUSPECT_WORD } +""" +ReactionActionCounts stores all the counts for the counts for the reaction +action on a given item. +""" type ReactionActionCounts { + """ + total is the total number of reactions against a given item. + """ total: Int! } +""" +DontAgreeActionCounts stores all the counts for the counts for the dontAgree +action on a given item. +""" type DontAgreeActionCounts { + """ + total is the total number of dontAgree actions against a given item. + """ total: Int! } @@ -132,15 +146,65 @@ type FlagReasonActionCounts { COMMENT_DETECTED_SUSPECT_WORD: Int! } +""" +FlagActionCounts stores all the counts for the counts for the flag action on a +given item and the reason counts. +""" type FlagActionCounts { + """ + total is the total number of flags against a given item. + """ total: Int! + + """ + reasons stores the counts for the various reasons that an item could be + flagged for. + """ reasons: FlagReasonActionCounts! } +""" +ActionCounts returns the counts of each action for an item. +""" type ActionCounts { + """ + reaction returns the counts for the reaction action on an item. + """ reaction: ReactionActionCounts! - dontagree: DontAgreeActionCounts! - flag: FlagActionCounts! + + """ + dontAgree returns the counts for the dontAgree action on an item. This edge is + restricted to administrators and moderators. + """ + dontAgree: DontAgreeActionCounts! @auth(roles: [ADMIN, MODERATOR]) + + """ + flag returns the counts for the flag action on an item. This edge is + restricted to administrators and moderators. + """ + flag: FlagActionCounts! @auth(roles: [ADMIN, MODERATOR]) +} + +""" +ActionPresence returns whether or not a given item has one of the following +actions on it. This is typically used to determine if a given user has left +one of the following actions. +""" +type ActionPresence { + """ + reaction is true when a reaction action was left on an item. + """ + reaction: Boolean! + + """ + dontAgree is true when a dontAgree action was left on an item. + """ + dontAgree: Boolean! + + """ + flag is true when a flag action was left on an item. + """ + flag: Boolean! } ################################################################################ @@ -769,10 +833,10 @@ type Comment { actionCounts: ActionCounts! """ - authoredActionCounts stores the counts of all the actions for the Comment as - it is written by the current User. + authoredActionPresence stores the presense information for all the actions + left by the current User on this Comment. """ - authoredActionCounts: ActionCounts! @auth + authoredActionPresence: ActionPresence! @auth } type PageInfo { @@ -940,7 +1004,7 @@ type Query { """ me is the current logged in User. """ - me: User @auth + me: User """ settings is the Settings for a given Tenant. diff --git a/src/core/server/models/__snapshots__/actions.spec.ts.snap b/src/core/server/models/__snapshots__/actions.spec.ts.snap index 0d5d34ad0..df5d294c3 100644 --- a/src/core/server/models/__snapshots__/actions.spec.ts.snap +++ b/src/core/server/models/__snapshots__/actions.spec.ts.snap @@ -12,10 +12,10 @@ Object { exports[`#decodeActionCounts parses the action counts correctly 2`] = ` Object { - "DONT_AGREE": Object { + "dontAgree": Object { "total": 1, }, - "FLAG": Object { + "flag": Object { "reasons": Object { "COMMENT_DETECTED_BANNED_WORD": 1, "COMMENT_DETECTED_BODY_COUNT": 1, @@ -29,7 +29,7 @@ Object { }, "total": 2, }, - "REACTION": Object { + "reaction": Object { "total": 3, }, } diff --git a/src/core/server/models/actions.ts b/src/core/server/models/actions.ts index acc6f150e..7cd7a1ba6 100644 --- a/src/core/server/models/actions.ts +++ b/src/core/server/models/actions.ts @@ -4,7 +4,11 @@ import { Db } from "mongodb"; import uuid from "uuid"; import { Omit, Sub } from "talk-common/types"; -import { GQLCOMMENT_FLAG_REASON } from "talk-server/graph/tenant/schema/__generated__/types"; +import { + GQLActionCounts, + GQLActionPresence, + GQLCOMMENT_FLAG_REASON, +} from "talk-server/graph/tenant/schema/__generated__/types"; import { FilterQuery } from "talk-server/models/query"; import { TenantResource } from "talk-server/models/tenant"; @@ -37,14 +41,6 @@ export interface ActionCountGroup { total: number; } -export interface ActionCounts { - [ACTION_TYPE.REACTION]: ActionCountGroup; - [ACTION_TYPE.DONT_AGREE]: ActionCountGroup; - [ACTION_TYPE.FLAG]: ActionCountGroup & { - reasons: Record; - }; -} - export enum ACTION_ITEM_TYPE { COMMENTS = "COMMENTS", } @@ -194,35 +190,49 @@ export async function createActions( } /** - * retrieveManyAuthoredActionCounts returns the action counts for a specific + * retrieveManyAuthoredActionPresence returns the action presence for a specific * user. */ -export async function retrieveManyAuthoredActionCounts( +export async function retrieveManyAuthoredActionPresence( mongo: Db, tenantID: string, userID: string | null, itemType: ACTION_ITEM_TYPE, itemIDs: string[] -) { - const cursor = await collection(mongo).find({ - tenant_id: tenantID, - user_id: userID, - item_type: itemType, - item_id: { $in: itemIDs }, - }); +): Promise { + const cursor = await collection(mongo).find( + { + tenant_id: tenantID, + user_id: userID, + item_type: itemType, + item_id: { $in: itemIDs }, + }, + { + // We only need the item_id and action_type from the database. + projection: { + item_id: 1, + action_type: 1, + }, + } + ); const actions = await cursor.toArray(); // For each of the actions returned by the query, group the actions by the - // item id. Then compute the action counts for each of these action groups, - // which can be used to determine existence of actions. + // item id. Then compute the action presence for each of the actions. return itemIDs .map(itemID => actions.filter(action => action.item_id === itemID)) .map(itemActions => itemActions.reduce( - (actionCounts, { action_type, reason }) => - incrementActionCounts(actionCounts, action_type, reason), - createEmptyActionCounts() + (actionPresence, { action_type }) => ({ + ...actionPresence, + [action_type.toLowerCase()]: true, + }), + { + reaction: false, + dontAgree: false, + flag: false, + } ) ); } @@ -385,16 +395,18 @@ function decodeActionCountKey(key: string): DecodedActionCountKey { /** * createEmptyActionCounts creates a default/empty set of action counts. */ -function createEmptyActionCounts(): ActionCounts { +function createEmptyActionCounts(): GQLActionCounts { return { - [ACTION_TYPE.REACTION]: { + reaction: { total: 0, }, - [ACTION_TYPE.DONT_AGREE]: { + dontAgree: { total: 0, }, - [ACTION_TYPE.FLAG]: { + flag: { total: 0, + // Note that this isn't type checked.. We force it because TS can't + // understand the reduce. reasons: Object.keys(GQLCOMMENT_FLAG_REASON).reduce( (reasons, reason) => ({ ...reasons, @@ -414,9 +426,9 @@ function createEmptyActionCounts(): ActionCounts { */ export function decodeActionCounts( encodedActionCounts: EncodedActionCounts -): ActionCounts { +): GQLActionCounts { // Default all the action counts to zero. - const actionCounts: ActionCounts = createEmptyActionCounts(); + const actionCounts: GQLActionCounts = createEmptyActionCounts(); // Loop over all the encoded action counts to extract each of the action // counts as they are encoded. @@ -432,26 +444,26 @@ export function decodeActionCounts( } function incrementActionCounts( - actionCounts: ActionCounts, + actionCounts: GQLActionCounts, actionType: ACTION_TYPE, reason: GQLCOMMENT_FLAG_REASON | undefined, count: number = 1 ) { switch (actionType) { case ACTION_TYPE.REACTION: - actionCounts[ACTION_TYPE.REACTION].total += count; + actionCounts.reaction.total += count; break; case ACTION_TYPE.DONT_AGREE: - actionCounts[ACTION_TYPE.DONT_AGREE].total += count; + actionCounts.dontAgree.total += count; break; case ACTION_TYPE.FLAG: // When we have a reason, we are incrementing for that particular reason // rather than incrementing for the total. If we don't have a reason, we // just got the updated reason. if (reason) { - actionCounts[ACTION_TYPE.FLAG].reasons[reason] += count; + actionCounts.flag.reasons[reason] += count; } else { - actionCounts[ACTION_TYPE.FLAG].total += count; + actionCounts.flag.total += count; } break; default: