mirror of
https://github.com/wassname/talk.git
synced 2026-07-19 11:28:50 +08:00
feat: presence and count improvements
This commit is contained in:
@@ -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<string, ActionCounts>(
|
||||
retrieveAuthoredActionPresence: new DataLoader<string, GQLActionPresence>(
|
||||
(itemIDs: string[]) =>
|
||||
retrieveManyAuthoredActionCounts(
|
||||
retrieveManyAuthoredActionPresence(
|
||||
ctx.mongo,
|
||||
ctx.tenant.id,
|
||||
// This should only ever be accessed when a user is logged in.
|
||||
|
||||
@@ -19,8 +19,8 @@ const Comment: GQLCommentTypeResolver<Comment> = {
|
||||
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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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<GQLCOMMENT_FLAG_REASON, number>;
|
||||
};
|
||||
}
|
||||
|
||||
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<GQLActionPresence[]> {
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user