From 1bd841ec015cf1d4d47e39ce3f7504e1f4f75e96 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 6 Sep 2018 16:24:34 -0600 Subject: [PATCH 01/53] feat: initial action implementations --- .../server/graph/tenant/schema/schema.graphql | 16 +- src/core/server/models/actions.spec.ts | 112 ++++++++++ src/core/server/models/actions.ts | 192 +++++++++++++++++- src/core/server/models/comment.ts | 28 +++ src/core/server/services/comments/index.ts | 72 ++++++- .../services/comments/moderation/index.ts | 10 +- 6 files changed, 416 insertions(+), 14 deletions(-) create mode 100644 src/core/server/models/actions.spec.ts diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 79d9a73a8..9d777cf10 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -31,8 +31,22 @@ scalar Cursor ################################################################################ enum ACTION_TYPE { + """ + REACTION corresponds to a reaction to a comment from a user. + """ + REACTION + + """ + FLAG corresponds to a flag action that indicates that the given resource needs + moderator attention. + """ FLAG - DONTAGREE + + """ + DONT_AGREE corresponds to when a user marks a given comment that they don't + agree with. + """ + DONT_AGREE } enum ACTION_ITEM_TYPE { diff --git a/src/core/server/models/actions.spec.ts b/src/core/server/models/actions.spec.ts new file mode 100644 index 000000000..c1bf862ce --- /dev/null +++ b/src/core/server/models/actions.spec.ts @@ -0,0 +1,112 @@ +import { + GQLACTION_GROUP, + GQLACTION_ITEM_TYPE, + GQLACTION_TYPE, +} from "talk-server/graph/tenant/schema/__generated__/types"; +import { + Action, + generateActionCounts, + validateAction, +} from "talk-server/models/actions"; + +describe("#generateActionCounts", () => { + it("generates the action counts correctly", () => { + const actions = [ + { action_type: GQLACTION_TYPE.DONT_AGREE }, + { + action_type: GQLACTION_TYPE.FLAG, + group_id: GQLACTION_GROUP.BANNED_WORD, + }, + { + action_type: GQLACTION_TYPE.FLAG, + group_id: GQLACTION_GROUP.BODY_COUNT, + }, + ]; + const actionCounts = generateActionCounts(...(actions as Action[])); + + expect(actionCounts).toEqual({ + [GQLACTION_TYPE.DONT_AGREE.toLowerCase()]: 1, + [GQLACTION_TYPE.FLAG.toLowerCase()]: 2, + [GQLACTION_TYPE.FLAG.toLowerCase() + + "_" + + GQLACTION_GROUP.BANNED_WORD.toLowerCase()]: 1, + [GQLACTION_TYPE.FLAG.toLowerCase() + + "_" + + GQLACTION_GROUP.BODY_COUNT.toLowerCase()]: 1, + }); + }); +}); + +describe("#validateAction", () => { + it("allows a valid action", () => { + const actions = [ + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.REACTION, + }, + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.DONT_AGREE, + }, + + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.FLAG, + group_id: GQLACTION_GROUP.SPAM_COMMENT, + }, + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.FLAG, + group_id: GQLACTION_GROUP.TOXIC_COMMENT, + }, + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.FLAG, + group_id: GQLACTION_GROUP.BODY_COUNT, + }, + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.FLAG, + group_id: GQLACTION_GROUP.TRUST, + }, + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.FLAG, + group_id: GQLACTION_GROUP.LINKS, + }, + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.FLAG, + group_id: GQLACTION_GROUP.BANNED_WORD, + }, + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.FLAG, + group_id: GQLACTION_GROUP.SUSPECT_WORD, + }, + ]; + + for (const action of actions) { + validateAction(action as Action); + } + }); + + it("does not allow an invalid action", () => { + const actions = [ + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.DONT_AGREE, + group_id: GQLACTION_GROUP.SPAM_COMMENT, + }, + { + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + action_type: GQLACTION_TYPE.DONT_AGREE, + group_id: GQLACTION_GROUP.BODY_COUNT, + }, + ]; + + for (const action of actions) { + expect(() => validateAction(action as Action)).toThrow(); + } + }); +}); diff --git a/src/core/server/models/actions.ts b/src/core/server/models/actions.ts index d162b1300..ed4555448 100644 --- a/src/core/server/models/actions.ts +++ b/src/core/server/models/actions.ts @@ -1,12 +1,22 @@ +import { Db } from "mongodb"; +import uuid from "uuid"; + +import { Omit, Sub } from "talk-common/types"; import { GQLACTION_GROUP, GQLACTION_ITEM_TYPE, GQLACTION_TYPE, } from "talk-server/graph/tenant/schema/__generated__/types"; +import { FilterQuery } from "talk-server/models/query"; +import { TenantResource } from "talk-server/models/tenant"; + +function collection(db: Db) { + return db.collection>("actions"); +} export type ActionCounts = Record; -export interface Action { +export interface Action extends TenantResource { readonly id: string; action_type: GQLACTION_TYPE; item_type: GQLACTION_ITEM_TYPE; @@ -16,3 +26,183 @@ export interface Action { created_at: Date; metadata?: Record; } + +const validActionCombinations: Record< + GQLACTION_ITEM_TYPE, + Record | true> +> = { + [GQLACTION_ITEM_TYPE.COMMENTS]: { + [GQLACTION_TYPE.REACTION]: true, + [GQLACTION_TYPE.DONT_AGREE]: true, + [GQLACTION_TYPE.FLAG]: { + [GQLACTION_GROUP.SPAM_COMMENT]: true, + [GQLACTION_GROUP.TOXIC_COMMENT]: true, + [GQLACTION_GROUP.BODY_COUNT]: true, + [GQLACTION_GROUP.TRUST]: true, + [GQLACTION_GROUP.LINKS]: true, + [GQLACTION_GROUP.BANNED_WORD]: true, + [GQLACTION_GROUP.SUSPECT_WORD]: true, + }, + }, +}; + +export function validateAction( + action: Pick +) { + if (!(action.item_type in validActionCombinations)) { + throw new Error("invalid item_type"); + } + + const itemType = validActionCombinations[action.item_type]; + if (!(action.action_type in itemType)) { + throw new Error("invalid action_type"); + } + + const actionType = itemType[action.action_type]; + if (action.group_id) { + if (actionType === true) { + throw new Error("invalid action_type"); + } + + if (!(action.group_id in actionType)) { + throw new Error("invalid action_type"); + } + } else if (actionType !== true) { + throw new Error("invalid action_type"); + } +} + +export type CreateActionInput = Omit; + +export interface CreateActionResultObject { + /** + * action contains the resultant action that was created. + */ + action: Action; + + /** + * wasUpserted when true, indicates that this action was just newly created. + * When false, it indicates that this action was just looked up, and had + * existed prior to the `createAction` call. + */ + wasUpserted: boolean; +} + +export async function createAction( + mongo: Db, + tenantID: string, + input: CreateActionInput +): Promise { + // Validate that the action is valid, if it isn't, this will throw an error. + validateAction(input); + + // Create a new ID for the action. + const id = uuid.v4(); + + // defaults are the properties set by the application when a new action is + // created. + const defaults: Sub = { + id, + tenant_id: tenantID, + created_at: new Date(), + }; + + // Merge the defaults with the input. + const action: Readonly = { + ...defaults, + ...input, + }; + + // This filter ensures that a given user can't flag/respect a given user more + // than once. + const filter: FilterQuery = { + action_type: input.action_type, + item_type: input.item_type, + item_id: input.item_id, + group_id: input.group_id, + user_id: input.user_id, + }; + + // Create the upsert/update operation. + const update: { $setOnInsert: Readonly } = { + $setOnInsert: action, + }; + + // Insert the action into the database using an upsert operation. + const result = await collection(mongo).findOneAndUpdate(filter, update, { + // We are using this to create a action, so we need to upsert it. + upsert: true, + + // False to return the updated document instead of the original document. + // This lets us detect if the document was updated or not. + returnOriginal: false, + }); + + // Check to see if this was a new action that was upserted, or one was found + // that matched existing records. We are sure here that the record exists + // because we're returning the updated document and performing an upsert + // operation. + + // Because it's relevant that we know that the action was just created, or + // was just looked up, we need to return the action with an object that + // indicates if it was upserted. + const wasUpserted = result.value!.id === id; + + // Return the action that was created/found with a boolean indicating if this + // action was just upserted (and therefore was newly created). + return { + action: result.value!, + wasUpserted, + }; +} + +export async function createActions( + mongo: Db, + tenantID: string, + inputs: CreateActionInput[] +): Promise { + return Promise.all(inputs.map(input => createAction(mongo, tenantID, input))); +} + +/** + * generateActionCounts will take a list of actions, and generate action counts + * from it. + * + * @param actions list of actions to generate the action counts from + */ +export function generateActionCounts(...actions: Action[]): ActionCounts { + const actionCounts: ActionCounts = {}; + + /** + * increment the key in the action counts variable. + */ + function incr(...keys: string[]) { + const key = keys.join("_"); + if (key in actionCounts) { + actionCounts[key]++; + } else { + actionCounts[key] = 1; + } + } + + function transform(action: Action) { + const actionType = action.action_type.toLowerCase(); + + // Add the action type to the action counts. + incr(actionType); + + // Check if the group id is set. + const groupID = action.group_id && action.group_id.toLowerCase(); + if (groupID) { + // Add the action type to the action counts. + incr(actionType, groupID); + } + } + + // Loop over the actions, and increment them. + for (const action of actions) { + transform(action); + } + + return actionCounts; +} diff --git a/src/core/server/models/comment.ts b/src/core/server/models/comment.ts index d2c8e03a6..91fa5c903 100644 --- a/src/core/server/models/comment.ts +++ b/src/core/server/models/comment.ts @@ -2,6 +2,7 @@ import { Db } from "mongodb"; import uuid from "uuid"; import { Omit, Sub } from "talk-common/types"; +import { dotize } from "talk-common/utils/dotize"; import { GQLCOMMENT_SORT, GQLCOMMENT_STATUS, @@ -359,3 +360,30 @@ function applyInputToQuery(input: ConnectionInput, query: Query) { break; } } + +/** + * updateCommentActionCounts will update the given comment's action counts. + * + * @param mongo the database handle + * @param tenantID the id of the tenant + * @param id the id of the comment being updated + * @param actionCounts the action counts to merge into the comment + */ +export async function updateCommentActionCounts( + mongo: Db, + tenantID: string, + id: string, + actionCounts: ActionCounts +) { + const result = await collection(mongo).findOneAndUpdate( + { id, tenant_id: tenantID }, + // Update all the specific action counts that are associated with each of + // the counts. + { $inc: dotize({ action_counts: actionCounts }) }, + // False to return the updated document instead of the original + // document. + { returnOriginal: false } + ); + + return result.value; +} diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index b356b919b..a2317edb8 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -1,13 +1,21 @@ import { Db } from "mongodb"; import { Omit } from "talk-common/types"; +import { GQLACTION_ITEM_TYPE } from "talk-server/graph/tenant/schema/__generated__/types"; +import { + CreateActionInput, + createActions, + generateActionCounts, +} from "talk-server/models/actions"; import { retrieveAsset } from "talk-server/models/asset"; import { + Comment, createComment, CreateCommentInput, editComment, EditCommentInput, retrieveComment, + updateCommentActionCounts, } from "talk-server/models/comment"; import { Tenant } from "talk-server/models/tenant"; import { User } from "talk-server/models/user"; @@ -47,7 +55,7 @@ export async function create( } // Run the comment through the moderation phases. - const { status, metadata } = await processForModeration({ + const { actions, status, metadata } = await processForModeration({ asset, tenant, comment: input, @@ -55,17 +63,71 @@ export async function create( req, }); - // TODO: (wyattjoh) use the actions somehow. - - const comment = await createComment(mongo, tenant.id, { + // Create the comment! + let comment = await createComment(mongo, tenant.id, { ...input, status, action_counts: {}, metadata, }); + if (actions.length > 0) { + // The actions coming from the moderation phases didn't know the item_id + // at the time, and we didn't want the repetitive nature of adding the + // item_type each time, so this mapping function adds them! + const inputs = actions.map( + (action): CreateActionInput => ({ + ...action, + item_id: comment.id, + item_type: GQLACTION_ITEM_TYPE.COMMENTS, + }) + ); + + // Insert and handle creating the actions. + comment = await addCommentActions(mongo, tenant, comment, inputs); + } + if (input.parent_id) { - // TODO: update reply count of parent. + // TODO: (wyattjoh) update reply count of parent. + } + + return comment; +} + +async function addCommentActions( + mongo: Db, + tenant: Tenant, + comment: Readonly, + inputs: CreateActionInput[] +): Promise> { + // Create each of the actions, returning each of the action results. + const results = await createActions(mongo, tenant.id, inputs); + + // Get the actions that were upserted. + const upsertedActions = results + .filter(({ wasUpserted }) => wasUpserted) + .map(({ action }) => action); + + if (upsertedActions.length > 0) { + // Compute the action counts. + const actionCounts = generateActionCounts(...upsertedActions); + + // Update the comment action counts here. + const updatedComment = await updateCommentActionCounts( + mongo, + tenant.id, + comment.id, + actionCounts + ); + + // Check to see if there was an actual comment returned (there should + // have been, we just created it!). + if (!updatedComment) { + // TODO: (wyattjoh) return a better error. + throw new Error("could not update comment action counts"); + } + + return updatedComment; } return comment; diff --git a/src/core/server/services/comments/moderation/index.ts b/src/core/server/services/comments/moderation/index.ts index d46be2c59..cc4d91a34 100644 --- a/src/core/server/services/comments/moderation/index.ts +++ b/src/core/server/services/comments/moderation/index.ts @@ -1,6 +1,6 @@ import { Omit, Promiseable } from "talk-common/types"; import { GQLCOMMENT_STATUS } from "talk-server/graph/tenant/schema/__generated__/types"; -import { Action } from "talk-server/models/actions"; +import { CreateActionInput } from "talk-server/models/actions"; import { Asset } from "talk-server/models/asset"; import { Comment } from "talk-server/models/comment"; import { Tenant } from "talk-server/models/tenant"; @@ -9,14 +9,10 @@ import { Request } from "talk-server/types/express"; import { moderationPhases } from "./phases"; -// TODO: (wyattjoh) move into actions module once we have action methods. -export type CreateAction = Omit< - Action, - "id" | "item_type" | "item_id" | "created_at" ->; +export type ModerationAction = Omit; export interface PhaseResult { - actions: CreateAction[]; + actions: ModerationAction[]; status: GQLCOMMENT_STATUS; metadata: Record; } From 7dbd2fae91d91be051c1fb46cc5a24a0db85cd9a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 11 Sep 2018 15:53:08 -0600 Subject: [PATCH 02/53] feat: refactored group_id -> reason --- .../server/graph/tenant/schema/schema.graphql | 189 ++++++++++++++++-- src/core/server/models/actions.spec.ts | 105 +++++----- src/core/server/models/actions.ts | 123 +++++++----- .../comments/moderation/index.spec.ts | 20 +- .../moderation/phases/commentLength.ts | 8 +- .../comments/moderation/phases/karma.ts | 8 +- .../comments/moderation/phases/links.ts | 8 +- .../comments/moderation/phases/spam.ts | 8 +- .../comments/moderation/phases/toxic.ts | 8 +- .../comments/moderation/phases/wordlist.ts | 12 +- 10 files changed, 327 insertions(+), 162 deletions(-) diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 9d777cf10..404af2046 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -30,37 +30,184 @@ scalar Cursor ## Actions ################################################################################ -enum ACTION_TYPE { - """ - REACTION corresponds to a reaction to a comment from a user. - """ - REACTION +""" +CommentAction describes an Action that is left on a Comment. +""" +interface CommentAction { + id: ID! """ - FLAG corresponds to a flag action that indicates that the given resource needs - moderator attention. + comment is the Comment that the CommentAction is about. """ - FLAG + comment: Comment! """ - DONT_AGREE corresponds to when a user marks a given comment that they don't - agree with. + User when available indicates the user that left the CommentAction, otherwise + it is implied that the CommentAction was left by the system. """ - DONT_AGREE + user: User + + """ + createdAt is the time that the CommentAction was created. + """ + createdAt: Time! } -enum ACTION_ITEM_TYPE { - COMMENTS +""" +COMMENT_FLAG_REPORTED_REASON is a reason that is reported by a User on a +Comment. +""" +enum COMMENT_FLAG_REPORTED_REASON { + """ + COMMENT_REPORTED_OFFENSIVE is used when a User reported a Comment as being + offensive. + """ + COMMENT_REPORTED_OFFENSIVE + + """ + COMMENT_REPORTED_SPAM is used when a User reported a Comment as appearing like + spam. + """ + COMMENT_REPORTED_SPAM } -enum ACTION_GROUP { - SPAM_COMMENT - TOXIC_COMMENT - BODY_COUNT - TRUST - LINKS - BANNED_WORD - SUSPECT_WORD +""" +COMMENT_FLAG_DETECTED_REASON is a reason that is detected by the system on a +Comment. +""" +enum COMMENT_FLAG_DETECTED_REASON { + """ + COMMENT_DETECTED_TOXIC is used when the Comment was detected as being toxic by + the system. + """ + COMMENT_DETECTED_TOXIC + + """ + COMMENT_DETECTED_SPAM is used when the Comment was detected as having spam by + the system. + """ + COMMENT_DETECTED_SPAM + + """ + COMMENT_DETECTED_BODY_COUNT is used when the Comment was detected as exceeding + the body length by the system. + """ + COMMENT_DETECTED_BODY_COUNT + + """ + COMMENT_DETECTED_TRUST is used when the Comment being left was done by a User + that has a low karma/trust score. + """ + COMMENT_DETECTED_TRUST + + """ + COMMENT_DETECTED_LINKS is used when the Comment was detected as containing + links. + """ + COMMENT_DETECTED_LINKS + + """ + COMMENT_DETECTED_BANNED_WORD is used when the Comment was detected as + containing a banned word. + """ + COMMENT_DETECTED_BANNED_WORD + + """ + COMMENT_DETECTED_SUSPECT_WORD is used when the Comment was detected as + containing a suspect word. + """ + COMMENT_DETECTED_SUSPECT_WORD +} + +""" +COMMENT_FLAG_REASON is the union of the COMMENT_FLAG_REPORTED_REASON +and COMMENT_FLAG_DETECTED_REASON types. +""" +enum COMMENT_FLAG_REASON { + COMMENT_REPORTED_OFFENSIVE + COMMENT_REPORTED_SPAM + COMMENT_DETECTED_TOXIC + COMMENT_DETECTED_SPAM + COMMENT_DETECTED_BODY_COUNT + COMMENT_DETECTED_TRUST + COMMENT_DETECTED_LINKS + COMMENT_DETECTED_BANNED_WORD + COMMENT_DETECTED_SUSPECT_WORD +} + +""" +CommentFlagAction represents a flag Action left on a Comment. +""" +type CommentFlagAction implements CommentAction { + id: ID! + + """ + reason is the reason that the CommentFlagAction was added. + """ + reason: COMMENT_FLAG_REASON! + + """ + comment is the Comment that the CommentReactionAction is about. + """ + comment: Comment! + + """ + User when available indicates the user that left the CommentReactionAction, + otherwise it is implied that the CommentReactionAction was left by the system. + """ + user: User + + """ + createdAt is the time that the CommentReactionAction was created. + """ + createdAt: Time! +} + +""" +CommentReactionAction represents a reaction Action left on a Comment. +""" +type CommentReactionAction implements CommentAction { + id: ID! + + """ + comment is the Comment that the CommentReactionAction is about. + """ + comment: Comment! + + """ + User when available indicates the user that left the CommentReactionAction, + otherwise it is implied that the CommentReactionAction was left by the system. + """ + user: User + + """ + createdAt is the time that the CommentReactionAction was created. + """ + createdAt: Time! +} + +""" +CommentDontAgreeAction represents a don't agree Action left on a Comment. +""" +type CommentDontAgreeAction implements CommentAction { + id: ID! + + """ + comment is the Comment that the CommentDontAgreeAction is about. + """ + comment: Comment! + + """ + User when available indicates the user that left the CommentDontAgreeAction, + otherwise it is implied that the CommentDontAgreeAction was left by the + system. + """ + user: User + + """ + createdAt is the time that the CommentDontAgreeAction was created. + """ + createdAt: Time! } ################################################################################ diff --git a/src/core/server/models/actions.spec.ts b/src/core/server/models/actions.spec.ts index c1bf862ce..d44e8185a 100644 --- a/src/core/server/models/actions.spec.ts +++ b/src/core/server/models/actions.spec.ts @@ -1,10 +1,8 @@ -import { - GQLACTION_GROUP, - GQLACTION_ITEM_TYPE, - GQLACTION_TYPE, -} from "talk-server/graph/tenant/schema/__generated__/types"; +import { GQLCOMMENT_FLAG_REASON } from "talk-server/graph/tenant/schema/__generated__/types"; import { Action, + ACTION_ITEM_TYPE, + ACTION_TYPE, generateActionCounts, validateAction, } from "talk-server/models/actions"; @@ -12,27 +10,27 @@ import { describe("#generateActionCounts", () => { it("generates the action counts correctly", () => { const actions = [ - { action_type: GQLACTION_TYPE.DONT_AGREE }, + { action_type: ACTION_TYPE.DONT_AGREE }, { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.BANNED_WORD, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BANNED_WORD, }, { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.BODY_COUNT, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, }, ]; const actionCounts = generateActionCounts(...(actions as Action[])); expect(actionCounts).toEqual({ - [GQLACTION_TYPE.DONT_AGREE.toLowerCase()]: 1, - [GQLACTION_TYPE.FLAG.toLowerCase()]: 2, - [GQLACTION_TYPE.FLAG.toLowerCase() + - "_" + - GQLACTION_GROUP.BANNED_WORD.toLowerCase()]: 1, - [GQLACTION_TYPE.FLAG.toLowerCase() + - "_" + - GQLACTION_GROUP.BODY_COUNT.toLowerCase()]: 1, + [ACTION_TYPE.DONT_AGREE.toLowerCase()]: 1, + [ACTION_TYPE.FLAG.toLowerCase()]: 2, + [ACTION_TYPE.FLAG.toLowerCase() + + "_" + + GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BANNED_WORD.toLowerCase()]: 1, + [ACTION_TYPE.FLAG.toLowerCase() + + "_" + + GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT.toLowerCase()]: 1, }); }); }); @@ -41,48 +39,47 @@ describe("#validateAction", () => { it("allows a valid action", () => { const actions = [ { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.REACTION, + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.REACTION, }, { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.DONT_AGREE, - }, - - { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.SPAM_COMMENT, + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.DONT_AGREE, }, { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.TOXIC_COMMENT, + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_SPAM, }, { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.BODY_COUNT, + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_TOXIC, }, { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.TRUST, + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, }, { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.LINKS, + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_TRUST, }, { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.BANNED_WORD, + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_LINKS, }, { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.SUSPECT_WORD, + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BANNED_WORD, + }, + { + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_SUSPECT_WORD, }, ]; @@ -94,14 +91,18 @@ describe("#validateAction", () => { it("does not allow an invalid action", () => { const actions = [ { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.DONT_AGREE, - group_id: GQLACTION_GROUP.SPAM_COMMENT, + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.DONT_AGREE, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_SPAM, }, { - item_type: GQLACTION_ITEM_TYPE.COMMENTS, - action_type: GQLACTION_TYPE.DONT_AGREE, - group_id: GQLACTION_GROUP.BODY_COUNT, + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.DONT_AGREE, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, + }, + { + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.FLAG, }, ]; diff --git a/src/core/server/models/actions.ts b/src/core/server/models/actions.ts index ed4555448..291fa9a8a 100644 --- a/src/core/server/models/actions.ts +++ b/src/core/server/models/actions.ts @@ -1,12 +1,10 @@ +import Joi from "joi"; +import { pick } from "lodash"; import { Db } from "mongodb"; import uuid from "uuid"; import { Omit, Sub } from "talk-common/types"; -import { - GQLACTION_GROUP, - GQLACTION_ITEM_TYPE, - GQLACTION_TYPE, -} from "talk-server/graph/tenant/schema/__generated__/types"; +import { 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"; @@ -16,59 +14,81 @@ function collection(db: Db) { export type ActionCounts = Record; +export enum ACTION_TYPE { + /** + * REACTION corresponds to a reaction to a comment from a user. + */ + REACTION = "REACTION", + + /** + * FLAG corresponds to a flag action that indicates that the given resource needs + * moderator attention. + */ + FLAG = "FLAG", + + /** + * DONT_AGREE corresponds to when a user marks a given comment that they don't + * agree with. + */ + DONT_AGREE = "DONT_AGREE", +} + +export enum ACTION_ITEM_TYPE { + COMMENTS = "COMMENTS", +} + export interface Action extends TenantResource { readonly id: string; - action_type: GQLACTION_TYPE; - item_type: GQLACTION_ITEM_TYPE; + action_type: ACTION_TYPE; + item_type: ACTION_ITEM_TYPE; item_id: string; - group_id?: GQLACTION_GROUP; + reason?: GQLCOMMENT_FLAG_REASON; user_id?: string; created_at: Date; metadata?: Record; } -const validActionCombinations: Record< - GQLACTION_ITEM_TYPE, - Record | true> -> = { - [GQLACTION_ITEM_TYPE.COMMENTS]: { - [GQLACTION_TYPE.REACTION]: true, - [GQLACTION_TYPE.DONT_AGREE]: true, - [GQLACTION_TYPE.FLAG]: { - [GQLACTION_GROUP.SPAM_COMMENT]: true, - [GQLACTION_GROUP.TOXIC_COMMENT]: true, - [GQLACTION_GROUP.BODY_COUNT]: true, - [GQLACTION_GROUP.TRUST]: true, - [GQLACTION_GROUP.LINKS]: true, - [GQLACTION_GROUP.BANNED_WORD]: true, - [GQLACTION_GROUP.SUSPECT_WORD]: true, - }, +const ActionSchema = [ + // Flags + { + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.FLAG, + // Only reasons for the flag action will be allowed here, and it must be + // specified. + reason: Object.keys(GQLCOMMENT_FLAG_REASON), }, -}; + // Don't Agree + { + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.DONT_AGREE, + }, + // Reaction + { + item_type: ACTION_ITEM_TYPE.COMMENTS, + action_type: ACTION_TYPE.REACTION, + }, +]; +/** + * validateAction is used to validate that a specific action conforms to the + * expected schema, `ActionSchema`. + */ export function validateAction( - action: Pick + action: Pick ) { - if (!(action.item_type in validActionCombinations)) { - throw new Error("invalid item_type"); - } - - const itemType = validActionCombinations[action.item_type]; - if (!(action.action_type in itemType)) { - throw new Error("invalid action_type"); - } - - const actionType = itemType[action.action_type]; - if (action.group_id) { - if (actionType === true) { - throw new Error("invalid action_type"); + const { error } = Joi.validate( + // In typescript, this isn't an issue, but when this is transpiled to + // javascript, it will contain additional elements. + pick(action, ["item_type", "action_type", "reason"]), + ActionSchema, + { + presence: "required", + abortEarly: false, } - - if (!(action.group_id in actionType)) { - throw new Error("invalid action_type"); - } - } else if (actionType !== true) { - throw new Error("invalid action_type"); + ); + if (error) { + // TODO: wrap error? + throw error; } } @@ -93,9 +113,6 @@ export async function createAction( tenantID: string, input: CreateActionInput ): Promise { - // Validate that the action is valid, if it isn't, this will throw an error. - validateAction(input); - // Create a new ID for the action. const id = uuid.v4(); @@ -119,7 +136,7 @@ export async function createAction( action_type: input.action_type, item_type: input.item_type, item_id: input.item_id, - group_id: input.group_id, + reason: input.reason, user_id: input.user_id, }; @@ -191,11 +208,11 @@ export function generateActionCounts(...actions: Action[]): ActionCounts { // Add the action type to the action counts. incr(actionType); - // Check if the group id is set. - const groupID = action.group_id && action.group_id.toLowerCase(); - if (groupID) { + // Check if the reason is set. + const reason = action.reason && action.reason.toLowerCase(); + if (reason) { // Add the action type to the action counts. - incr(actionType, groupID); + incr(actionType, reason); } } diff --git a/src/core/server/services/comments/moderation/index.spec.ts b/src/core/server/services/comments/moderation/index.spec.ts index 4b0ce2ebf..f0367bb65 100644 --- a/src/core/server/services/comments/moderation/index.spec.ts +++ b/src/core/server/services/comments/moderation/index.spec.ts @@ -1,8 +1,8 @@ import { - GQLACTION_GROUP, - GQLACTION_TYPE, + GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; +import { ACTION_TYPE } from "talk-server/models/actions"; import { compose, ModerationPhaseContext, @@ -51,12 +51,12 @@ describe("compose", () => { const flags = [ { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.TOXIC_COMMENT, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_TOXIC, }, { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.SPAM_COMMENT, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_SPAM, }, ]; @@ -71,8 +71,8 @@ describe("compose", () => { () => ({ actions: [ { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.LINKS, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_LINKS, }, ], }), @@ -85,8 +85,8 @@ describe("compose", () => { } expect(final.actions).not.toContainEqual({ - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.LINKS, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_LINKS, }); }); diff --git a/src/core/server/services/comments/moderation/phases/commentLength.ts b/src/core/server/services/comments/moderation/phases/commentLength.ts index cb51f1c56..65e37df03 100644 --- a/src/core/server/services/comments/moderation/phases/commentLength.ts +++ b/src/core/server/services/comments/moderation/phases/commentLength.ts @@ -1,8 +1,8 @@ import { - GQLACTION_GROUP, - GQLACTION_TYPE, + GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; +import { ACTION_TYPE } from "talk-server/models/actions"; import { ModerationSettings } from "talk-server/models/settings"; import { IntermediateModerationPhase, @@ -35,8 +35,8 @@ export const commentLength: IntermediateModerationPhase = ({ status: GQLCOMMENT_STATUS.REJECTED, actions: [ { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.BODY_COUNT, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, metadata: { count: length, }, diff --git a/src/core/server/services/comments/moderation/phases/karma.ts b/src/core/server/services/comments/moderation/phases/karma.ts index f5f4d9e37..4fdf50fec 100755 --- a/src/core/server/services/comments/moderation/phases/karma.ts +++ b/src/core/server/services/comments/moderation/phases/karma.ts @@ -1,8 +1,8 @@ import { - GQLACTION_GROUP, - GQLACTION_TYPE, + GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; +import { ACTION_TYPE } from "talk-server/models/actions"; import { IntermediateModerationPhase, IntermediatePhaseResult, @@ -33,8 +33,8 @@ export const karma: IntermediateModerationPhase = ({ status: GQLCOMMENT_STATUS.SYSTEM_WITHHELD, actions: [ { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.TRUST, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_TOXIC, metadata: { trust: getCommentTrustScore(author), }, diff --git a/src/core/server/services/comments/moderation/phases/links.ts b/src/core/server/services/comments/moderation/phases/links.ts index 2eed3715e..4bf584428 100755 --- a/src/core/server/services/comments/moderation/phases/links.ts +++ b/src/core/server/services/comments/moderation/phases/links.ts @@ -2,10 +2,10 @@ import linkify from "linkify-it"; import tlds from "tlds"; import { - GQLACTION_GROUP, - GQLACTION_TYPE, + GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; +import { ACTION_TYPE } from "talk-server/models/actions"; import { ModerationSettings } from "talk-server/models/settings"; import { IntermediateModerationPhase, @@ -39,8 +39,8 @@ export const links: IntermediateModerationPhase = ({ status: GQLCOMMENT_STATUS.SYSTEM_WITHHELD, actions: [ { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.LINKS, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_LINKS, metadata: { links: comment.body, }, diff --git a/src/core/server/services/comments/moderation/phases/spam.ts b/src/core/server/services/comments/moderation/phases/spam.ts index a15e47d9a..2e73b90dc 100644 --- a/src/core/server/services/comments/moderation/phases/spam.ts +++ b/src/core/server/services/comments/moderation/phases/spam.ts @@ -1,11 +1,11 @@ import { Client } from "akismet-api"; import { - GQLACTION_GROUP, - GQLACTION_TYPE, + GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; import logger from "talk-server/logger"; +import { ACTION_TYPE } from "talk-server/models/actions"; import { IntermediateModerationPhase, IntermediatePhaseResult, @@ -110,8 +110,8 @@ export const spam: IntermediateModerationPhase = async ({ status: GQLCOMMENT_STATUS.SYSTEM_WITHHELD, actions: [ { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.SPAM_COMMENT, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_SPAM, }, ], metadata: { diff --git a/src/core/server/services/comments/moderation/phases/toxic.ts b/src/core/server/services/comments/moderation/phases/toxic.ts index 8232b1767..0a9cda887 100644 --- a/src/core/server/services/comments/moderation/phases/toxic.ts +++ b/src/core/server/services/comments/moderation/phases/toxic.ts @@ -4,12 +4,12 @@ import fetch from "node-fetch"; import { Omit } from "talk-common/types"; import { - GQLACTION_GROUP, - GQLACTION_TYPE, + GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, GQLPerspectiveExternalIntegration, } from "talk-server/graph/tenant/schema/__generated__/types"; import logger from "talk-server/logger"; +import { ACTION_TYPE } from "talk-server/models/actions"; import { IntermediateModerationPhase, IntermediatePhaseResult, @@ -103,8 +103,8 @@ export const toxic: IntermediateModerationPhase = async ({ status: GQLCOMMENT_STATUS.SYSTEM_WITHHELD, actions: [ { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.TOXIC_COMMENT, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_TOXIC, }, ], metadata: { diff --git a/src/core/server/services/comments/moderation/phases/wordlist.ts b/src/core/server/services/comments/moderation/phases/wordlist.ts index b129093cb..4a4eebb42 100755 --- a/src/core/server/services/comments/moderation/phases/wordlist.ts +++ b/src/core/server/services/comments/moderation/phases/wordlist.ts @@ -1,8 +1,8 @@ import { - GQLACTION_GROUP, - GQLACTION_TYPE, + GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; +import { ACTION_TYPE } from "talk-server/models/actions"; import { IntermediateModerationPhase, IntermediatePhaseResult, @@ -29,8 +29,8 @@ export const wordlist: IntermediateModerationPhase = ({ status: GQLCOMMENT_STATUS.REJECTED, actions: [ { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.BANNED_WORD, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BANNED_WORD, }, ], }; @@ -46,8 +46,8 @@ export const wordlist: IntermediateModerationPhase = ({ return { actions: [ { - action_type: GQLACTION_TYPE.FLAG, - group_id: GQLACTION_GROUP.SUSPECT_WORD, + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_SUSPECT_WORD, }, ], }; From 76c222dbedf2a5434c4c7d9f085acf6141a6cb1d Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 11 Sep 2018 16:50:10 -0600 Subject: [PATCH 03/53] fix: linting --- src/core/server/services/comments/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index a2317edb8..5db49c751 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -1,8 +1,8 @@ import { Db } from "mongodb"; import { Omit } from "talk-common/types"; -import { GQLACTION_ITEM_TYPE } from "talk-server/graph/tenant/schema/__generated__/types"; import { + ACTION_ITEM_TYPE, CreateActionInput, createActions, generateActionCounts, @@ -79,7 +79,7 @@ export async function create( (action): CreateActionInput => ({ ...action, item_id: comment.id, - item_type: GQLACTION_ITEM_TYPE.COMMENTS, + item_type: ACTION_ITEM_TYPE.COMMENTS, }) ); From 395cfe4adeb6823f28b12754db03f97d3eaf26b6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 18 Sep 2018 16:59:21 -0600 Subject: [PATCH 04/53] feat: added reaction configuration --- .../server/graph/tenant/schema/schema.graphql | 37 ++++++++++++++++--- src/core/server/models/settings.ts | 6 +++ src/core/server/models/tenant.ts | 12 ++++-- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 424a777e5..10fa82564 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -471,6 +471,26 @@ type Email { fromAddress: String } +################################################################################ +## ReactionConfiguration +################################################################################ + +""" +ReactionConfiguration stores the configuration for reactions used across this +Tenant. +""" +type ReactionConfiguration { + """ + label is the string placed beside the reaction icon to provide better context. + """ + label: String! + + """ + icon is the string representing the icon to be used for the reactions. + """ + icon: String! +} + ################################################################################ ## Settings ################################################################################ @@ -584,22 +604,22 @@ type Settings { """ organizationName is the name of the organization. """ - organizationName: String + organizationName: String! """ organizationContactEmail is the email of the organization. """ - organizationContactEmail: String + organizationContactEmail: String! """ email is the set of credentials and settings associated with the organization. """ - email: Email @auth(roles: [ADMIN]) + email: Email! @auth(roles: [ADMIN]) """ wordlist will return a given list of words. """ - wordlist: Wordlist @auth(roles: [ADMIN, MODERATOR]) + wordlist: Wordlist! @auth(roles: [ADMIN, MODERATOR]) """ auth contains all the settings related to authentication and authorization. @@ -609,13 +629,18 @@ type Settings { """ integrations contains all the external integrations that can be enabled. """ - integrations: ExternalIntegrations @auth(roles: [ADMIN]) + integrations: ExternalIntegrations! @auth(roles: [ADMIN]) """ karma is the set of settings related to how user Trust and Karma are handled. """ - karma: Karma @auth(roles: [ADMIN, MODERATOR]) + karma: Karma! @auth(roles: [ADMIN, MODERATOR]) + + """ + reaction specifies the configuration for reactions. + """ + reaction: ReactionConfiguration! @auth(roles: [ADMIN]) } ################################################################################ diff --git a/src/core/server/models/settings.ts b/src/core/server/models/settings.ts index 451bfdf7a..00f25282a 100644 --- a/src/core/server/models/settings.ts +++ b/src/core/server/models/settings.ts @@ -4,6 +4,7 @@ import { GQLExternalIntegrations, GQLKarma, GQLMODERATION_MODE, + GQLReactionConfiguration, GQLWordlist, } from "talk-server/graph/tenant/schema/__generated__/types"; @@ -101,4 +102,9 @@ export interface Settings extends ModerationSettings { * Various integrations with external services. */ integrations: GQLExternalIntegrations; + + /** + * reaction specifies the configuration for reactions. + */ + reaction: GQLReactionConfiguration; } diff --git a/src/core/server/models/tenant.ts b/src/core/server/models/tenant.ts index f67982e6d..e11d55b2c 100644 --- a/src/core/server/models/tenant.ts +++ b/src/core/server/models/tenant.ts @@ -49,10 +49,10 @@ export type CreateTenantInput = Pick< /** * create will create a new Tenant. * - * @param db the MongoDB connection used to create the tenant. + * @param mongo the MongoDB connection used to create the tenant. * @param input the customizable parts of the Tenant available during creation */ -export async function createTenant(db: Db, input: CreateTenantInput) { +export async function createTenant(mongo: Db, input: CreateTenantInput) { const defaults: Sub = { // Create a new ID. id: uuid.v4(), @@ -115,6 +115,12 @@ export async function createTenant(db: Db, input: CreateTenantInput) { enabled: false, }, }, + reaction: { + // By default, the standard reaction style will use the Respect with the + // handshake. + label: "Respect", + icon: "handshake", + }, }; // Create the new Tenant by merging it together with the defaults. @@ -124,7 +130,7 @@ export async function createTenant(db: Db, input: CreateTenantInput) { }; // Insert the Tenant into the database. - await collection(db).insert(tenant); + await collection(mongo).insert(tenant); return tenant; } From 8d869003761e0af622d15937d146f510c3d68254 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 18 Sep 2018 16:59:32 -0600 Subject: [PATCH 05/53] feat: added support for action creation via edit --- src/core/server/services/comments/index.ts | 133 ++++++++++++--------- 1 file changed, 77 insertions(+), 56 deletions(-) diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index 0c0073b9f..6a359cee3 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -94,6 +94,81 @@ export async function create( return comment; } +export type EditComment = Omit< + EditCommentInput, + "status" | "author_id" | "lastEditableCommentCreatedAt" +>; + +export async function edit( + mongo: Db, + tenant: Tenant, + author: User, + input: EditComment, + req?: Request +) { + // Get the comment that we're editing. + let comment = await retrieveComment(mongo, tenant.id, input.id); + if (!comment) { + // TODO: replace to match error returned by the models/comments.ts + throw new Error("comment not found"); + } + + // Grab the asset that we'll use to check moderation pieces with. + const asset = await retrieveAsset(mongo, tenant.id, comment.asset_id); + if (!asset) { + // TODO: (wyattjoh) return better error. + throw new Error("asset referenced does not exist"); + } + + // Run the comment through the moderation phases. + const { status, metadata, actions } = await processForModeration({ + asset, + tenant, + comment: input, + author, + req, + }); + + comment = await editComment(mongo, tenant.id, { + id: input.id, + author_id: author.id, + body: input.body, + status, + metadata, + // The editable time is based on the current time, and the edit window + // length. By subtracting the current date from the edit window length, we + // get the maximum value for the `created_at` time that would be permitted + // for the comment edit to succeed. + lastEditableCommentCreatedAt: new Date( + Date.now() - tenant.editCommentWindowLength + ), + }); + if (!comment) { + // TODO: replace to match error returned by the models/comments.ts + throw new Error("comment not found"); + } + + if (actions.length > 0) { + // The actions coming from the moderation phases didn't know the item_id + // at the time, and we didn't want the repetitive nature of adding the + // item_type each time, so this mapping function adds them! + const inputs = actions.map( + (action): CreateActionInput => ({ + ...action, + // Strict null check seems to have failed here... Null checking was done + // above where we errored if the comment was falsely. + item_id: comment!.id, + item_type: ACTION_ITEM_TYPE.COMMENTS, + }) + ); + + // Insert and handle creating the actions. + comment = await addCommentActions(mongo, tenant, comment, inputs); + } + + return comment; +} + async function addCommentActions( mongo: Db, tenant: Tenant, @@ -103,7 +178,8 @@ async function addCommentActions( // Create each of the actions, returning each of the action results. const results = await createActions(mongo, tenant.id, inputs); - // Get the actions that were upserted. + // Get the actions that were upserted, we only want to increment the action + // counts of actions that were just created. const upsertedActions = results .filter(({ wasUpserted }) => wasUpserted) .map(({ action }) => action); @@ -132,58 +208,3 @@ async function addCommentActions( return comment; } - -export type EditComment = Omit< - EditCommentInput, - "status" | "author_id" | "lastEditableCommentCreatedAt" ->; - -export async function edit( - mongo: Db, - tenant: Tenant, - author: User, - input: EditComment, - req?: Request -) { - // Get the comment that we're editing. - let comment = await retrieveComment(mongo, tenant.id, input.id); - if (!comment) { - // TODO: replace to match error returned by the models/comments.ts - throw new Error("comment not found"); - } - - // Grab the asset that we'll use to check moderation pieces with. - const asset = await retrieveAsset(mongo, tenant.id, comment.asset_id); - if (!asset) { - // TODO: (wyattjoh) return better error. - throw new Error("asset referenced does not exist"); - } - - // Run the comment through the moderation phases. - const { status, metadata } = await processForModeration({ - asset, - tenant, - comment: input, - author, - req, - }); - - // TODO: (wyattjoh) use the actions somehow. - - comment = await editComment(mongo, tenant.id, { - id: input.id, - author_id: author.id, - body: input.body, - status, - metadata, - // The editable time is based on the current time, and the edit window - // length. By subtracting the current date from the edit window length, we - // get the maximum value for the `created_at` time that would be permitted - // for the comment edit to succeed. - lastEditableCommentCreatedAt: new Date( - Date.now() - tenant.editCommentWindowLength - ), - }); - - return comment; -} From 7eb1755c5e4ef7ce7b71b56ec6b1cc1ef264e1ff Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 20 Sep 2018 15:13:46 -0600 Subject: [PATCH 06/53] feat: expanded action counts --- .../models/__snapshots__/actions.spec.ts.snap | 54 +++++ src/core/server/models/actions.spec.ts | 45 ++-- src/core/server/models/actions.ts | 205 +++++++++++++++--- src/core/server/models/comment.ts | 6 +- src/core/server/models/user.ts | 4 +- src/core/server/services/comments/index.ts | 4 +- 6 files changed, 264 insertions(+), 54 deletions(-) create mode 100644 src/core/server/models/__snapshots__/actions.spec.ts.snap diff --git a/src/core/server/models/__snapshots__/actions.spec.ts.snap b/src/core/server/models/__snapshots__/actions.spec.ts.snap new file mode 100644 index 000000000..791eabc1e --- /dev/null +++ b/src/core/server/models/__snapshots__/actions.spec.ts.snap @@ -0,0 +1,54 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`#decodeActionCounts parses the action counts correctly 1`] = ` +Object { + "DONT_AGREE": 1, + "FLAG": 2, + "FLAG__COMMENT_DETECTED_BANNED_WORD": 1, + "FLAG__COMMENT_DETECTED_BODY_COUNT": 1, + "REACTION": 3, +} +`; + +exports[`#decodeActionCounts parses the action counts correctly 2`] = ` +Object { + "DONT_AGREE": Object { + "total": 1, + }, + "FLAG": Object { + "reasons": Object { + "COMMENT_DETECTED_BANNED_WORD": 1, + "COMMENT_DETECTED_BODY_COUNT": 1, + "COMMENT_DETECTED_LINKS": 0, + "COMMENT_DETECTED_SPAM": 0, + "COMMENT_DETECTED_SUSPECT_WORD": 0, + "COMMENT_DETECTED_TOXIC": 0, + "COMMENT_DETECTED_TRUST": 0, + "COMMENT_REPORTED_OFFENSIVE": 0, + "COMMENT_REPORTED_SPAM": 0, + }, + "total": 2, + }, + "REACTION": Object { + "total": 3, + }, +} +`; + +exports[`#encodeActionCounts generates the action counts correctly 1`] = ` +Object { + "DONT_AGREE": 1, + "FLAG": 2, + "FLAG__COMMENT_DETECTED_BANNED_WORD": 1, + "FLAG__COMMENT_DETECTED_BODY_COUNT": 1, +} +`; + +exports[`#generateActionCounts generates the action counts correctly 1`] = ` +Object { + "DONT_AGREE": 1, + "FLAG": 2, + "FLAG__COMMENT_DETECTED_BANNED_WORD": 1, + "FLAG__COMMENT_DETECTED_BODY_COUNT": 1, +} +`; diff --git a/src/core/server/models/actions.spec.ts b/src/core/server/models/actions.spec.ts index d44e8185a..a09595003 100644 --- a/src/core/server/models/actions.spec.ts +++ b/src/core/server/models/actions.spec.ts @@ -3,11 +3,12 @@ import { Action, ACTION_ITEM_TYPE, ACTION_TYPE, - generateActionCounts, + decodeActionCounts, + encodeActionCounts, validateAction, } from "talk-server/models/actions"; -describe("#generateActionCounts", () => { +describe("#encodeActionCounts", () => { it("generates the action counts correctly", () => { const actions = [ { action_type: ACTION_TYPE.DONT_AGREE }, @@ -20,18 +21,36 @@ describe("#generateActionCounts", () => { reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, }, ]; - const actionCounts = generateActionCounts(...(actions as Action[])); + const actionCounts = encodeActionCounts(...(actions as Action[])); - expect(actionCounts).toEqual({ - [ACTION_TYPE.DONT_AGREE.toLowerCase()]: 1, - [ACTION_TYPE.FLAG.toLowerCase()]: 2, - [ACTION_TYPE.FLAG.toLowerCase() + - "_" + - GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BANNED_WORD.toLowerCase()]: 1, - [ACTION_TYPE.FLAG.toLowerCase() + - "_" + - GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT.toLowerCase()]: 1, - }); + expect(actionCounts).toMatchSnapshot(); + }); +}); + +describe("#decodeActionCounts", () => { + it("parses the action counts correctly", () => { + const actions = [ + { action_type: ACTION_TYPE.REACTION }, + { action_type: ACTION_TYPE.REACTION }, + { action_type: ACTION_TYPE.REACTION }, + { action_type: ACTION_TYPE.DONT_AGREE }, + { + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BANNED_WORD, + }, + { + action_type: ACTION_TYPE.FLAG, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, + }, + ]; + + const modelActionCounts = encodeActionCounts(...(actions as Action[])); + + expect(modelActionCounts).toMatchSnapshot(); + + const actionCounts = decodeActionCounts(modelActionCounts); + + expect(actionCounts).toMatchSnapshot(); }); }); diff --git a/src/core/server/models/actions.ts b/src/core/server/models/actions.ts index 291fa9a8a..93bc992d8 100644 --- a/src/core/server/models/actions.ts +++ b/src/core/server/models/actions.ts @@ -12,25 +12,37 @@ function collection(db: Db) { return db.collection>("actions"); } -export type ActionCounts = Record; - export enum ACTION_TYPE { /** * REACTION corresponds to a reaction to a comment from a user. */ REACTION = "REACTION", - /** - * FLAG corresponds to a flag action that indicates that the given resource needs - * moderator attention. - */ - FLAG = "FLAG", - /** * DONT_AGREE corresponds to when a user marks a given comment that they don't * agree with. */ DONT_AGREE = "DONT_AGREE", + + /** + * FLAG corresponds to a flag action that indicates that the given resource needs + * moderator attention. + */ + FLAG = "FLAG", +} + +export type EncodedActionCounts = Record; + +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 { @@ -182,44 +194,169 @@ export async function createActions( } /** - * generateActionCounts will take a list of actions, and generate action counts + * ACTION_COUNT_JOIN_CHAR is the character that is used to separate the reason + * from the action type when storing the action counts in the models. + */ +export const ACTION_COUNT_JOIN_CHAR = "__"; + +/** + * encodeActionCounts will take a list of actions, and generate action counts * from it. * * @param actions list of actions to generate the action counts from */ -export function generateActionCounts(...actions: Action[]): ActionCounts { - const actionCounts: ActionCounts = {}; +export function encodeActionCounts(...actions: Action[]): EncodedActionCounts { + const actionCounts: EncodedActionCounts = {}; - /** - * increment the key in the action counts variable. - */ - function incr(...keys: string[]) { - const key = keys.join("_"); + // Loop over the actions, and increment them. + for (const action of actions) { + for (const key of encodeActionCountKeys(action)) { if (key in actionCounts) { actionCounts[key]++; } else { actionCounts[key] = 1; } } - - function transform(action: Action) { - const actionType = action.action_type.toLowerCase(); - - // Add the action type to the action counts. - incr(actionType); - - // Check if the reason is set. - const reason = action.reason && action.reason.toLowerCase(); - if (reason) { - // Add the action type to the action counts. - incr(actionType, reason); - } - } - - // Loop over the actions, and increment them. - for (const action of actions) { - transform(action); } return actionCounts; } + +/** + * encodeActionCountKeys encodes the action into string keys which represents + * the groupings as seen in `EncodedActionCounts`. + */ +function encodeActionCountKeys(action: Action): string[] { + const keys = [action.action_type as string]; + if (action.reason) { + keys.push( + [action.action_type as string, action.reason as string].join( + ACTION_COUNT_JOIN_CHAR + ) + ); + } + return keys; +} + +interface DecodedActionCountKey { + /** + * actionType stores the action type referenced by the key. + */ + actionType: ACTION_TYPE; + + /** + * reason stores the reason referenced by the key if the actionType is FLAG. + */ + reason?: GQLCOMMENT_FLAG_REASON; +} + +/** + * decodeActionCountGroup will unpack the key as it is encoded into the separate + * actionType and reason. + */ +function decodeActionCountKey(key: string): DecodedActionCountKey { + let actionType: string = ""; + let reason: string = ""; + + if (key.indexOf(ACTION_COUNT_JOIN_CHAR) >= 0) { + const keys = key.split(ACTION_COUNT_JOIN_CHAR); + if (keys.length !== 2) { + throw new Error( + "invalid action count contained more than two components" + ); + } + + actionType = keys[0]; + reason = keys[1]; + + // Validate that the action type is flag. + if (actionType !== ACTION_TYPE.FLAG) { + throw new Error("invalid action type, expected only flag to have reason"); + } + + // Validate that the reason is valid. + if (!reason || !(reason in GQLCOMMENT_FLAG_REASON)) { + throw new Error("expected flag to have a reason that was valid"); + } + } else { + actionType = key; + } + + // Validate that the action type is valid. + if (!actionType || !(actionType in ACTION_TYPE)) { + throw new Error("expected action to have an action type that was valid"); + } + + const result: DecodedActionCountKey = { + actionType: actionType as ACTION_TYPE, + }; + + // Merge in the reason if it's provided. If we got here, we know that the + // reason is a GQLCOMMENT_FLAG_REASON. + if (reason) { + result.reason = reason as GQLCOMMENT_FLAG_REASON; + } + + return result; + } + +/** + * decodeActionCounts will take the encoded action counts and decode them into + * a useable format. + * + * @param encodedActionCounts the action counts to decode + */ +export function decodeActionCounts( + encodedActionCounts: EncodedActionCounts +): ActionCounts { + // Default all the action counts to zero. + const actionCounts: ActionCounts = { + [ACTION_TYPE.REACTION]: { + total: 0, + }, + [ACTION_TYPE.DONT_AGREE]: { + total: 0, + }, + [ACTION_TYPE.FLAG]: { + total: 0, + reasons: Object.keys(GQLCOMMENT_FLAG_REASON).reduce( + (reasons, reason) => ({ + ...reasons, + [reason]: 0, + }), + {} + ) as Record, + }, + }; + + // Loop over all the encoded action counts to extract each of the action + // counts as they are encoded. + Object.entries(encodedActionCounts).forEach(([key, count]) => { + // Pull out the action type and the reason from the key. + const { actionType, reason } = decodeActionCountKey(key); + + // Handle the different types and reasons. + switch (actionType) { + case ACTION_TYPE.REACTION: + actionCounts[ACTION_TYPE.REACTION].total += count; + break; + case ACTION_TYPE.DONT_AGREE: + actionCounts[ACTION_TYPE.DONT_AGREE].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; + } else { + actionCounts[ACTION_TYPE.FLAG].total += count; + } + break; + default: + throw new Error("unexpected action type"); + } + }); + + return actionCounts; +} diff --git a/src/core/server/models/comment.ts b/src/core/server/models/comment.ts index bea38ea20..2fa32efc1 100644 --- a/src/core/server/models/comment.ts +++ b/src/core/server/models/comment.ts @@ -7,7 +7,7 @@ import { GQLCOMMENT_SORT, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; -import { ActionCounts } from "talk-server/models/actions"; +import { EncodedActionCounts } from "talk-server/models/actions"; import { Connection, Cursor, @@ -42,7 +42,7 @@ export interface Comment extends TenantResource { body_history: BodyHistoryItem[]; status: GQLCOMMENT_STATUS; status_history: StatusHistoryItem[]; - action_counts: ActionCounts; + action_counts: EncodedActionCounts; reply_count: number; created_at: Date; deleted_at?: Date; @@ -386,7 +386,7 @@ export async function updateCommentActionCounts( mongo: Db, tenantID: string, id: string, - actionCounts: ActionCounts + actionCounts: EncodedActionCounts ) { const result = await collection(mongo).findOneAndUpdate( { id, tenant_id: tenantID }, diff --git a/src/core/server/models/user.ts b/src/core/server/models/user.ts index 9b1d04343..8bfac404a 100644 --- a/src/core/server/models/user.ts +++ b/src/core/server/models/user.ts @@ -7,7 +7,7 @@ import { GQLUSER_ROLE, GQLUSER_USERNAME_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; -import { ActionCounts } from "talk-server/models/actions"; +import { EncodedActionCounts } from "talk-server/models/actions"; import { FilterQuery } from "talk-server/models/query"; import { TenantResource } from "talk-server/models/tenant"; @@ -70,7 +70,7 @@ export interface User extends TenantResource { tokens: Token[]; role: GQLUSER_ROLE; status: UserStatus; - action_counts: ActionCounts; + action_counts: EncodedActionCounts; ignored_users: string[]; created_at: Date; } diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index 6a359cee3..2bddddc44 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -5,7 +5,7 @@ import { ACTION_ITEM_TYPE, CreateActionInput, createActions, - generateActionCounts, + encodeActionCounts, } from "talk-server/models/actions"; import { retrieveAsset } from "talk-server/models/asset"; import { @@ -186,7 +186,7 @@ async function addCommentActions( if (upsertedActions.length > 0) { // Compute the action counts. - const actionCounts = generateActionCounts(...upsertedActions); + const actionCounts = encodeActionCounts(...upsertedActions); // Update the comment action counts here. const updatedComment = await updateCommentActionCounts( From af22b9808b5afd6152ba576130e0a850c51550c4 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 20 Sep 2018 15:14:02 -0600 Subject: [PATCH 07/53] feat: added action deletion --- src/core/server/models/actions.ts | 66 ++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/src/core/server/models/actions.ts b/src/core/server/models/actions.ts index 93bc992d8..c8094d9ca 100644 --- a/src/core/server/models/actions.ts +++ b/src/core/server/models/actions.ts @@ -193,6 +193,54 @@ export async function createActions( return Promise.all(inputs.map(input => createAction(mongo, tenantID, input))); } +export type DeleteActionInput = Pick< + Action, + "action_type" | "item_type" | "item_id" | "reason" | "user_id" +>; + +/** + * The result returned by `deleteAction`. + */ +export interface DeletedActionResultObject { + /** + * action is the action that was deleted. + */ + action?: Action; + + /** + * wasDeleted is true when the action that was supposed to be deleted was + * actually deleted. + */ + wasDeleted: boolean; +} + +/** + * deleteAction will delete the action based on the form of the action rather + * than a specific action by ID. + */ +export async function deleteAction( + mongo: Db, + tenantID: string, + input: DeleteActionInput +): Promise { + // Extract the filter parameters. + const filter: FilterQuery = { + tenant_id: tenantID, + action_type: input.action_type, + item_type: input.item_type, + item_id: input.item_id, + reason: input.reason, + user_id: input.user_id, + }; + + // Remove the action from the database, returning the action that was deleted. + const result = await collection(mongo).findOneAndDelete(filter); + return { + action: result.value, + wasDeleted: Boolean(result.ok && result.value), + }; +} + /** * ACTION_COUNT_JOIN_CHAR is the character that is used to separate the reason * from the action type when storing the action counts in the models. @@ -211,13 +259,13 @@ export function encodeActionCounts(...actions: Action[]): EncodedActionCounts { // Loop over the actions, and increment them. for (const action of actions) { for (const key of encodeActionCountKeys(action)) { - if (key in actionCounts) { - actionCounts[key]++; - } else { - actionCounts[key] = 1; + if (key in actionCounts) { + actionCounts[key]++; + } else { + actionCounts[key] = 1; + } } } - } return actionCounts; } @@ -293,12 +341,12 @@ function decodeActionCountKey(key: string): DecodedActionCountKey { // Merge in the reason if it's provided. If we got here, we know that the // reason is a GQLCOMMENT_FLAG_REASON. - if (reason) { + if (reason) { result.reason = reason as GQLCOMMENT_FLAG_REASON; - } + } return result; - } +} /** * decodeActionCounts will take the encoded action counts and decode them into @@ -355,7 +403,7 @@ export function decodeActionCounts( break; default: throw new Error("unexpected action type"); - } + } }); return actionCounts; From be3ef7887597450c275a12f1475039569f2f6e54 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 20 Sep 2018 23:17:58 -0600 Subject: [PATCH 08/53] feat: added action counts to graph --- package-lock.json | 24 ++-- package.json | 2 +- .../server/graph/tenant/loaders/comments.ts | 16 +++ .../server/graph/tenant/resolvers/comment.ts | 4 + .../server/graph/tenant/schema/schema.graphql | 44 ++++++- .../server/graph/tenant/schema/schema.spec.ts | 24 ++++ src/core/server/models/actions.ts | 112 +++++++++++++----- 7 files changed, 181 insertions(+), 45 deletions(-) create mode 100644 src/core/server/graph/tenant/schema/schema.spec.ts diff --git a/package-lock.json b/package-lock.json index 87ed024ab..9ced80ccd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1647,9 +1647,9 @@ } }, "@types/bson": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-1.0.10.tgz", - "integrity": "sha512-gRf+Qy5Qiyjz28ZkPRP37bDHtGG67op/lV2qcIMhWUq4vIMJ6/j13ajeYH7LFhJ5RNflyLHmdANPGDXZ5a8EzQ==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-1.0.11.tgz", + "integrity": "sha512-j+UcCWI+FsbI5/FQP/Kj2CXyplWAz39ktHFkXk84h7dNblKRSoNJs95PZFRd96NQGqsPEPgeclqnznWZr14ZDA==", "dev": true, "requires": { "@types/node": "*" @@ -1989,9 +1989,9 @@ } }, "@types/mongodb": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.1.1.tgz", - "integrity": "sha512-lHEH+OwYNeuC28jlmdPT/wBAVMuB6M1sHjZKAtaho/LeJf78ILJPYUq2OD7mWVj9QR7uYiKVt4ExGkXegHFCJQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.1.7.tgz", + "integrity": "sha512-ljS4mE9o3apEkI59pftdnLf3b1ZczMPtXWp1myrhR+E/CLk0O5SjbTt6Rn3OxMB+Qc2eAytrQVufa4y1pFqE2A==", "dev": true, "requires": { "@types/bson": "*", @@ -10222,8 +10222,11 @@ "resolved": "https://registry.npmjs.org/fluent-intl-polyfill/-/fluent-intl-polyfill-0.1.0.tgz", "integrity": "sha1-ETOUSrJHeINHOZVZaIPg05z4hc8=", "dev": true, - "requires": { - "intl-pluralrules": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b" + "dependencies": { + "intl-pluralrules": { + "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b", + "from": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b" + } } }, "fluent-langneg": { @@ -12667,11 +12670,6 @@ "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, - "intl-pluralrules": { - "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b", - "from": "github:projectfluent/IntlPluralRules#module", - "dev": true - }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", diff --git a/package.json b/package.json index 5a29bc959..ce5efe0c4 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "@types/lodash": "^4.14.111", "@types/luxon": "^0.5.3", "@types/mini-css-extract-plugin": "^0.2.0", - "@types/mongodb": "^3.1.1", + "@types/mongodb": "^3.1.7", "@types/ms": "^0.7.30", "@types/node": "^10.5.2", "@types/node-fetch": "^2.1.2", diff --git a/src/core/server/graph/tenant/loaders/comments.ts b/src/core/server/graph/tenant/loaders/comments.ts index 111a40cd4..23b0da93b 100644 --- a/src/core/server/graph/tenant/loaders/comments.ts +++ b/src/core/server/graph/tenant/loaders/comments.ts @@ -6,6 +6,11 @@ import { CommentToRepliesArgs, GQLCOMMENT_SORT, } from "talk-server/graph/tenant/schema/__generated__/types"; +import { + ACTION_ITEM_TYPE, + ActionCounts, + retrieveManyAuthoredActionCounts, +} from "talk-server/models/actions"; import { retrieveCommentAssetConnection, retrieveCommentRepliesConnection, @@ -16,6 +21,17 @@ export default (ctx: Context) => ({ comment: new DataLoader((ids: string[]) => retrieveManyComments(ctx.mongo, ctx.tenant.id, ids) ), + retrieveAuthoredActionCounts: new DataLoader( + (itemIDs: string[]) => + retrieveManyAuthoredActionCounts( + ctx.mongo, + ctx.tenant.id, + // This should only ever be accessed when a user is logged in. + ctx.user!.id, + ACTION_ITEM_TYPE.COMMENTS, + itemIDs + ) + ), forAsset: ( assetID: string, // Apply the graph schema defaults at the loader. diff --git a/src/core/server/graph/tenant/resolvers/comment.ts b/src/core/server/graph/tenant/resolvers/comment.ts index ee775ff0f..ef6dcb2cf 100644 --- a/src/core/server/graph/tenant/resolvers/comment.ts +++ b/src/core/server/graph/tenant/resolvers/comment.ts @@ -1,4 +1,5 @@ import { GQLCommentTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types"; +import { decodeActionCounts } from "talk-server/models/actions"; import { Comment } from "talk-server/models/comment"; const Comment: GQLCommentTypeResolver = { @@ -17,6 +18,9 @@ const Comment: GQLCommentTypeResolver = { ctx.loaders.Users.user.load(comment.author_id), 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), }; export default Comment; diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 10fa82564..af8169138 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -210,6 +210,37 @@ type CommentDontAgreeAction implements CommentAction { createdAt: Time! } +type ReactionActionCounts { + total: Int! +} + +type DontAgreeActionCounts { + total: Int! +} + +type FlagReasonActionCounts { + COMMENT_REPORTED_OFFENSIVE: Int! + COMMENT_REPORTED_SPAM: Int! + COMMENT_DETECTED_TOXIC: Int! + COMMENT_DETECTED_SPAM: Int! + COMMENT_DETECTED_BODY_COUNT: Int! + COMMENT_DETECTED_TRUST: Int! + COMMENT_DETECTED_LINKS: Int! + COMMENT_DETECTED_BANNED_WORD: Int! + COMMENT_DETECTED_SUSPECT_WORD: Int! +} + +type FlagActionCounts { + total: Int! + reasons: FlagReasonActionCounts! +} + +type ActionCounts { + reaction: ReactionActionCounts! + dontagree: DontAgreeActionCounts! + flag: FlagActionCounts! +} + ################################################################################ ## Settings ################################################################################ @@ -829,6 +860,17 @@ type Comment { editing returns details about the edit status of a Comment. """ editing: EditInfo! + + """ + actionCounts stores the counts of all the actions for the Comment. + """ + actionCounts: ActionCounts! + + """ + authoredActionCounts stores the counts of all the actions for the Comment as + it is written by the current User. + """ + authoredActionCounts: ActionCounts! @auth } type PageInfo { @@ -996,7 +1038,7 @@ type Query { """ me is the current logged in User. """ - me: User + me: User @auth """ settings is the Settings for a given Tenant. diff --git a/src/core/server/graph/tenant/schema/schema.spec.ts b/src/core/server/graph/tenant/schema/schema.spec.ts new file mode 100644 index 000000000..f3aea0007 --- /dev/null +++ b/src/core/server/graph/tenant/schema/schema.spec.ts @@ -0,0 +1,24 @@ +import { + GQLCOMMENT_FLAG_REASON, + GQLFlagReasonActionCounts, +} from "talk-server/graph/tenant/schema/__generated__/types"; + +type ExtractKeys = { [P in keyof T]: P }[keyof T]; +type A = ExtractKeys; +type B = ExtractKeys; + +// These tests ensure that the enums contained in GQLCOMMENT_FLAG_REASON are +// also defined on the GQLFlagReasonActionCounts type. +describe("GQLFlagReasonActionCounts", () => { + it("contains all the flag enum types", () => { + const a: A = "" as any; + let b: B = "" as any; + b = a; + expect(b).toBe(""); + + let c: A = "" as any; + const d: B = "" as any; + c = d; + expect(c).toBe(""); + }); +}); diff --git a/src/core/server/models/actions.ts b/src/core/server/models/actions.ts index c8094d9ca..acc6f150e 100644 --- a/src/core/server/models/actions.ts +++ b/src/core/server/models/actions.ts @@ -193,6 +193,40 @@ export async function createActions( return Promise.all(inputs.map(input => createAction(mongo, tenantID, input))); } +/** + * retrieveManyAuthoredActionCounts returns the action counts for a specific + * user. + */ +export async function retrieveManyAuthoredActionCounts( + 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 }, + }); + + 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. + 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() + ) + ); +} + export type DeleteActionInput = Pick< Action, "action_type" | "item_type" | "item_id" | "reason" | "user_id" @@ -349,16 +383,10 @@ function decodeActionCountKey(key: string): DecodedActionCountKey { } /** - * decodeActionCounts will take the encoded action counts and decode them into - * a useable format. - * - * @param encodedActionCounts the action counts to decode + * createEmptyActionCounts creates a default/empty set of action counts. */ -export function decodeActionCounts( - encodedActionCounts: EncodedActionCounts -): ActionCounts { - // Default all the action counts to zero. - const actionCounts: ActionCounts = { +function createEmptyActionCounts(): ActionCounts { + return { [ACTION_TYPE.REACTION]: { total: 0, }, @@ -376,35 +404,59 @@ export function decodeActionCounts( ) as Record, }, }; +} + +/** + * decodeActionCounts will take the encoded action counts and decode them into + * a useable format. + * + * @param encodedActionCounts the action counts to decode + */ +export function decodeActionCounts( + encodedActionCounts: EncodedActionCounts +): ActionCounts { + // Default all the action counts to zero. + const actionCounts: ActionCounts = createEmptyActionCounts(); // Loop over all the encoded action counts to extract each of the action // counts as they are encoded. - Object.entries(encodedActionCounts).forEach(([key, count]) => { + Object.entries(encodedActionCounts).map(([key, count]) => { // Pull out the action type and the reason from the key. const { actionType, reason } = decodeActionCountKey(key); // Handle the different types and reasons. - switch (actionType) { - case ACTION_TYPE.REACTION: - actionCounts[ACTION_TYPE.REACTION].total += count; - break; - case ACTION_TYPE.DONT_AGREE: - actionCounts[ACTION_TYPE.DONT_AGREE].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; - } else { - actionCounts[ACTION_TYPE.FLAG].total += count; - } - break; - default: - throw new Error("unexpected action type"); - } + incrementActionCounts(actionCounts, actionType, reason, count); }); return actionCounts; } + +function incrementActionCounts( + actionCounts: ActionCounts, + actionType: ACTION_TYPE, + reason: GQLCOMMENT_FLAG_REASON | undefined, + count: number = 1 +) { + switch (actionType) { + case ACTION_TYPE.REACTION: + actionCounts[ACTION_TYPE.REACTION].total += count; + break; + case ACTION_TYPE.DONT_AGREE: + actionCounts[ACTION_TYPE.DONT_AGREE].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; + } else { + actionCounts[ACTION_TYPE.FLAG].total += count; + } + break; + default: + throw new Error("unexpected action type"); + } + + return actionCounts; +} From aff29d781897bece05184e0e3e8252a5e50aa5ef Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 20 Sep 2018 23:19:46 -0600 Subject: [PATCH 09/53] fix: removed unused types --- .../server/graph/tenant/schema/schema.graphql | 98 ------------------- 1 file changed, 98 deletions(-) diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index af8169138..5ae9f95e9 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -30,29 +30,6 @@ scalar Cursor ## Actions ################################################################################ -""" -CommentAction describes an Action that is left on a Comment. -""" -interface CommentAction { - id: ID! - - """ - comment is the Comment that the CommentAction is about. - """ - comment: Comment! - - """ - User when available indicates the user that left the CommentAction, otherwise - it is implied that the CommentAction was left by the system. - """ - user: User - - """ - createdAt is the time that the CommentAction was created. - """ - createdAt: Time! -} - """ COMMENT_FLAG_REPORTED_REASON is a reason that is reported by a User on a Comment. @@ -135,81 +112,6 @@ enum COMMENT_FLAG_REASON { COMMENT_DETECTED_SUSPECT_WORD } -""" -CommentFlagAction represents a flag Action left on a Comment. -""" -type CommentFlagAction implements CommentAction { - id: ID! - - """ - reason is the reason that the CommentFlagAction was added. - """ - reason: COMMENT_FLAG_REASON! - - """ - comment is the Comment that the CommentReactionAction is about. - """ - comment: Comment! - - """ - User when available indicates the user that left the CommentReactionAction, - otherwise it is implied that the CommentReactionAction was left by the system. - """ - user: User - - """ - createdAt is the time that the CommentReactionAction was created. - """ - createdAt: Time! -} - -""" -CommentReactionAction represents a reaction Action left on a Comment. -""" -type CommentReactionAction implements CommentAction { - id: ID! - - """ - comment is the Comment that the CommentReactionAction is about. - """ - comment: Comment! - - """ - User when available indicates the user that left the CommentReactionAction, - otherwise it is implied that the CommentReactionAction was left by the system. - """ - user: User - - """ - createdAt is the time that the CommentReactionAction was created. - """ - createdAt: Time! -} - -""" -CommentDontAgreeAction represents a don't agree Action left on a Comment. -""" -type CommentDontAgreeAction implements CommentAction { - id: ID! - - """ - comment is the Comment that the CommentDontAgreeAction is about. - """ - comment: Comment! - - """ - User when available indicates the user that left the CommentDontAgreeAction, - otherwise it is implied that the CommentDontAgreeAction was left by the - system. - """ - user: User - - """ - createdAt is the time that the CommentDontAgreeAction was created. - """ - createdAt: Time! -} - type ReactionActionCounts { total: Int! } From bc78efc42809dcde89d42e50407a79f2df9f7bdd Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 21 Sep 2018 09:40:00 -0600 Subject: [PATCH 10/53] fix: updated snapshots --- package-lock.json | 12 +++++++----- .../server/models/__snapshots__/actions.spec.ts.snap | 9 --------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a30c6d59..db9901d58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10222,11 +10222,8 @@ "resolved": "https://registry.npmjs.org/fluent-intl-polyfill/-/fluent-intl-polyfill-0.1.0.tgz", "integrity": "sha1-ETOUSrJHeINHOZVZaIPg05z4hc8=", "dev": true, - "dependencies": { - "intl-pluralrules": { - "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b", - "from": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b" - } + "requires": { + "intl-pluralrules": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b" } }, "fluent-langneg": { @@ -12670,6 +12667,11 @@ "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, + "intl-pluralrules": { + "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b", + "from": "github:projectfluent/IntlPluralRules#module", + "dev": true + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", diff --git a/src/core/server/models/__snapshots__/actions.spec.ts.snap b/src/core/server/models/__snapshots__/actions.spec.ts.snap index 791eabc1e..0d5d34ad0 100644 --- a/src/core/server/models/__snapshots__/actions.spec.ts.snap +++ b/src/core/server/models/__snapshots__/actions.spec.ts.snap @@ -43,12 +43,3 @@ Object { "FLAG__COMMENT_DETECTED_BODY_COUNT": 1, } `; - -exports[`#generateActionCounts generates the action counts correctly 1`] = ` -Object { - "DONT_AGREE": 1, - "FLAG": 2, - "FLAG__COMMENT_DETECTED_BANNED_WORD": 1, - "FLAG__COMMENT_DETECTED_BODY_COUNT": 1, -} -`; From 6b814cee0df522c600db65e47d798fa7b40d303f Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 21 Sep 2018 12:50:21 -0600 Subject: [PATCH 11/53] fix: patched issues with static --- src/core/server/app/index.ts | 2 +- src/core/server/app/middleware/serveStatic.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/server/app/index.ts b/src/core/server/app/index.ts index 9c418db21..c6663b4e8 100644 --- a/src/core/server/app/index.ts +++ b/src/core/server/app/index.ts @@ -97,7 +97,7 @@ function setupViews(options: AppOptions) { const { parent } = options; // configure the default views directory. - const views = path.join(__dirname, "..", "..", "..", "static"); + const views = path.join(__dirname, "..", "..", "..", "..", "dist", "static"); parent.set("views", views); // Reconfigure nunjucks. diff --git a/src/core/server/app/middleware/serveStatic.ts b/src/core/server/app/middleware/serveStatic.ts index ed21e62a2..418dede24 100644 --- a/src/core/server/app/middleware/serveStatic.ts +++ b/src/core/server/app/middleware/serveStatic.ts @@ -2,7 +2,7 @@ import serveStatic from "express-static-gzip"; import path from "path"; const staticPath = path.resolve( - path.join(__dirname, "..", "..", "..", "..", "static", "assets") + path.join(__dirname, "..", "..", "..", "..", "..", "dist", "static", "assets") ); export default serveStatic(staticPath, { index: false }); From ebe43a28e62f8e160f31a625da8f419ba0933e6c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 21 Sep 2018 13:16:21 -0600 Subject: [PATCH 12/53] feat: presence and count improvements --- .../server/graph/tenant/loaders/comments.ts | 8 +- .../server/graph/tenant/resolvers/comment.ts | 4 +- .../server/graph/tenant/schema/schema.graphql | 76 ++++++++++++++++-- .../models/__snapshots__/actions.spec.ts.snap | 6 +- src/core/server/models/actions.ts | 80 +++++++++++-------- 5 files changed, 125 insertions(+), 49 deletions(-) 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: From ce1002b16c42a470583bd387c4c2e89b4f2ca8b3 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 21 Sep 2018 14:55:00 -0600 Subject: [PATCH 13/53] fix: updated @types/mongodb --- package-lock.json | 18 ++++++++---------- package.json | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index db9901d58..e1ad0e400 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1989,9 +1989,9 @@ } }, "@types/mongodb": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.1.7.tgz", - "integrity": "sha512-ljS4mE9o3apEkI59pftdnLf3b1ZczMPtXWp1myrhR+E/CLk0O5SjbTt6Rn3OxMB+Qc2eAytrQVufa4y1pFqE2A==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.1.8.tgz", + "integrity": "sha512-5higsHdPx63XKIh5hjr5GGrCCErBqEbpZZiNsUcqk97mMDpCBH9R4dRi/T8bcMrQItCdL+wecagdAj3JPKkuVg==", "dev": true, "requires": { "@types/bson": "*", @@ -10222,8 +10222,11 @@ "resolved": "https://registry.npmjs.org/fluent-intl-polyfill/-/fluent-intl-polyfill-0.1.0.tgz", "integrity": "sha1-ETOUSrJHeINHOZVZaIPg05z4hc8=", "dev": true, - "requires": { - "intl-pluralrules": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b" + "dependencies": { + "intl-pluralrules": { + "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b", + "from": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b" + } } }, "fluent-langneg": { @@ -12667,11 +12670,6 @@ "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, - "intl-pluralrules": { - "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b", - "from": "github:projectfluent/IntlPluralRules#module", - "dev": true - }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", diff --git a/package.json b/package.json index f9ad289f8..30db5c9ef 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "@types/lodash": "^4.14.111", "@types/luxon": "^0.5.3", "@types/mini-css-extract-plugin": "^0.2.0", - "@types/mongodb": "^3.1.7", + "@types/mongodb": "^3.1.8", "@types/ms": "^0.7.30", "@types/node": "^10.5.2", "@types/node-fetch": "^2.1.2", From daee96dc4c445171f45c00c009eefdf44d93244e Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 21 Sep 2018 15:29:34 -0600 Subject: [PATCH 14/53] review: applied some review changes --- package-lock.json | 12 +++++++----- src/core/server/graph/tenant/loaders/comments.ts | 6 +++--- src/core/server/graph/tenant/resolvers/comment.ts | 6 ++++-- src/core/server/graph/tenant/schema/schema.graphql | 4 ++-- src/core/server/models/actions.ts | 8 ++++---- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index e1ad0e400..96e667219 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10222,11 +10222,8 @@ "resolved": "https://registry.npmjs.org/fluent-intl-polyfill/-/fluent-intl-polyfill-0.1.0.tgz", "integrity": "sha1-ETOUSrJHeINHOZVZaIPg05z4hc8=", "dev": true, - "dependencies": { - "intl-pluralrules": { - "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b", - "from": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b" - } + "requires": { + "intl-pluralrules": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b" } }, "fluent-langneg": { @@ -12670,6 +12667,11 @@ "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, + "intl-pluralrules": { + "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b", + "from": "github:projectfluent/IntlPluralRules#module", + "dev": true + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", diff --git a/src/core/server/graph/tenant/loaders/comments.ts b/src/core/server/graph/tenant/loaders/comments.ts index c12fe4296..c90c1ef6b 100644 --- a/src/core/server/graph/tenant/loaders/comments.ts +++ b/src/core/server/graph/tenant/loaders/comments.ts @@ -9,7 +9,7 @@ import { } from "talk-server/graph/tenant/schema/__generated__/types"; import { ACTION_ITEM_TYPE, - retrieveManyAuthoredActionPresence, + retrieveManyUserActionPresence, } 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) ), - retrieveAuthoredActionPresence: new DataLoader( + retrieveMyActionPresence: new DataLoader( (itemIDs: string[]) => - retrieveManyAuthoredActionPresence( + retrieveManyUserActionPresence( 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 4c552727b..25b9b2f73 100644 --- a/src/core/server/graph/tenant/resolvers/comment.ts +++ b/src/core/server/graph/tenant/resolvers/comment.ts @@ -19,8 +19,10 @@ const Comment: GQLCommentTypeResolver = { replies: (comment, input, ctx) => ctx.loaders.Comments.forParent(comment.asset_id, comment.id, input), actionCounts: comment => decodeActionCounts(comment.action_counts), - authoredActionPresence: (comment, input, ctx) => - ctx.loaders.Comments.retrieveAuthoredActionPresence.load(comment.id), + myActionPresence: (comment, input, ctx) => + ctx.user + ? ctx.loaders.Comments.retrieveMyActionPresence.load(comment.id) + : null, }; export default Comment; diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 4e1a05047..f8c00d98b 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -833,10 +833,10 @@ type Comment { actionCounts: ActionCounts! """ - authoredActionPresence stores the presense information for all the actions + myActionPresence stores the presense information for all the actions left by the current User on this Comment. """ - authoredActionPresence: ActionPresence! @auth + myActionPresence: ActionPresence } type PageInfo { diff --git a/src/core/server/models/actions.ts b/src/core/server/models/actions.ts index 7cd7a1ba6..a9e3a4654 100644 --- a/src/core/server/models/actions.ts +++ b/src/core/server/models/actions.ts @@ -1,5 +1,5 @@ import Joi from "joi"; -import { pick } from "lodash"; +import { camelCase, pick } from "lodash"; import { Db } from "mongodb"; import uuid from "uuid"; @@ -190,10 +190,10 @@ export async function createActions( } /** - * retrieveManyAuthoredActionPresence returns the action presence for a specific + * retrieveManyUserActionPresence returns the action presence for a specific * user. */ -export async function retrieveManyAuthoredActionPresence( +export async function retrieveManyUserActionPresence( mongo: Db, tenantID: string, userID: string | null, @@ -226,7 +226,7 @@ export async function retrieveManyAuthoredActionPresence( itemActions.reduce( (actionPresence, { action_type }) => ({ ...actionPresence, - [action_type.toLowerCase()]: true, + [camelCase(action_type)]: true, }), { reaction: false, From d8417eda2e8d0ebe17541f3cb57674217634f9e8 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Sun, 23 Sep 2018 20:43:43 -0600 Subject: [PATCH 15/53] feat: introduce asset action count caching --- .../server/graph/tenant/resolvers/asset.ts | 2 + .../server/graph/tenant/schema/schema.graphql | 5 ++ src/core/server/models/asset.ts | 53 ++++++++++++++----- src/core/server/models/comment.ts | 6 +-- src/core/server/services/comments/index.ts | 14 ++++- 5 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/core/server/graph/tenant/resolvers/asset.ts b/src/core/server/graph/tenant/resolvers/asset.ts index e5487ba08..98c09ca88 100644 --- a/src/core/server/graph/tenant/resolvers/asset.ts +++ b/src/core/server/graph/tenant/resolvers/asset.ts @@ -1,4 +1,5 @@ import { GQLAssetTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types"; +import { decodeActionCounts } from "talk-server/models/actions"; import { Asset } from "talk-server/models/asset"; const Asset: GQLAssetTypeResolver = { @@ -6,6 +7,7 @@ const Asset: GQLAssetTypeResolver = { ctx.loaders.Comments.forAsset(asset.id, input), // TODO: implement this. isClosed: () => false, + actionCounts: asset => decodeActionCounts(asset.action_counts), }; export default Asset; diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index f8c00d98b..c3cdf71b4 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -930,6 +930,11 @@ type Asset { after: Cursor ): CommentsConnection! + """ + actionCounts stores the counts of all the actions for the Comment. + """ + actionCounts: ActionCounts! + """ author is the authors listed in the meta tags for the Asset. """ diff --git a/src/core/server/models/asset.ts b/src/core/server/models/asset.ts index 8d412c522..0d15cf6d3 100644 --- a/src/core/server/models/asset.ts +++ b/src/core/server/models/asset.ts @@ -1,9 +1,9 @@ -import { defaults } from "lodash"; import { Db } from "mongodb"; import uuid from "uuid"; import { Omit } from "talk-common/types"; import { dotize } from "talk-common/utils/dotize"; +import { EncodedActionCounts } from "talk-server/models/actions"; import { ModerationSettings } from "talk-server/models/settings"; import { TenantResource } from "talk-server/models/tenant"; @@ -27,6 +27,11 @@ export interface Asset extends TenantResource { modified_date?: Date; created_at: Date; + /** + * action_counts stores all the action counts for all Comment's on this Asset. + */ + action_counts: EncodedActionCounts; + /** * settings provides a point where the settings can be overriden for a * specific Asset. @@ -51,17 +56,13 @@ export async function upsertAsset( // Create the asset, optionally sourcing the id from the input, additionally // porting in the tenant_id. const update: { $setOnInsert: Asset } = { - $setOnInsert: defaults( - { - url, - tenant_id: tenantID, - created_at: now, - }, - { id }, - { - id: uuid.v4(), - } - ), + $setOnInsert: { + id: id ? id : uuid.v4(), + url, + tenant_id: tenantID, + created_at: now, + action_counts: {}, + }, }; // Perform the find and update operation to try and find and or create the @@ -193,3 +194,31 @@ export async function updateAsset( return result.value || null; } + +/** + * updateAssetActionCounts will update the given comment's action counts on + * the Asset. + * + * @param mongo the database handle + * @param tenantID the id of the Tenant + * @param id the id of the Asset being updated + * @param actionCounts the action counts to merge into the Asset + */ +export async function updateAssetActionCounts( + mongo: Db, + tenantID: string, + id: string, + actionCounts: EncodedActionCounts +) { + const result = await collection(mongo).findOneAndUpdate( + { id, tenant_id: tenantID }, + // Update all the specific action counts that are associated with each of + // the counts. + { $inc: dotize({ action_counts: actionCounts }) }, + // False to return the updated document instead of the original + // document. + { returnOriginal: false } + ); + + return result.value; +} diff --git a/src/core/server/models/comment.ts b/src/core/server/models/comment.ts index 2fa32efc1..99780bda1 100644 --- a/src/core/server/models/comment.ts +++ b/src/core/server/models/comment.ts @@ -378,9 +378,9 @@ function applyInputToQuery(input: ConnectionInput, query: Query) { * updateCommentActionCounts will update the given comment's action counts. * * @param mongo the database handle - * @param tenantID the id of the tenant - * @param id the id of the comment being updated - * @param actionCounts the action counts to merge into the comment + * @param tenantID the id of the Tenant + * @param id the id of the Comment being updated + * @param actionCounts the action counts to merge into the Comment */ export async function updateCommentActionCounts( mongo: Db, diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index 2bddddc44..7fa33fa75 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -7,7 +7,10 @@ import { createActions, encodeActionCounts, } from "talk-server/models/actions"; -import { retrieveAsset } from "talk-server/models/asset"; +import { + retrieveAsset, + updateAssetActionCounts, +} from "talk-server/models/asset"; import { Comment, createComment, @@ -85,6 +88,7 @@ export async function create( // Insert and handle creating the actions. comment = await addCommentActions(mongo, tenant, comment, inputs); + // asse } if (input.parent_id) { @@ -196,6 +200,14 @@ async function addCommentActions( actionCounts ); + // Update the Asset with the updated action counts. + await updateAssetActionCounts( + mongo, + tenant.id, + comment.asset_id, + actionCounts + ); + // Check to see if there was an actual comment returned (there should // have been, we just created it!). if (!updatedComment) { From 936347e8e4bc54f675607a7f8e657086bc78bc15 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 24 Sep 2018 11:36:44 -0600 Subject: [PATCH 16/53] fix: restricted action counts on Asset's --- src/core/server/graph/tenant/schema/schema.graphql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index c3cdf71b4..2f275ca0f 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -931,9 +931,10 @@ type Asset { ): CommentsConnection! """ - actionCounts stores the counts of all the actions for the Comment. + actionCounts stores the counts of all the actions against this Asset and it's + Comments. """ - actionCounts: ActionCounts! + actionCounts: ActionCounts! @auth(roles: [ADMIN, MODERATOR]) """ author is the authors listed in the meta tags for the Asset. From 8c3b0071c2a2d43e1b644d82b0c9a773caf26e31 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 24 Sep 2018 12:32:57 -0600 Subject: [PATCH 17/53] fix: renamed, middleware fixes --- src/core/server/app/middleware/error.ts | 10 ++-------- src/core/server/app/middleware/playground.ts | 15 ++++++++++++++- src/core/server/graph/tenant/loaders/comments.ts | 2 +- src/core/server/graph/tenant/resolvers/asset.ts | 2 +- src/core/server/graph/tenant/resolvers/comment.ts | 2 +- .../models/{actions.spec.ts => action.spec.ts} | 2 +- src/core/server/models/{actions.ts => action.ts} | 1 + src/core/server/models/asset.ts | 2 +- src/core/server/models/comment.ts | 2 +- src/core/server/models/user.ts | 2 +- src/core/server/services/comments/index.ts | 2 +- .../services/comments/moderation/index.spec.ts | 2 +- .../server/services/comments/moderation/index.ts | 2 +- .../comments/moderation/phases/commentLength.ts | 2 +- .../services/comments/moderation/phases/karma.ts | 2 +- .../services/comments/moderation/phases/links.ts | 2 +- .../services/comments/moderation/phases/spam.ts | 2 +- .../services/comments/moderation/phases/toxic.ts | 2 +- .../comments/moderation/phases/wordlist.ts | 2 +- 19 files changed, 33 insertions(+), 25 deletions(-) rename src/core/server/models/{actions.spec.ts => action.spec.ts} (99%) rename src/core/server/models/{actions.ts => action.ts} (99%) diff --git a/src/core/server/app/middleware/error.ts b/src/core/server/app/middleware/error.ts index b7be782cb..8eadfc187 100644 --- a/src/core/server/app/middleware/error.ts +++ b/src/core/server/app/middleware/error.ts @@ -9,15 +9,9 @@ export const errorHandler: ErrorRequestHandler = (err, req, res, next) => { // TODO: handle better when we improve errors. if (err.message === "not found") { // TODO: handle better when we improve errors. - res - .status(404) - .send(err.message) - .end(); + res.status(404).send(err.message); } else { // TODO: handle better when we improve errors. - res - .status(500) - .send(err.message) - .end(); + res.status(500).send(err.message); } }; diff --git a/src/core/server/app/middleware/playground.ts b/src/core/server/app/middleware/playground.ts index 1873a0537..0364d10aa 100644 --- a/src/core/server/app/middleware/playground.ts +++ b/src/core/server/app/middleware/playground.ts @@ -1,4 +1,17 @@ +import { RequestHandler } from "express"; import { MiddlewareOptions } from "graphql-playground-html"; import playground from "graphql-playground-middleware-express"; -export default (options: MiddlewareOptions) => playground(options); +export default (options: MiddlewareOptions): RequestHandler => ( + req, + res, + next +) => { + try { + playground(options)(req, res, () => { + // The playground calls next() when it's not supposed to. + }); + } catch (err) { + return next(err); + } +}; diff --git a/src/core/server/graph/tenant/loaders/comments.ts b/src/core/server/graph/tenant/loaders/comments.ts index c90c1ef6b..abd78803f 100644 --- a/src/core/server/graph/tenant/loaders/comments.ts +++ b/src/core/server/graph/tenant/loaders/comments.ts @@ -10,7 +10,7 @@ import { import { ACTION_ITEM_TYPE, retrieveManyUserActionPresence, -} from "talk-server/models/actions"; +} from "talk-server/models/action"; import { retrieveCommentAssetConnection, retrieveCommentRepliesConnection, diff --git a/src/core/server/graph/tenant/resolvers/asset.ts b/src/core/server/graph/tenant/resolvers/asset.ts index 98c09ca88..f1ebc640c 100644 --- a/src/core/server/graph/tenant/resolvers/asset.ts +++ b/src/core/server/graph/tenant/resolvers/asset.ts @@ -1,5 +1,5 @@ import { GQLAssetTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types"; -import { decodeActionCounts } from "talk-server/models/actions"; +import { decodeActionCounts } from "talk-server/models/action"; import { Asset } from "talk-server/models/asset"; const Asset: GQLAssetTypeResolver = { diff --git a/src/core/server/graph/tenant/resolvers/comment.ts b/src/core/server/graph/tenant/resolvers/comment.ts index 25b9b2f73..5919732d0 100644 --- a/src/core/server/graph/tenant/resolvers/comment.ts +++ b/src/core/server/graph/tenant/resolvers/comment.ts @@ -1,5 +1,5 @@ import { GQLCommentTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types"; -import { decodeActionCounts } from "talk-server/models/actions"; +import { decodeActionCounts } from "talk-server/models/action"; import { Comment } from "talk-server/models/comment"; const Comment: GQLCommentTypeResolver = { diff --git a/src/core/server/models/actions.spec.ts b/src/core/server/models/action.spec.ts similarity index 99% rename from src/core/server/models/actions.spec.ts rename to src/core/server/models/action.spec.ts index a09595003..f89f587eb 100644 --- a/src/core/server/models/actions.spec.ts +++ b/src/core/server/models/action.spec.ts @@ -6,7 +6,7 @@ import { decodeActionCounts, encodeActionCounts, validateAction, -} from "talk-server/models/actions"; +} from "talk-server/models/action"; describe("#encodeActionCounts", () => { it("generates the action counts correctly", () => { diff --git a/src/core/server/models/actions.ts b/src/core/server/models/action.ts similarity index 99% rename from src/core/server/models/actions.ts rename to src/core/server/models/action.ts index a9e3a4654..2c1834c4e 100644 --- a/src/core/server/models/actions.ts +++ b/src/core/server/models/action.ts @@ -186,6 +186,7 @@ export async function createActions( tenantID: string, inputs: CreateActionInput[] ): Promise { + // TODO: (wyattjoh) replace with a batch write. return Promise.all(inputs.map(input => createAction(mongo, tenantID, input))); } diff --git a/src/core/server/models/asset.ts b/src/core/server/models/asset.ts index 0d15cf6d3..aa3289503 100644 --- a/src/core/server/models/asset.ts +++ b/src/core/server/models/asset.ts @@ -3,7 +3,7 @@ import uuid from "uuid"; import { Omit } from "talk-common/types"; import { dotize } from "talk-common/utils/dotize"; -import { EncodedActionCounts } from "talk-server/models/actions"; +import { EncodedActionCounts } from "talk-server/models/action"; import { ModerationSettings } from "talk-server/models/settings"; import { TenantResource } from "talk-server/models/tenant"; diff --git a/src/core/server/models/comment.ts b/src/core/server/models/comment.ts index 99780bda1..e2ff8da1d 100644 --- a/src/core/server/models/comment.ts +++ b/src/core/server/models/comment.ts @@ -7,7 +7,7 @@ import { GQLCOMMENT_SORT, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; -import { EncodedActionCounts } from "talk-server/models/actions"; +import { EncodedActionCounts } from "talk-server/models/action"; import { Connection, Cursor, diff --git a/src/core/server/models/user.ts b/src/core/server/models/user.ts index 8bfac404a..a89d3b42a 100644 --- a/src/core/server/models/user.ts +++ b/src/core/server/models/user.ts @@ -7,7 +7,7 @@ import { GQLUSER_ROLE, GQLUSER_USERNAME_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; -import { EncodedActionCounts } from "talk-server/models/actions"; +import { EncodedActionCounts } from "talk-server/models/action"; import { FilterQuery } from "talk-server/models/query"; import { TenantResource } from "talk-server/models/tenant"; diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index 7fa33fa75..7acc5f5bb 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -6,7 +6,7 @@ import { CreateActionInput, createActions, encodeActionCounts, -} from "talk-server/models/actions"; +} from "talk-server/models/action"; import { retrieveAsset, updateAssetActionCounts, diff --git a/src/core/server/services/comments/moderation/index.spec.ts b/src/core/server/services/comments/moderation/index.spec.ts index f0367bb65..3adcccfd2 100644 --- a/src/core/server/services/comments/moderation/index.spec.ts +++ b/src/core/server/services/comments/moderation/index.spec.ts @@ -2,7 +2,7 @@ import { GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; -import { ACTION_TYPE } from "talk-server/models/actions"; +import { ACTION_TYPE } from "talk-server/models/action"; import { compose, ModerationPhaseContext, diff --git a/src/core/server/services/comments/moderation/index.ts b/src/core/server/services/comments/moderation/index.ts index cc4d91a34..7bf497f75 100644 --- a/src/core/server/services/comments/moderation/index.ts +++ b/src/core/server/services/comments/moderation/index.ts @@ -1,6 +1,6 @@ import { Omit, Promiseable } from "talk-common/types"; import { GQLCOMMENT_STATUS } from "talk-server/graph/tenant/schema/__generated__/types"; -import { CreateActionInput } from "talk-server/models/actions"; +import { CreateActionInput } from "talk-server/models/action"; import { Asset } from "talk-server/models/asset"; import { Comment } from "talk-server/models/comment"; import { Tenant } from "talk-server/models/tenant"; diff --git a/src/core/server/services/comments/moderation/phases/commentLength.ts b/src/core/server/services/comments/moderation/phases/commentLength.ts index 65e37df03..5ea6393fd 100644 --- a/src/core/server/services/comments/moderation/phases/commentLength.ts +++ b/src/core/server/services/comments/moderation/phases/commentLength.ts @@ -2,7 +2,7 @@ import { GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; -import { ACTION_TYPE } from "talk-server/models/actions"; +import { ACTION_TYPE } from "talk-server/models/action"; import { ModerationSettings } from "talk-server/models/settings"; import { IntermediateModerationPhase, diff --git a/src/core/server/services/comments/moderation/phases/karma.ts b/src/core/server/services/comments/moderation/phases/karma.ts index 4fdf50fec..91ffb069f 100755 --- a/src/core/server/services/comments/moderation/phases/karma.ts +++ b/src/core/server/services/comments/moderation/phases/karma.ts @@ -2,7 +2,7 @@ import { GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; -import { ACTION_TYPE } from "talk-server/models/actions"; +import { ACTION_TYPE } from "talk-server/models/action"; import { IntermediateModerationPhase, IntermediatePhaseResult, diff --git a/src/core/server/services/comments/moderation/phases/links.ts b/src/core/server/services/comments/moderation/phases/links.ts index 4bf584428..ae2c0e723 100755 --- a/src/core/server/services/comments/moderation/phases/links.ts +++ b/src/core/server/services/comments/moderation/phases/links.ts @@ -5,7 +5,7 @@ import { GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; -import { ACTION_TYPE } from "talk-server/models/actions"; +import { ACTION_TYPE } from "talk-server/models/action"; import { ModerationSettings } from "talk-server/models/settings"; import { IntermediateModerationPhase, diff --git a/src/core/server/services/comments/moderation/phases/spam.ts b/src/core/server/services/comments/moderation/phases/spam.ts index 2e73b90dc..5e3c495a7 100644 --- a/src/core/server/services/comments/moderation/phases/spam.ts +++ b/src/core/server/services/comments/moderation/phases/spam.ts @@ -5,7 +5,7 @@ import { GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; import logger from "talk-server/logger"; -import { ACTION_TYPE } from "talk-server/models/actions"; +import { ACTION_TYPE } from "talk-server/models/action"; import { IntermediateModerationPhase, IntermediatePhaseResult, diff --git a/src/core/server/services/comments/moderation/phases/toxic.ts b/src/core/server/services/comments/moderation/phases/toxic.ts index 0a9cda887..ec3a9f6ae 100644 --- a/src/core/server/services/comments/moderation/phases/toxic.ts +++ b/src/core/server/services/comments/moderation/phases/toxic.ts @@ -9,7 +9,7 @@ import { GQLPerspectiveExternalIntegration, } from "talk-server/graph/tenant/schema/__generated__/types"; import logger from "talk-server/logger"; -import { ACTION_TYPE } from "talk-server/models/actions"; +import { ACTION_TYPE } from "talk-server/models/action"; import { IntermediateModerationPhase, IntermediatePhaseResult, diff --git a/src/core/server/services/comments/moderation/phases/wordlist.ts b/src/core/server/services/comments/moderation/phases/wordlist.ts index 4a4eebb42..05f6431b9 100755 --- a/src/core/server/services/comments/moderation/phases/wordlist.ts +++ b/src/core/server/services/comments/moderation/phases/wordlist.ts @@ -2,7 +2,7 @@ import { GQLCOMMENT_FLAG_REASON, GQLCOMMENT_STATUS, } from "talk-server/graph/tenant/schema/__generated__/types"; -import { ACTION_TYPE } from "talk-server/models/actions"; +import { ACTION_TYPE } from "talk-server/models/action"; import { IntermediateModerationPhase, IntermediatePhaseResult, From dee14709c873511104deb99484d64ab12f00cc2a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 24 Sep 2018 12:34:27 -0600 Subject: [PATCH 18/53] feat: added reaction adding mutation --- .../server/graph/tenant/mutators/comment.ts | 11 +++++- .../server/graph/tenant/resolvers/mutation.ts | 12 ++++--- .../server/graph/tenant/schema/schema.graphql | 36 +++++++++++++++++++ src/core/server/services/comments/index.ts | 29 +++++++++++++++ 4 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/core/server/graph/tenant/mutators/comment.ts b/src/core/server/graph/tenant/mutators/comment.ts index aeaac70ee..8a8e86066 100644 --- a/src/core/server/graph/tenant/mutators/comment.ts +++ b/src/core/server/graph/tenant/mutators/comment.ts @@ -1,9 +1,14 @@ import TenantContext from "talk-server/graph/tenant/context"; import { GQLCreateCommentInput, + GQLCreateCommentReactionInput, GQLEditCommentInput, } from "talk-server/graph/tenant/schema/__generated__/types"; -import { create, edit } from "talk-server/services/comments"; +import { + create, + createCommentReaction, + edit, +} from "talk-server/services/comments"; export default (ctx: TenantContext) => ({ create: (input: GQLCreateCommentInput) => @@ -30,4 +35,8 @@ export default (ctx: TenantContext) => ({ }, ctx.req ), + createReaction: (input: GQLCreateCommentReactionInput) => + createCommentReaction(ctx.mongo, ctx.tenant, ctx.user!, { + item_id: input.commentID, + }), }); diff --git a/src/core/server/graph/tenant/resolvers/mutation.ts b/src/core/server/graph/tenant/resolvers/mutation.ts index 5b7c65dc4..2ff79b8d2 100644 --- a/src/core/server/graph/tenant/resolvers/mutation.ts +++ b/src/core/server/graph/tenant/resolvers/mutation.ts @@ -9,10 +9,10 @@ const Mutation: GQLMutationTypeResolver = { const comment = await ctx.mutators.Comment.create(input); return { edge: { - // (cvle) - // Depending on the sort we can't determine the accurate cursor - // in a performant way, so we return null instead. - // It seems that Relay does not directly use this value... + // NOTE: (cvle) + // Depending on the sort we can't determine the accurate cursor in a + // performant way, so we return null instead. It seems that Relay does + // not directly use this value. cursor: null, node: comment, }, @@ -23,6 +23,10 @@ const Mutation: GQLMutationTypeResolver = { settings: await ctx.mutators.Settings.update(input.settings), clientMutationId: input.clientMutationId, }), + createCommentReaction: async (source, { input }, ctx) => ({ + comment: await ctx.mutators.Comment.createReaction(input), + clientMutationId: input.clientMutationId, + }), }; export default Mutation; diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 2f275ca0f..f03de4ffc 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -1478,6 +1478,34 @@ type UpdateSettingsPayload { ## Mutation ################## +input CreateCommentReactionInput { + """ + commentID is the Comment's ID that we want to create a Reaction on. + """ + commentID: ID! + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + +type CreateCommentReactionPayload { + """ + comment is the Comment that the Reaction was created on. + """ + comment: Comment + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + +################## +## Mutation +################## + type Mutation { """ createComment will create a Comment as the current logged in User. @@ -1495,6 +1523,14 @@ type Mutation { """ updateSettings(input: UpdateSettingsInput!): UpdateSettingsPayload @auth(roles: [ADMIN, MODERATOR]) + + """ + createCommentReaction will create a Reaction authored by the current logged in + user on a Comment. + """ + createCommentReaction( + input: CreateCommentReactionInput! + ): CreateCommentReactionPayload @auth } ################################################################################ diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index 7acc5f5bb..ee1fb969d 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -3,6 +3,7 @@ import { Db } from "mongodb"; import { Omit } from "talk-common/types"; import { ACTION_ITEM_TYPE, + ACTION_TYPE, CreateActionInput, createActions, encodeActionCounts, @@ -220,3 +221,31 @@ async function addCommentActions( return comment; } + +export type CreateCommentReaction = Pick; + +export async function createCommentReaction( + mongo: Db, + tenant: Tenant, + author: User, + input: CreateCommentReaction +) { + // Get the Comment that we are leaving the Action on. + let comment = await retrieveComment(mongo, tenant.id, input.item_id); + if (!comment) { + // TODO: replace to match error returned by the models/comments.ts + throw new Error("comment not found"); + } + + // Add the comment actions, and return the Comment that we just updated. + comment = await addCommentActions(mongo, tenant, comment, [ + { + action_type: ACTION_TYPE.REACTION, + item_type: ACTION_ITEM_TYPE.COMMENTS, + item_id: input.item_id, + user_id: author.id, + }, + ]); + + return comment; +} From 10748347b9293ad3f12c31eadaeaf15b3d42e1bb Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 24 Sep 2018 12:50:30 -0600 Subject: [PATCH 19/53] feat: support deleting comment reactions --- .../server/graph/tenant/mutators/comment.ts | 10 ++- .../server/graph/tenant/resolvers/mutation.ts | 4 ++ .../server/graph/tenant/schema/schema.graphql | 38 +++++++++- src/core/server/models/action.ts | 21 ++++++ src/core/server/services/comments/index.ts | 72 ++++++++++++++++++- 5 files changed, 141 insertions(+), 4 deletions(-) diff --git a/src/core/server/graph/tenant/mutators/comment.ts b/src/core/server/graph/tenant/mutators/comment.ts index 8a8e86066..ba0211d1b 100644 --- a/src/core/server/graph/tenant/mutators/comment.ts +++ b/src/core/server/graph/tenant/mutators/comment.ts @@ -2,11 +2,13 @@ import TenantContext from "talk-server/graph/tenant/context"; import { GQLCreateCommentInput, GQLCreateCommentReactionInput, + GQLDeleteCommentReactionInput, GQLEditCommentInput, } from "talk-server/graph/tenant/schema/__generated__/types"; import { create, - createCommentReaction, + createReaction, + deleteReaction, edit, } from "talk-server/services/comments"; @@ -36,7 +38,11 @@ export default (ctx: TenantContext) => ({ ctx.req ), createReaction: (input: GQLCreateCommentReactionInput) => - createCommentReaction(ctx.mongo, ctx.tenant, ctx.user!, { + createReaction(ctx.mongo, ctx.tenant, ctx.user!, { + item_id: input.commentID, + }), + deleteReaction: (input: GQLDeleteCommentReactionInput) => + deleteReaction(ctx.mongo, ctx.tenant, ctx.user!, { item_id: input.commentID, }), }); diff --git a/src/core/server/graph/tenant/resolvers/mutation.ts b/src/core/server/graph/tenant/resolvers/mutation.ts index 2ff79b8d2..25746017f 100644 --- a/src/core/server/graph/tenant/resolvers/mutation.ts +++ b/src/core/server/graph/tenant/resolvers/mutation.ts @@ -27,6 +27,10 @@ const Mutation: GQLMutationTypeResolver = { comment: await ctx.mutators.Comment.createReaction(input), clientMutationId: input.clientMutationId, }), + deleteCommentReaction: async (source, { input }, ctx) => ({ + comment: await ctx.mutators.Comment.deleteReaction(input), + clientMutationId: input.clientMutationId, + }), }; export default Mutation; diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index f03de4ffc..6a4b05051 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -1475,7 +1475,7 @@ type UpdateSettingsPayload { } ################## -## Mutation +## createCommentReaction ################## input CreateCommentReactionInput { @@ -1502,6 +1502,34 @@ type CreateCommentReactionPayload { clientMutationId: String! } +################## +## deleteCommentReaction +################## + +input DeleteCommentReactionInput { + """ + commentID is the Comment's ID that we want to delete a Reaction on. + """ + commentID: ID! + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + +type DeleteCommentReactionPayload { + """ + comment is the Comment that the Reaction was deleted on. + """ + comment: Comment + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + ################## ## Mutation ################## @@ -1531,6 +1559,14 @@ type Mutation { createCommentReaction( input: CreateCommentReactionInput! ): CreateCommentReactionPayload @auth + + """ + deleteCommentReaction will delete a Reaction authored by the current logged in + user on a Comment if it exists. + """ + deleteCommentReaction( + input: CreateCommentReactionInput! + ): CreateCommentReactionPayload @auth } ################################################################################ diff --git a/src/core/server/models/action.ts b/src/core/server/models/action.ts index 2c1834c4e..c5b8c36d9 100644 --- a/src/core/server/models/action.ts +++ b/src/core/server/models/action.ts @@ -315,6 +315,27 @@ export function encodeActionCounts(...actions: Action[]): EncodedActionCounts { return actionCounts; } +/** + * invertEncodedActionCounts will allow inverting of the action count object. + * + * @param actionCounts the encoded action counts to invert + */ +export function invertEncodedActionCounts( + actionCounts: EncodedActionCounts +): EncodedActionCounts { + for (const key in actionCounts) { + if (!actionCounts.hasOwnProperty(key)) { + continue; + } + + if (actionCounts[key] > 0) { + actionCounts[key] = -actionCounts[key]; + } + } + + return actionCounts; +} + /** * encodeActionCountKeys encodes the action into string keys which represents * the groupings as seen in `EncodedActionCounts`. diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index ee1fb969d..dde8826af 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -6,7 +6,10 @@ import { ACTION_TYPE, CreateActionInput, createActions, + deleteAction, + DeleteActionInput, encodeActionCounts, + invertEncodedActionCounts, } from "talk-server/models/action"; import { retrieveAsset, @@ -222,9 +225,50 @@ async function addCommentActions( return comment; } +async function deleteCommentAction( + mongo: Db, + tenant: Tenant, + comment: Readonly, + input: DeleteActionInput +): Promise> { + // Create each of the actions, returning each of the action results. + const { wasDeleted, action } = await deleteAction(mongo, tenant.id, input); + if (wasDeleted) { + // Compute the action counts, and invert them (because we're deleting an + // action). + const actionCounts = invertEncodedActionCounts(encodeActionCounts(action!)); + + // Update the comment action counts here. + const updatedComment = await updateCommentActionCounts( + mongo, + tenant.id, + comment.id, + actionCounts + ); + + // Update the Asset with the updated action counts. + await updateAssetActionCounts( + mongo, + tenant.id, + comment.asset_id, + actionCounts + ); + + // Check to see if there was an actual comment returned. + if (!updatedComment) { + // TODO: (wyattjoh) return a better error. + throw new Error("could not update comment action counts"); + } + + return updatedComment; + } + + return comment; +} + export type CreateCommentReaction = Pick; -export async function createCommentReaction( +export async function createReaction( mongo: Db, tenant: Tenant, author: User, @@ -249,3 +293,29 @@ export async function createCommentReaction( return comment; } + +export type DeleteCommentReaction = Pick; + +export async function deleteReaction( + mongo: Db, + tenant: Tenant, + author: User, + input: DeleteCommentReaction +) { + // Get the Comment that we are leaving the Action on. + let comment = await retrieveComment(mongo, tenant.id, input.item_id); + if (!comment) { + // TODO: replace to match error returned by the models/comments.ts + throw new Error("comment not found"); + } + + // Add the comment actions, and return the Comment that we just updated. + comment = await deleteCommentAction(mongo, tenant, comment, { + action_type: ACTION_TYPE.REACTION, + item_type: ACTION_ITEM_TYPE.COMMENTS, + item_id: input.item_id, + user_id: author.id, + }); + + return comment; +} From a19ac4e2ccf29025073711784d61fc05c3d2105d Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 24 Sep 2018 13:08:16 -0600 Subject: [PATCH 20/53] fix: addressed test updates --- .../__snapshots__/{actions.spec.ts.snap => action.spec.ts.snap} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/core/server/models/__snapshots__/{actions.spec.ts.snap => action.spec.ts.snap} (100%) diff --git a/src/core/server/models/__snapshots__/actions.spec.ts.snap b/src/core/server/models/__snapshots__/action.spec.ts.snap similarity index 100% rename from src/core/server/models/__snapshots__/actions.spec.ts.snap rename to src/core/server/models/__snapshots__/action.spec.ts.snap From 3cdaa284ed759706a269f2c879f860abbb7eb720 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 24 Sep 2018 13:46:13 -0600 Subject: [PATCH 21/53] feat: added flag creation and deletion mutations --- .../server/graph/tenant/mutators/comment.ts | 13 +++ .../server/graph/tenant/resolvers/mutation.ts | 8 ++ .../server/graph/tenant/schema/schema.graphql | 79 ++++++++++++++++++- src/core/server/models/action.ts | 19 ++++- src/core/server/services/comments/index.ts | 58 ++++++++++++++ 5 files changed, 173 insertions(+), 4 deletions(-) diff --git a/src/core/server/graph/tenant/mutators/comment.ts b/src/core/server/graph/tenant/mutators/comment.ts index ba0211d1b..734e724b5 100644 --- a/src/core/server/graph/tenant/mutators/comment.ts +++ b/src/core/server/graph/tenant/mutators/comment.ts @@ -1,13 +1,17 @@ import TenantContext from "talk-server/graph/tenant/context"; import { + GQLCreateCommentFlagInput, GQLCreateCommentInput, GQLCreateCommentReactionInput, + GQLDeleteCommentFlagInput, GQLDeleteCommentReactionInput, GQLEditCommentInput, } from "talk-server/graph/tenant/schema/__generated__/types"; import { create, + createFlag, createReaction, + deleteFlag, deleteReaction, edit, } from "talk-server/services/comments"; @@ -45,4 +49,13 @@ export default (ctx: TenantContext) => ({ deleteReaction(ctx.mongo, ctx.tenant, ctx.user!, { item_id: input.commentID, }), + createFlag: (input: GQLCreateCommentFlagInput) => + createFlag(ctx.mongo, ctx.tenant, ctx.user!, { + item_id: input.commentID, + reason: input.reason, + }), + deleteFlag: (input: GQLDeleteCommentFlagInput) => + deleteFlag(ctx.mongo, ctx.tenant, ctx.user!, { + item_id: input.commentID, + }), }); diff --git a/src/core/server/graph/tenant/resolvers/mutation.ts b/src/core/server/graph/tenant/resolvers/mutation.ts index 25746017f..9074ccfc8 100644 --- a/src/core/server/graph/tenant/resolvers/mutation.ts +++ b/src/core/server/graph/tenant/resolvers/mutation.ts @@ -31,6 +31,14 @@ const Mutation: GQLMutationTypeResolver = { comment: await ctx.mutators.Comment.deleteReaction(input), clientMutationId: input.clientMutationId, }), + createCommentFlag: async (source, { input }, ctx) => ({ + comment: await ctx.mutators.Comment.createFlag(input), + clientMutationId: input.clientMutationId, + }), + deleteCommentFlag: async (source, { input }, ctx) => ({ + comment: await ctx.mutators.Comment.deleteFlag(input), + clientMutationId: input.clientMutationId, + }), }; export default Mutation; diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 6a4b05051..7ca1ec4dc 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -1530,6 +1530,67 @@ type DeleteCommentReactionPayload { clientMutationId: String! } +################## +## createCommentFlag +################## + +input CreateCommentFlagInput { + """ + commentID is the Comment's ID that we want to create a Flag on. + """ + commentID: ID! + + """ + reason is the selected reason why the Flag is being created. + """ + reason: COMMENT_FLAG_REPORTED_REASON! + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + +type CreateCommentFlagPayload { + """ + comment is the Comment that the Flag was created on. + """ + comment: Comment + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + +################## +## deleteCommentFlag +################## + +input DeleteCommentFlagInput { + """ + commentID is the Comment's ID that we want to delete a Flag on. + """ + commentID: ID! + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + +type DeleteCommentFlagPayload { + """ + comment is the Comment that the Flag was deleted on. + """ + comment: Comment + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + ################## ## Mutation ################## @@ -1554,7 +1615,7 @@ type Mutation { """ createCommentReaction will create a Reaction authored by the current logged in - user on a Comment. + User on a Comment. """ createCommentReaction( input: CreateCommentReactionInput! @@ -1562,11 +1623,25 @@ type Mutation { """ deleteCommentReaction will delete a Reaction authored by the current logged in - user on a Comment if it exists. + User on a Comment if it exists. """ deleteCommentReaction( input: CreateCommentReactionInput! ): CreateCommentReactionPayload @auth + + """ + createCommentFlag will create a Flag authored by the current logged in User on + a given Comment. + """ + createCommentFlag(input: CreateCommentFlagInput!): CreateCommentFlagPayload + @auth + + """ + deleteCommentFlag will create a Flag authored by the current logged in User on + a given Comment. + """ + deleteCommentFlag(input: DeleteCommentFlagInput!): DeleteCommentFlagPayload + @auth } ################################################################################ diff --git a/src/core/server/models/action.ts b/src/core/server/models/action.ts index c5b8c36d9..bcc1c4fd3 100644 --- a/src/core/server/models/action.ts +++ b/src/core/server/models/action.ts @@ -7,7 +7,9 @@ import { Omit, Sub } from "talk-common/types"; import { GQLActionCounts, GQLActionPresence, + GQLCOMMENT_FLAG_DETECTED_REASON, GQLCOMMENT_FLAG_REASON, + GQLCOMMENT_FLAG_REPORTED_REASON, } from "talk-server/graph/tenant/schema/__generated__/types"; import { FilterQuery } from "talk-server/models/query"; import { TenantResource } from "talk-server/models/tenant"; @@ -45,12 +47,20 @@ export enum ACTION_ITEM_TYPE { COMMENTS = "COMMENTS", } +/** + * FLAG_REASON is the reason that a given Flag has been created. + */ +export type FLAG_REASON = + | GQLCOMMENT_FLAG_DETECTED_REASON + | GQLCOMMENT_FLAG_REPORTED_REASON + | GQLCOMMENT_FLAG_REASON; + export interface Action extends TenantResource { readonly id: string; action_type: ACTION_TYPE; item_type: ACTION_ITEM_TYPE; item_id: string; - reason?: GQLCOMMENT_FLAG_REASON; + reason?: FLAG_REASON; user_id?: string; created_at: Date; metadata?: Record; @@ -274,10 +284,15 @@ export async function deleteAction( action_type: input.action_type, item_type: input.item_type, item_id: input.item_id, - reason: input.reason, user_id: input.user_id, }; + // Only add the reason to the filter if it's been specified, otherwise we'll + // never match a Flag that has an unspecified reason. + if (input.reason) { + filter.reason = input.reason; + } + // Remove the action from the database, returning the action that was deleted. const result = await collection(mongo).findOneAndDelete(filter); return { diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index dde8826af..947ac4ae9 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -1,6 +1,7 @@ import { Db } from "mongodb"; import { Omit } from "talk-common/types"; +import { GQLCOMMENT_FLAG_REPORTED_REASON } from "talk-server/graph/tenant/schema/__generated__/types"; import { ACTION_ITEM_TYPE, ACTION_TYPE, @@ -319,3 +320,60 @@ export async function deleteReaction( return comment; } + +export type CreateCommentFlag = Pick & { + reason: GQLCOMMENT_FLAG_REPORTED_REASON; +}; + +export async function createFlag( + mongo: Db, + tenant: Tenant, + author: User, + input: CreateCommentFlag +) { + // Get the Comment that we are leaving the Action on. + let comment = await retrieveComment(mongo, tenant.id, input.item_id); + if (!comment) { + // TODO: replace to match error returned by the models/comments.ts + throw new Error("comment not found"); + } + + // Add the comment actions, and return the Comment that we just updated. + comment = await addCommentActions(mongo, tenant, comment, [ + { + action_type: ACTION_TYPE.FLAG, + reason: input.reason, + item_type: ACTION_ITEM_TYPE.COMMENTS, + item_id: input.item_id, + user_id: author.id, + }, + ]); + + return comment; +} + +export type DeleteCommentFlag = Pick; + +export async function deleteFlag( + mongo: Db, + tenant: Tenant, + author: User, + input: DeleteCommentFlag +) { + // Get the Comment that we are leaving the Action on. + let comment = await retrieveComment(mongo, tenant.id, input.item_id); + if (!comment) { + // TODO: replace to match error returned by the models/comments.ts + throw new Error("comment not found"); + } + + // Add the comment actions, and return the Comment that we just updated. + comment = await deleteCommentAction(mongo, tenant, comment, { + action_type: ACTION_TYPE.FLAG, + item_type: ACTION_ITEM_TYPE.COMMENTS, + item_id: input.item_id, + user_id: author.id, + }); + + return comment; +} From 3c640f87d32bfb2c5768642dbc2df53c8d5eb5c8 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 24 Sep 2018 14:07:44 -0600 Subject: [PATCH 22/53] fix: cleanup of comments service --- .../server/graph/tenant/mutators/comment.ts | 5 +- src/core/server/services/comments/actions.ts | 197 ++++++++++++++++ src/core/server/services/comments/index.ts | 220 +----------------- 3 files changed, 202 insertions(+), 220 deletions(-) create mode 100644 src/core/server/services/comments/actions.ts diff --git a/src/core/server/graph/tenant/mutators/comment.ts b/src/core/server/graph/tenant/mutators/comment.ts index 734e724b5..0867ee5e3 100644 --- a/src/core/server/graph/tenant/mutators/comment.ts +++ b/src/core/server/graph/tenant/mutators/comment.ts @@ -7,14 +7,13 @@ import { GQLDeleteCommentReactionInput, GQLEditCommentInput, } from "talk-server/graph/tenant/schema/__generated__/types"; +import { create, edit } from "talk-server/services/comments"; import { - create, createFlag, createReaction, deleteFlag, deleteReaction, - edit, -} from "talk-server/services/comments"; +} from "talk-server/services/comments/actions"; export default (ctx: TenantContext) => ({ create: (input: GQLCreateCommentInput) => diff --git a/src/core/server/services/comments/actions.ts b/src/core/server/services/comments/actions.ts new file mode 100644 index 000000000..f8313c913 --- /dev/null +++ b/src/core/server/services/comments/actions.ts @@ -0,0 +1,197 @@ +import { Db } from "mongodb"; + +import { GQLCOMMENT_FLAG_REPORTED_REASON } from "talk-server/graph/tenant/schema/__generated__/types"; +import { + ACTION_ITEM_TYPE, + ACTION_TYPE, + CreateActionInput, + createActions, + deleteAction, + DeleteActionInput, + encodeActionCounts, + invertEncodedActionCounts, +} from "talk-server/models/action"; +import { updateAssetActionCounts } from "talk-server/models/asset"; +import { + retrieveComment, + updateCommentActionCounts, +} from "talk-server/models/comment"; +import { Comment } from "talk-server/models/comment"; +import { Tenant } from "talk-server/models/tenant"; +import { User } from "talk-server/models/user"; + +export async function addCommentActions( + mongo: Db, + tenant: Tenant, + comment: Readonly, + inputs: CreateActionInput[] +): Promise> { + // Create each of the actions, returning each of the action results. + const results = await createActions(mongo, tenant.id, inputs); + + // Get the actions that were upserted, we only want to increment the action + // counts of actions that were just created. + const upsertedActions = results + .filter(({ wasUpserted }) => wasUpserted) + .map(({ action }) => action); + + if (upsertedActions.length > 0) { + // Compute the action counts. + const actionCounts = encodeActionCounts(...upsertedActions); + + // Update the comment action counts here. + const updatedComment = await updateCommentActionCounts( + mongo, + tenant.id, + comment.id, + actionCounts + ); + + // Update the Asset with the updated action counts. + await updateAssetActionCounts( + mongo, + tenant.id, + comment.asset_id, + actionCounts + ); + + // Check to see if there was an actual comment returned (there should + // have been, we just created it!). + if (!updatedComment) { + // TODO: (wyattjoh) return a better error. + throw new Error("could not update comment action counts"); + } + + return updatedComment; + } + + return comment; +} + +async function addCommentAction( + mongo: Db, + tenant: Tenant, + input: CreateActionInput +): Promise> { + const comment = await retrieveComment(mongo, tenant.id, input.item_id); + if (!comment) { + // TODO: replace to match error returned by the models/comments.ts + throw new Error("comment not found"); + } + + return addCommentActions(mongo, tenant, comment, [input]); +} + +export async function removeCommentAction( + mongo: Db, + tenant: Tenant, + input: DeleteActionInput +): Promise> { + // Get the Comment that we are leaving the Action on. + const comment = await retrieveComment(mongo, tenant.id, input.item_id); + if (!comment) { + // TODO: replace to match error returned by the models/comments.ts + throw new Error("comment not found"); + } + + // Create each of the actions, returning each of the action results. + const { wasDeleted, action } = await deleteAction(mongo, tenant.id, input); + if (wasDeleted) { + // Compute the action counts, and invert them (because we're deleting an + // action). + const actionCounts = invertEncodedActionCounts(encodeActionCounts(action!)); + + // Update the comment action counts here. + const updatedComment = await updateCommentActionCounts( + mongo, + tenant.id, + comment.id, + actionCounts + ); + + // Update the Asset with the updated action counts. + await updateAssetActionCounts( + mongo, + tenant.id, + comment.asset_id, + actionCounts + ); + + // Check to see if there was an actual comment returned. + if (!updatedComment) { + // TODO: (wyattjoh) return a better error. + throw new Error("could not update comment action counts"); + } + + return updatedComment; + } + + return comment; +} + +export type CreateCommentReaction = Pick; + +export async function createReaction( + mongo: Db, + tenant: Tenant, + author: User, + input: CreateCommentReaction +) { + return addCommentAction(mongo, tenant, { + action_type: ACTION_TYPE.REACTION, + item_type: ACTION_ITEM_TYPE.COMMENTS, + item_id: input.item_id, + user_id: author.id, + }); +} + +export type DeleteCommentReaction = Pick; + +export async function deleteReaction( + mongo: Db, + tenant: Tenant, + author: User, + input: DeleteCommentReaction +) { + return removeCommentAction(mongo, tenant, { + action_type: ACTION_TYPE.REACTION, + item_type: ACTION_ITEM_TYPE.COMMENTS, + item_id: input.item_id, + user_id: author.id, + }); +} + +export type CreateCommentFlag = Pick & { + reason: GQLCOMMENT_FLAG_REPORTED_REASON; +}; + +export async function createFlag( + mongo: Db, + tenant: Tenant, + author: User, + input: CreateCommentFlag +) { + return addCommentAction(mongo, tenant, { + action_type: ACTION_TYPE.FLAG, + reason: input.reason, + item_type: ACTION_ITEM_TYPE.COMMENTS, + item_id: input.item_id, + user_id: author.id, + }); +} + +export type DeleteCommentFlag = Pick; + +export async function deleteFlag( + mongo: Db, + tenant: Tenant, + author: User, + input: DeleteCommentFlag +) { + return removeCommentAction(mongo, tenant, { + action_type: ACTION_TYPE.FLAG, + item_type: ACTION_ITEM_TYPE.COMMENTS, + item_id: input.item_id, + user_id: author.id, + }); +} diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index 947ac4ae9..6388df472 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -1,32 +1,18 @@ import { Db } from "mongodb"; import { Omit } from "talk-common/types"; -import { GQLCOMMENT_FLAG_REPORTED_REASON } from "talk-server/graph/tenant/schema/__generated__/types"; +import { ACTION_ITEM_TYPE, CreateActionInput } from "talk-server/models/action"; +import { retrieveAsset } from "talk-server/models/asset"; import { - ACTION_ITEM_TYPE, - ACTION_TYPE, - CreateActionInput, - createActions, - deleteAction, - DeleteActionInput, - encodeActionCounts, - invertEncodedActionCounts, -} from "talk-server/models/action"; -import { - retrieveAsset, - updateAssetActionCounts, -} from "talk-server/models/asset"; -import { - Comment, createComment, CreateCommentInput, editComment, EditCommentInput, retrieveComment, - updateCommentActionCounts, } from "talk-server/models/comment"; import { Tenant } from "talk-server/models/tenant"; import { User } from "talk-server/models/user"; +import { addCommentActions } from "talk-server/services/comments/actions"; import { processForModeration } from "talk-server/services/comments/moderation"; import { Request } from "talk-server/types/express"; @@ -177,203 +163,3 @@ export async function edit( return comment; } - -async function addCommentActions( - mongo: Db, - tenant: Tenant, - comment: Readonly, - inputs: CreateActionInput[] -): Promise> { - // Create each of the actions, returning each of the action results. - const results = await createActions(mongo, tenant.id, inputs); - - // Get the actions that were upserted, we only want to increment the action - // counts of actions that were just created. - const upsertedActions = results - .filter(({ wasUpserted }) => wasUpserted) - .map(({ action }) => action); - - if (upsertedActions.length > 0) { - // Compute the action counts. - const actionCounts = encodeActionCounts(...upsertedActions); - - // Update the comment action counts here. - const updatedComment = await updateCommentActionCounts( - mongo, - tenant.id, - comment.id, - actionCounts - ); - - // Update the Asset with the updated action counts. - await updateAssetActionCounts( - mongo, - tenant.id, - comment.asset_id, - actionCounts - ); - - // Check to see if there was an actual comment returned (there should - // have been, we just created it!). - if (!updatedComment) { - // TODO: (wyattjoh) return a better error. - throw new Error("could not update comment action counts"); - } - - return updatedComment; - } - - return comment; -} - -async function deleteCommentAction( - mongo: Db, - tenant: Tenant, - comment: Readonly, - input: DeleteActionInput -): Promise> { - // Create each of the actions, returning each of the action results. - const { wasDeleted, action } = await deleteAction(mongo, tenant.id, input); - if (wasDeleted) { - // Compute the action counts, and invert them (because we're deleting an - // action). - const actionCounts = invertEncodedActionCounts(encodeActionCounts(action!)); - - // Update the comment action counts here. - const updatedComment = await updateCommentActionCounts( - mongo, - tenant.id, - comment.id, - actionCounts - ); - - // Update the Asset with the updated action counts. - await updateAssetActionCounts( - mongo, - tenant.id, - comment.asset_id, - actionCounts - ); - - // Check to see if there was an actual comment returned. - if (!updatedComment) { - // TODO: (wyattjoh) return a better error. - throw new Error("could not update comment action counts"); - } - - return updatedComment; - } - - return comment; -} - -export type CreateCommentReaction = Pick; - -export async function createReaction( - mongo: Db, - tenant: Tenant, - author: User, - input: CreateCommentReaction -) { - // Get the Comment that we are leaving the Action on. - let comment = await retrieveComment(mongo, tenant.id, input.item_id); - if (!comment) { - // TODO: replace to match error returned by the models/comments.ts - throw new Error("comment not found"); - } - - // Add the comment actions, and return the Comment that we just updated. - comment = await addCommentActions(mongo, tenant, comment, [ - { - action_type: ACTION_TYPE.REACTION, - item_type: ACTION_ITEM_TYPE.COMMENTS, - item_id: input.item_id, - user_id: author.id, - }, - ]); - - return comment; -} - -export type DeleteCommentReaction = Pick; - -export async function deleteReaction( - mongo: Db, - tenant: Tenant, - author: User, - input: DeleteCommentReaction -) { - // Get the Comment that we are leaving the Action on. - let comment = await retrieveComment(mongo, tenant.id, input.item_id); - if (!comment) { - // TODO: replace to match error returned by the models/comments.ts - throw new Error("comment not found"); - } - - // Add the comment actions, and return the Comment that we just updated. - comment = await deleteCommentAction(mongo, tenant, comment, { - action_type: ACTION_TYPE.REACTION, - item_type: ACTION_ITEM_TYPE.COMMENTS, - item_id: input.item_id, - user_id: author.id, - }); - - return comment; -} - -export type CreateCommentFlag = Pick & { - reason: GQLCOMMENT_FLAG_REPORTED_REASON; -}; - -export async function createFlag( - mongo: Db, - tenant: Tenant, - author: User, - input: CreateCommentFlag -) { - // Get the Comment that we are leaving the Action on. - let comment = await retrieveComment(mongo, tenant.id, input.item_id); - if (!comment) { - // TODO: replace to match error returned by the models/comments.ts - throw new Error("comment not found"); - } - - // Add the comment actions, and return the Comment that we just updated. - comment = await addCommentActions(mongo, tenant, comment, [ - { - action_type: ACTION_TYPE.FLAG, - reason: input.reason, - item_type: ACTION_ITEM_TYPE.COMMENTS, - item_id: input.item_id, - user_id: author.id, - }, - ]); - - return comment; -} - -export type DeleteCommentFlag = Pick; - -export async function deleteFlag( - mongo: Db, - tenant: Tenant, - author: User, - input: DeleteCommentFlag -) { - // Get the Comment that we are leaving the Action on. - let comment = await retrieveComment(mongo, tenant.id, input.item_id); - if (!comment) { - // TODO: replace to match error returned by the models/comments.ts - throw new Error("comment not found"); - } - - // Add the comment actions, and return the Comment that we just updated. - comment = await deleteCommentAction(mongo, tenant, comment, { - action_type: ACTION_TYPE.FLAG, - item_type: ACTION_ITEM_TYPE.COMMENTS, - item_id: input.item_id, - user_id: author.id, - }); - - return comment; -} From 7cce84b7f82611c3a63abcfd712c73bdc9bfe737 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 24 Sep 2018 14:25:48 -0600 Subject: [PATCH 23/53] feat: added dontAgree create/delete mutations --- .../server/graph/tenant/mutators/comment.ts | 12 ++++ .../server/graph/tenant/resolvers/mutation.ts | 8 +++ .../server/graph/tenant/schema/schema.graphql | 72 +++++++++++++++++++ src/core/server/services/comments/actions.ts | 32 +++++++++ 4 files changed, 124 insertions(+) diff --git a/src/core/server/graph/tenant/mutators/comment.ts b/src/core/server/graph/tenant/mutators/comment.ts index 0867ee5e3..01c734185 100644 --- a/src/core/server/graph/tenant/mutators/comment.ts +++ b/src/core/server/graph/tenant/mutators/comment.ts @@ -1,16 +1,20 @@ import TenantContext from "talk-server/graph/tenant/context"; import { + GQLCreateCommentDontAgreeInput, GQLCreateCommentFlagInput, GQLCreateCommentInput, GQLCreateCommentReactionInput, + GQLDeleteCommentDontAgreeInput, GQLDeleteCommentFlagInput, GQLDeleteCommentReactionInput, GQLEditCommentInput, } from "talk-server/graph/tenant/schema/__generated__/types"; import { create, edit } from "talk-server/services/comments"; import { + createDontAgree, createFlag, createReaction, + deleteDontAgree, deleteFlag, deleteReaction, } from "talk-server/services/comments/actions"; @@ -48,6 +52,14 @@ export default (ctx: TenantContext) => ({ deleteReaction(ctx.mongo, ctx.tenant, ctx.user!, { item_id: input.commentID, }), + createDontAgree: (input: GQLCreateCommentDontAgreeInput) => + createDontAgree(ctx.mongo, ctx.tenant, ctx.user!, { + item_id: input.commentID, + }), + deleteDontAgree: (input: GQLDeleteCommentDontAgreeInput) => + deleteDontAgree(ctx.mongo, ctx.tenant, ctx.user!, { + item_id: input.commentID, + }), createFlag: (input: GQLCreateCommentFlagInput) => createFlag(ctx.mongo, ctx.tenant, ctx.user!, { item_id: input.commentID, diff --git a/src/core/server/graph/tenant/resolvers/mutation.ts b/src/core/server/graph/tenant/resolvers/mutation.ts index 9074ccfc8..77f0ca109 100644 --- a/src/core/server/graph/tenant/resolvers/mutation.ts +++ b/src/core/server/graph/tenant/resolvers/mutation.ts @@ -31,6 +31,14 @@ const Mutation: GQLMutationTypeResolver = { comment: await ctx.mutators.Comment.deleteReaction(input), clientMutationId: input.clientMutationId, }), + createCommentDontAgree: async (source, { input }, ctx) => ({ + comment: await ctx.mutators.Comment.createDontAgree(input), + clientMutationId: input.clientMutationId, + }), + deleteCommentDontAgree: async (source, { input }, ctx) => ({ + comment: await ctx.mutators.Comment.deleteDontAgree(input), + clientMutationId: input.clientMutationId, + }), createCommentFlag: async (source, { input }, ctx) => ({ comment: await ctx.mutators.Comment.createFlag(input), clientMutationId: input.clientMutationId, diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 7ca1ec4dc..c96f32305 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -1530,6 +1530,62 @@ type DeleteCommentReactionPayload { clientMutationId: String! } +################## +## createCommentDontAgree +################## + +input CreateCommentDontAgreeInput { + """ + commentID is the Comment's ID that we want to create a DontAgree on. + """ + commentID: ID! + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + +type CreateCommentDontAgreePayload { + """ + comment is the Comment that the DontAgree was created on. + """ + comment: Comment + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + +################## +## deleteCommentDontAgree +################## + +input DeleteCommentDontAgreeInput { + """ + commentID is the Comment's ID that we want to delete a DontAgree on. + """ + commentID: ID! + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + +type DeleteCommentDontAgreePayload { + """ + comment is the Comment that the DontAgree was deleted on. + """ + comment: Comment + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + ################## ## createCommentFlag ################## @@ -1629,6 +1685,22 @@ type Mutation { input: CreateCommentReactionInput! ): CreateCommentReactionPayload @auth + """ + createCommentDontAgree will create a DontAgree authored by the current logged in + User on a Comment. + """ + createCommentDontAgree( + input: CreateCommentDontAgreeInput! + ): CreateCommentDontAgreePayload @auth + + """ + deleteCommentDontAgree will delete a DontAgree authored by the current logged in + User on a Comment if it exists. + """ + deleteCommentDontAgree( + input: CreateCommentDontAgreeInput! + ): CreateCommentDontAgreePayload @auth + """ createCommentFlag will create a Flag authored by the current logged in User on a given Comment. diff --git a/src/core/server/services/comments/actions.ts b/src/core/server/services/comments/actions.ts index f8313c913..e788757c9 100644 --- a/src/core/server/services/comments/actions.ts +++ b/src/core/server/services/comments/actions.ts @@ -161,6 +161,38 @@ export async function deleteReaction( }); } +export type CreateCommentDontAgree = Pick; + +export async function createDontAgree( + mongo: Db, + tenant: Tenant, + author: User, + input: CreateCommentDontAgree +) { + return addCommentAction(mongo, tenant, { + action_type: ACTION_TYPE.DONT_AGREE, + item_type: ACTION_ITEM_TYPE.COMMENTS, + item_id: input.item_id, + user_id: author.id, + }); +} + +export type DeleteCommentDontAgree = Pick; + +export async function deleteDontAgree( + mongo: Db, + tenant: Tenant, + author: User, + input: DeleteCommentDontAgree +) { + return removeCommentAction(mongo, tenant, { + action_type: ACTION_TYPE.DONT_AGREE, + item_type: ACTION_ITEM_TYPE.COMMENTS, + item_id: input.item_id, + user_id: author.id, + }); +} + export type CreateCommentFlag = Pick & { reason: GQLCOMMENT_FLAG_REPORTED_REASON; }; From a318b56233f80829d2d41ac804c8cd466bc14543 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 24 Sep 2018 15:38:12 -0600 Subject: [PATCH 24/53] fix: patch for sort bug --- src/core/server/models/comment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/server/models/comment.ts b/src/core/server/models/comment.ts index e2ff8da1d..39d8ccc5c 100644 --- a/src/core/server/models/comment.ts +++ b/src/core/server/models/comment.ts @@ -366,7 +366,7 @@ function applyInputToQuery(input: ConnectionInput, query: Query) { } break; case GQLCOMMENT_SORT.RESPECT_DESC: - query.orderBy({ "action_counts.respect": -1, created_at: -1 }); + query.orderBy({ "action_counts.REACTION": -1, created_at: -1 }); if (input.after) { query.after(input.after as number); } From b664b9abc9184105c0a4ca41dbadaa3452a5e88f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Tue, 25 Sep 2018 18:03:56 -0300 Subject: [PATCH 25/53] Adding Respect Button UI --- .../comments/components/RespectButton.tsx | 21 +++++++++++++++++++ .../comments/containers/CommentContainer.tsx | 2 ++ .../containers/RespectButtonContainer.tsx | 9 ++++++++ src/locales/en-US/stream.ftl | 2 ++ 4 files changed, 34 insertions(+) create mode 100644 src/core/client/stream/tabs/comments/components/RespectButton.tsx create mode 100644 src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx diff --git a/src/core/client/stream/tabs/comments/components/RespectButton.tsx b/src/core/client/stream/tabs/comments/components/RespectButton.tsx new file mode 100644 index 000000000..0bf7bc6fa --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/RespectButton.tsx @@ -0,0 +1,21 @@ +import { Localized } from "fluent-react/compat"; +import React from "react"; + +import { Button, ButtonIcon, MatchMedia } from "talk-ui/components"; + +class RespectButton extends React.Component { + public render() { + return ( + + ); + } +} + +export default RespectButton; diff --git a/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx b/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx index 24e09fd23..9c5e45020 100644 --- a/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx @@ -19,6 +19,7 @@ import ReplyButton from "../components/Comment/ReplyButton"; import EditCommentFormContainer from "./EditCommentFormContainer"; import PermalinkButtonContainer from "./PermalinkButtonContainer"; import ReplyCommentFormContainer from "./ReplyCommentFormContainer"; +import RespectButtonContainer from "./RespectButtonContainer"; interface InnerProps { me: MeData | null; @@ -150,6 +151,7 @@ export class CommentContainer extends Component { active={showReplyDialog} /> + } /> diff --git a/src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx b/src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx new file mode 100644 index 000000000..1c34ae75c --- /dev/null +++ b/src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx @@ -0,0 +1,9 @@ +import React, { StatelessComponent } from "react"; + +import RespectButton from "../components/RespectButton"; + +export const RespectButtonContainer: StatelessComponent = () => { + return ; +}; + +export default RespectButtonContainer; diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index a62d6723a..0a8ecced6 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -68,3 +68,5 @@ comments-editCommentForm-rte = comments-editCommentForm-editRemainingTime = Edit: remaining comments-editCommentForm-editTimeExpired = Edit time has expired. You can no longer edit this comment. Why not post another one? comments-editedMarker-edited = Edited + +comments-respectButton-respect = Respect From c2023672d35187cc2e74662503205b44a2545a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Tue, 25 Sep 2018 18:16:31 -0300 Subject: [PATCH 26/53] Adding creating CreateCommentReactionMutation --- .../CreateCommentReactionMutation.ts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/core/client/stream/mutations/CreateCommentReactionMutation.ts diff --git a/src/core/client/stream/mutations/CreateCommentReactionMutation.ts b/src/core/client/stream/mutations/CreateCommentReactionMutation.ts new file mode 100644 index 000000000..a331e34e0 --- /dev/null +++ b/src/core/client/stream/mutations/CreateCommentReactionMutation.ts @@ -0,0 +1,54 @@ +import { graphql } from "react-relay"; +import { Environment } from "relay-runtime"; + +import { + commitMutationPromiseNormalized, + createMutationContainer, +} from "talk-framework/lib/relay"; +import { Omit } from "talk-framework/types"; + +import { CreateCommentReactionMutation as MutationTypes } from "talk-stream/__generated__/CreateCommentReactionMutation.graphql"; + +export type CreateCommentReactionInput = Omit< + MutationTypes["variables"]["input"], + "clientMutationId" +>; + +const mutation = graphql` + mutation CreateCommentReactionMutation($input: CreateCommentReactionInput!) { + createCommentReaction(input: $input) { + comment { + id + actionCounts { + reaction { + total + } + } + } + clientMutationId + } + } +`; + +const clientMutationId = 0; + +function commit(environment: Environment, input: CreateCommentReactionInput) { + return commitMutationPromiseNormalized(environment, { + mutation, + variables: { + input: { + ...input, + clientMutationId: clientMutationId.toString(), + }, + }, + }); +} + +export const withEditCommentMutation = createMutationContainer( + "createCommentReaction", + commit +); + +export type CreateCommentReactionMutation = ( + input: CreateCommentReactionInput +) => Promise; From f7f24a37dde6cc9f0382d24a272447d3bf1580af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Tue, 25 Sep 2018 18:35:41 -0300 Subject: [PATCH 27/53] Performing respect --- .../CreateCommentReactionMutation.ts | 2 +- src/core/client/stream/mutations/index.ts | 5 ++++ .../comments/components/RespectButton.tsx | 8 ++++-- .../comments/containers/CommentContainer.tsx | 2 +- .../containers/RespectButtonContainer.tsx | 27 +++++++++++++++---- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/core/client/stream/mutations/CreateCommentReactionMutation.ts b/src/core/client/stream/mutations/CreateCommentReactionMutation.ts index a331e34e0..5da3b33ca 100644 --- a/src/core/client/stream/mutations/CreateCommentReactionMutation.ts +++ b/src/core/client/stream/mutations/CreateCommentReactionMutation.ts @@ -44,7 +44,7 @@ function commit(environment: Environment, input: CreateCommentReactionInput) { }); } -export const withEditCommentMutation = createMutationContainer( +export const withCreateCommentReactionMutation = createMutationContainer( "createCommentReaction", commit ); diff --git a/src/core/client/stream/mutations/index.ts b/src/core/client/stream/mutations/index.ts index 5b39e4e78..853d2d824 100644 --- a/src/core/client/stream/mutations/index.ts +++ b/src/core/client/stream/mutations/index.ts @@ -30,3 +30,8 @@ export { withSetActiveTabMutation, SetActiveTabMutation, } from "./SetActiveTabMutation"; +export { + withCreateCommentReactionMutation, + CreateCommentReactionMutation, + CreateCommentReactionInput, +} from "./CreateCommentReactionMutation"; diff --git a/src/core/client/stream/tabs/comments/components/RespectButton.tsx b/src/core/client/stream/tabs/comments/components/RespectButton.tsx index 0bf7bc6fa..932813738 100644 --- a/src/core/client/stream/tabs/comments/components/RespectButton.tsx +++ b/src/core/client/stream/tabs/comments/components/RespectButton.tsx @@ -3,10 +3,14 @@ import React from "react"; import { Button, ButtonIcon, MatchMedia } from "talk-ui/components"; -class RespectButton extends React.Component { +interface RespectButtonProps { + onButtonClick: () => {}; +} + +class RespectButton extends React.Component { public render() { return ( - + ) : ( ) : ( diff --git a/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx b/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx index 1e0bb697d..ef13254b4 100644 --- a/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx @@ -166,7 +166,7 @@ export class CommentContainer extends Component { /> )} - + {this.props.me && } } /> diff --git a/src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx b/src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx index 6f816a335..bbccf22d4 100644 --- a/src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx @@ -25,21 +25,34 @@ class RespectButtonContainer extends React.Component< commentID: this.props.comment.id, }; - // const { createCommentReaction, deleteCommentReaction } = this.props; - // const { myActionPresence } = this.props.comment; + const { createCommentReaction, deleteCommentReaction } = this.props; + const reacted = + this.props.comment.myActionPresence && + this.props.comment.myActionPresence.reaction; - // return myActionPresence - // ? deleteCommentReaction(input) - // : createCommentReaction(input); - return this.props.createCommentReaction(input); + return reacted + ? deleteCommentReaction(input) + : createCommentReaction(input); }; public render() { const { actionCounts: { - reaction: { total }, + reaction: { total: totalReactions }, }, } = this.props.comment; - return ; + + const reacted = + this.props.comment.myActionPresence && + this.props.comment.myActionPresence.reaction; + + console.log(this.props.comment); + return ( + + ); } } @@ -49,14 +62,14 @@ export default withDeleteCommentReactionMutation( comment: graphql` fragment RespectButtonContainer_comment on Comment { id + myActionPresence { + reaction + } actionCounts { reaction { total } } - myActionPresence { - reaction - } } `, })(RespectButtonContainer) From f5c8b455f166a3d9c53ce0b89fa0220cd646e019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Wed, 26 Sep 2018 15:18:12 -0300 Subject: [PATCH 32/53] Adding respect count --- .../stream/tabs/comments/components/RespectButton.tsx | 7 +++++-- .../tabs/comments/containers/RespectButtonContainer.tsx | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/client/stream/tabs/comments/components/RespectButton.tsx b/src/core/client/stream/tabs/comments/components/RespectButton.tsx index 27d5c4a2d..4102359c2 100644 --- a/src/core/client/stream/tabs/comments/components/RespectButton.tsx +++ b/src/core/client/stream/tabs/comments/components/RespectButton.tsx @@ -11,18 +11,21 @@ interface RespectButtonProps { class RespectButton extends React.Component { public render() { - return this.props.reacted ? ( + const { totalReactions, reacted } = this.props; + return reacted ? ( ) : ( - - - -
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
- -
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; - -exports[`edit a comment: edit form 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
- - Markus - - -
-
-
- -
-
-
- - - -
-
-
-
-
-
- - - Edit: - - remaining - -
-
- - -
-
- -
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; - -exports[`edit a comment: optimistic response 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
- - Markus - - -
-
-
- -
-
-
- - - -
-
-
-
-
-
- - - Edit: - - remaining - -
-
- - -
-
- -
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; - exports[`edit a comment: render stream 1`] = `
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
- -
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; +
+ Cannot return null for non-nullable field Comment.actionCounts. -exports[`edit a comment: server response 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
- ( - - Edited - - ) -
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; +GraphQL request (218:3) +217: } +218: actionCounts { + ^ +219: reaction { -exports[`shows expiry message: edit form closed 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
- -
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; - -exports[`shows expiry message: edit time expired 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
- - Markus - - -
-
-
- -
-
-
- - - -
-
-
-
-
-
- - Edit time has expired. You can no longer edit this comment. Why not post another one? -
-
- -
-
- -
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap index 7a6a13b76..c2150f405 100644 --- a/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap @@ -364,304 +364,15 @@ exports[`renders comment stream 1`] = `
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
- -
+
+ Cannot return null for non-nullable field Comment.actionCounts. + +GraphQL request (218:3) +217: } +218: actionCounts { + ^ +219: reaction { +
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap index 7de038658..dd7a2b2fb 100644 --- a/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap @@ -4,91 +4,15 @@ exports[`renders permalink view 1`] = `
-
- - Show All Comments - -
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
+
+ Cannot return null for non-nullable field Comment.actionCounts. + +GraphQL request (72:3) +71: } +72: actionCounts { + ^ +73: reaction { +
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap index f52d50282..73b4d7de6 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap @@ -1,1138 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`post a comment: optimistic response 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
-
Hello world!", - } - } - disabled={true} - id="comments-postCommentForm-field" - onBlur={[Function]} - onChange={[Function]} - onCut={[Function]} - onFocus={[Function]} - onInput={[Function]} - onKeyDown={[Function]} - onPaste={[Function]} - onSelect={[Function]} - /> -
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
- -
-
-
Hello world!", - } - } - /> -
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; - -exports[`post a comment: server response 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
Hello world! (from server)", - } - } - /> -
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; - exports[`renders comment stream 1`] = `
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
+
+ Cannot return null for non-nullable field Comment.actionCounts. + +GraphQL request (218:3) +217: } +218: actionCounts { + ^ +219: reaction { +
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap index 35948074c..73b4d7de6 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap @@ -1,2939 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`post a reply: open reply form 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-`; - -exports[`post a reply: optimistic response 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- - - -
-
Hello world!", - } - } - disabled={true} - id="comments-replyCommentForm-rte-comment-with-deepest-replies-5" - onBlur={[Function]} - onChange={[Function]} - onCut={[Function]} - onFocus={[Function]} - onInput={[Function]} - onKeyDown={[Function]} - onPaste={[Function]} - onSelect={[Function]} - /> -
-
-
-
- - -
-
- -
-
-
-
-
-
-
- - Markus - -
- -
-
-
- -
-
-
Hello world!", - } - } - /> -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`; - -exports[`post a reply: server response 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
Hello world! (from server)", - } - } - /> -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`; - exports[`renders comment stream 1`] = `
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ Cannot return null for non-nullable field Comment.actionCounts. + +GraphQL request (218:3) +217: } +218: actionCounts { + ^ +219: reaction { +
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap index c1913d1ba..73b4d7de6 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap @@ -1,1733 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`post a reply: open reply form 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; - -exports[`post a reply: optimistic response 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- - - -
-
Hello world!", - } - } - disabled={true} - id="comments-replyCommentForm-rte-comment-0" - onBlur={[Function]} - onChange={[Function]} - onCut={[Function]} - onFocus={[Function]} - onInput={[Function]} - onKeyDown={[Function]} - onPaste={[Function]} - onSelect={[Function]} - /> -
-
-
-
- - -
-
- -
-
-
-
-
-
-
- - Markus - -
- -
-
-
- -
-
-
Hello world!", - } - } - /> -
- -
-
-
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; - -exports[`post a reply: server response 1`] = ` -
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
Hello world! (from server)", - } - } - /> -
- -
-
-
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-`; - exports[`renders comment stream 1`] = `
-
-
-
-
-
- Signed in as - - Markus - - . -
-
- - Not you?  - - -
-
-
-
-
-
- -
-
-
- - - -
- -
-
-
-
-
-
- - Powered by - - ⁨The Coral Project⁩ - - -
- -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
+
+ Cannot return null for non-nullable field Comment.actionCounts. + +GraphQL request (218:3) +217: } +218: actionCounts { + ^ +219: reaction { +
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap index 2266711cd..73b4d7de6 100644 --- a/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap @@ -4,580 +4,15 @@ exports[`renders comment stream 1`] = `
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Isabelle - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Isabelle - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
- - Isabelle - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
+
+ Cannot return null for non-nullable field Comment.actionCounts. + +GraphQL request (218:3) +217: } +218: actionCounts { + ^ +219: reaction { +
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap index bfe500ef4..73b4d7de6 100644 --- a/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap @@ -4,288 +4,15 @@ exports[`renders comment stream 1`] = `
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
+
+ Cannot return null for non-nullable field Comment.actionCounts. + +GraphQL request (218:3) +217: } +218: actionCounts { + ^ +219: reaction { +
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap index 27fc4c77a..574177ee9 100644 --- a/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap @@ -4,318 +4,15 @@ exports[`renders comment stream 1`] = `
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- - Markus - -
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - Lukas - -
- -
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
+
+ Cannot return null for non-nullable field Comment.actionCounts. + +GraphQL request (218:3) +217: } +218: actionCounts { + ^ +219: reaction { +
`; From fd01fec70b1fcef92778384c02731e6411d881ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Wed, 26 Sep 2018 17:03:01 -0300 Subject: [PATCH 35/53] Adding actionCounts to the fixtures --- .../__snapshots__/editComment.spec.tsx.snap | 2804 ++++++++++++++++- .../__snapshots__/loadMore.spec.tsx.snap | 307 +- .../__snapshots__/permalinkView.spec.tsx.snap | 94 +- .../__snapshots__/postComment.spec.tsx.snap | 364 ++- .../postLocalReply.spec.tsx.snap | 1590 +++++++++- .../__snapshots__/postReply.spec.tsx.snap | 850 ++++- .../__snapshots__/renderReplies.spec.tsx.snap | 583 +++- .../__snapshots__/renderStream.spec.tsx.snap | 291 +- .../showAllReplies.spec.tsx.snap | 321 +- src/core/client/stream/test/fixtures.ts | 40 + 10 files changed, 7163 insertions(+), 81 deletions(-) diff --git a/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap index 933909ab1..3d4a0fb0d 100644 --- a/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap @@ -1,18 +1,2804 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`cancel edit: edit canceled 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; + +exports[`edit a comment: edit form 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+ + Markus + + +
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+ + + Edit: + + remaining + +
+
+ + +
+
+ +
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; + +exports[`edit a comment: optimistic response 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+ + Markus + + +
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+ + + Edit: + + remaining + +
+
+ + +
+
+ +
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; + exports[`edit a comment: render stream 1`] = `
-
- Cannot return null for non-nullable field Comment.actionCounts. - -GraphQL request (218:3) -217: } -218: actionCounts { - ^ -219: reaction { - +
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; + +exports[`edit a comment: server response 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+ ( + + Edited + + ) +
+
+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; + +exports[`shows expiry message: edit form closed 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; + +exports[`shows expiry message: edit time expired 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+ + Markus + + +
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+ + Edit time has expired. You can no longer edit this comment. Why not post another one? +
+
+ +
+
+ +
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap index c2150f405..7a6a13b76 100644 --- a/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap @@ -364,15 +364,304 @@ exports[`renders comment stream 1`] = `
-
- Cannot return null for non-nullable field Comment.actionCounts. - -GraphQL request (218:3) -217: } -218: actionCounts { - ^ -219: reaction { - +
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap index dd7a2b2fb..7de038658 100644 --- a/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap @@ -4,15 +4,91 @@ exports[`renders permalink view 1`] = `
-
- Cannot return null for non-nullable field Comment.actionCounts. - -GraphQL request (72:3) -71: } -72: actionCounts { - ^ -73: reaction { - +
+ + Show All Comments + +
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ +
+
+
+
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap index 73b4d7de6..2d48a2e8c 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap @@ -1,18 +1,364 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`post a comment: optimistic response 1`] = `null`; + +exports[`post a comment: server response 1`] = `null`; + exports[`renders comment stream 1`] = `
-
- Cannot return null for non-nullable field Comment.actionCounts. - -GraphQL request (218:3) -217: } -218: actionCounts { - ^ -219: reaction { - +
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap index 73b4d7de6..1f134a82f 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap @@ -1,18 +1,1590 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`post a reply: open reply form 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`post a reply: optimistic response 1`] = `null`; + +exports[`post a reply: server response 1`] = `null`; + exports[`renders comment stream 1`] = `
-
- Cannot return null for non-nullable field Comment.actionCounts. - -GraphQL request (218:3) -217: } -218: actionCounts { - ^ -219: reaction { - +
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap index 73b4d7de6..f0cd9ccbc 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap @@ -1,18 +1,850 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`post a reply: open reply form 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; + +exports[`post a reply: optimistic response 1`] = `null`; + +exports[`post a reply: server response 1`] = `null`; + exports[`renders comment stream 1`] = `
-
- Cannot return null for non-nullable field Comment.actionCounts. - -GraphQL request (218:3) -217: } -218: actionCounts { - ^ -219: reaction { - +
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap index 73b4d7de6..2266711cd 100644 --- a/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap @@ -4,15 +4,580 @@ exports[`renders comment stream 1`] = `
-
- Cannot return null for non-nullable field Comment.actionCounts. - -GraphQL request (218:3) -217: } -218: actionCounts { - ^ -219: reaction { - +
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + Isabelle + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + Isabelle + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + Isabelle + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap index 73b4d7de6..bfe500ef4 100644 --- a/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap @@ -4,15 +4,288 @@ exports[`renders comment stream 1`] = `
-
- Cannot return null for non-nullable field Comment.actionCounts. - -GraphQL request (218:3) -217: } -218: actionCounts { - ^ -219: reaction { - +
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ +
+
+
+
+
+
`; diff --git a/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap index 574177ee9..27fc4c77a 100644 --- a/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap @@ -4,15 +4,318 @@ exports[`renders comment stream 1`] = `
-
- Cannot return null for non-nullable field Comment.actionCounts. - -GraphQL request (218:3) -217: } -218: actionCounts { - ^ -219: reaction { - +
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
`; diff --git a/src/core/client/stream/test/fixtures.ts b/src/core/client/stream/test/fixtures.ts index a0e6e6a31..d77b4dde3 100644 --- a/src/core/client/stream/test/fixtures.ts +++ b/src/core/client/stream/test/fixtures.ts @@ -24,6 +24,11 @@ export const comments = [ edited: false, editableUntil: "2018-07-06T18:24:30.000Z", }, + actionCounts: { + reaction: { + total: 0, + }, + }, }, { id: "comment-1", @@ -35,6 +40,11 @@ export const comments = [ edited: false, editableUntil: "2018-07-06T18:20:30.000Z", }, + actionCounts: { + reaction: { + total: 0, + }, + }, }, { id: "comment-2", @@ -46,6 +56,11 @@ export const comments = [ edited: false, editableUntil: "2018-07-06T18:14:30.000Z", }, + actionCounts: { + reaction: { + total: 0, + }, + }, }, { id: "comment-3", @@ -57,6 +72,11 @@ export const comments = [ edited: false, editableUntil: "2018-07-06T18:14:30.000Z", }, + actionCounts: { + reaction: { + total: 0, + }, + }, }, { id: "comment-4", @@ -68,6 +88,11 @@ export const comments = [ edited: false, editableUntil: "2018-07-06T18:14:30.000Z", }, + actionCounts: { + reaction: { + total: 0, + }, + }, }, { id: "comment-5", @@ -79,6 +104,11 @@ export const comments = [ edited: false, editableUntil: "2018-07-06T18:14:30.000Z", }, + actionCounts: { + reaction: { + total: 0, + }, + }, }, ]; @@ -117,6 +147,11 @@ export const commentWithReplies = { edited: false, editableUntil: "2018-07-06T18:24:30.000Z", }, + actionCounts: { + reaction: { + total: 0, + }, + }, }; export const commentWithDeepReplies = { @@ -137,6 +172,11 @@ export const commentWithDeepReplies = { edited: false, editableUntil: "2018-07-06T18:24:30.000Z", }, + actionCounts: { + reaction: { + total: 0, + }, + }, }; export const assetWithReplies = { From 66c3a48668a6f611cc3e0685d0458c964c660e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Wed, 26 Sep 2018 17:07:38 -0300 Subject: [PATCH 36/53] It was the optimistic response --- src/core/client/stream/mutations/CreateCommentMutation.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/client/stream/mutations/CreateCommentMutation.ts b/src/core/client/stream/mutations/CreateCommentMutation.ts index 71ea3d894..913606e8e 100644 --- a/src/core/client/stream/mutations/CreateCommentMutation.ts +++ b/src/core/client/stream/mutations/CreateCommentMutation.ts @@ -147,6 +147,11 @@ function commit( editing: { editableUntil: new Date(Date.now() + 10000), }, + actionCounts: { + reaction: { + total: 0, + }, + }, }, }, clientMutationId: (clientMutationId++).toString(), From 210146e998937571c5045e03fc1d8f248e6acf44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Wed, 26 Sep 2018 17:13:28 -0300 Subject: [PATCH 37/53] More snapshots --- .../__snapshots__/postComment.spec.tsx.snap | 807 +++++++- .../postLocalReply.spec.tsx.snap | 1791 ++++++++++++++++- .../__snapshots__/postReply.spec.tsx.snap | 1067 +++++++++- 3 files changed, 3659 insertions(+), 6 deletions(-) diff --git a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap index 2d48a2e8c..11d3fb467 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap @@ -1,8 +1,811 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`post a comment: optimistic response 1`] = `null`; +exports[`post a comment: optimistic response 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+
Hello world!", + } + } + disabled={true} + id="comments-postCommentForm-field" + onBlur={[Function]} + onChange={[Function]} + onCut={[Function]} + onFocus={[Function]} + onInput={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onSelect={[Function]} + /> +
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+ +
+
+
Hello world!", + } + } + /> +
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; -exports[`post a comment: server response 1`] = `null`; +exports[`post a comment: server response 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+
Hello world!", + } + } + disabled={false} + id="comments-postCommentForm-field" + onBlur={[Function]} + onChange={[Function]} + onCut={[Function]} + onFocus={[Function]} + onInput={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onSelect={[Function]} + /> +
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; exports[`renders comment stream 1`] = `
`; -exports[`post a reply: optimistic response 1`] = `null`; +exports[`post a reply: optimistic response 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+
+ + + +
+
Hello world!", + } + } + disabled={true} + id="comments-replyCommentForm-rte-comment-with-deepest-replies-5" + onBlur={[Function]} + onChange={[Function]} + onCut={[Function]} + onFocus={[Function]} + onInput={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onSelect={[Function]} + /> +
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+ +
+
+
Hello world!", + } + } + /> +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; -exports[`post a reply: server response 1`] = `null`; +exports[`post a reply: server response 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+
+ + + +
+
Hello world!", + } + } + disabled={false} + id="comments-replyCommentForm-rte-comment-with-deepest-replies-5" + onBlur={[Function]} + onChange={[Function]} + onCut={[Function]} + onFocus={[Function]} + onInput={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onSelect={[Function]} + /> +
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; exports[`renders comment stream 1`] = `
`; -exports[`post a reply: optimistic response 1`] = `null`; +exports[`post a reply: optimistic response 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+
+ + + +
+
Hello world!", + } + } + disabled={true} + id="comments-replyCommentForm-rte-comment-0" + onBlur={[Function]} + onChange={[Function]} + onCut={[Function]} + onFocus={[Function]} + onInput={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onSelect={[Function]} + /> +
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+ +
+
+
Hello world!", + } + } + /> +
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; -exports[`post a reply: server response 1`] = `null`; +exports[`post a reply: server response 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+
+ + + +
+
Hello world!", + } + } + disabled={false} + id="comments-replyCommentForm-rte-comment-0" + onBlur={[Function]} + onChange={[Function]} + onCut={[Function]} + onFocus={[Function]} + onInput={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onSelect={[Function]} + /> +
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; exports[`renders comment stream 1`] = `
Date: Wed, 26 Sep 2018 17:23:54 -0300 Subject: [PATCH 38/53] Adding spected --- .../__snapshots__/postComment.spec.tsx.snap | 97 ++++++++++- .../postLocalReply.spec.tsx.snap | 158 ++++++----------- .../__snapshots__/postReply.spec.tsx.snap | 162 +++++++----------- .../stream/test/comments/postComment.spec.tsx | 5 + .../test/comments/postLocalReply.spec.tsx | 5 + .../stream/test/comments/postReply.spec.tsx | 5 + 6 files changed, 231 insertions(+), 201 deletions(-) diff --git a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap index 11d3fb467..fa033c7d5 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap @@ -569,13 +569,19 @@ exports[`post a comment: server response 1`] = `
+
Hello world!", + "__html": "", } } disabled={false} @@ -610,8 +616,8 @@ exports[`post a comment: server response 1`] = `
+ +
+
+
+
+
diff --git a/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap index 7749e33f2..618f6b7ea 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap @@ -2476,7 +2476,7 @@ exports[`post a reply: server response 1`] = ` className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow" >
-
-
-
diff --git a/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap index 4803b779e..8dd6ae3f6 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap @@ -1307,7 +1307,7 @@ exports[`post a reply: server response 1`] = ` className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow" >
-
-
-
{ editableUntil: "2018-07-06T18:24:30.000Z", }, replies: { edges: [], pageInfo: {} }, + actionCounts: { + reaction: { + total: 0, + }, + }, }, }, clientMutationId: "0", diff --git a/src/core/client/stream/test/comments/postLocalReply.spec.tsx b/src/core/client/stream/test/comments/postLocalReply.spec.tsx index f0e5c8949..c063118ae 100644 --- a/src/core/client/stream/test/comments/postLocalReply.spec.tsx +++ b/src/core/client/stream/test/comments/postLocalReply.spec.tsx @@ -46,6 +46,11 @@ beforeEach(() => { edited: false, editableUntil: "2018-07-06T18:24:30.000Z", }, + actionCounts: { + reaction: { + total: 0, + }, + }, }, }, clientMutationId: "0", diff --git a/src/core/client/stream/test/comments/postReply.spec.tsx b/src/core/client/stream/test/comments/postReply.spec.tsx index 6f75b57a8..19c105fc5 100644 --- a/src/core/client/stream/test/comments/postReply.spec.tsx +++ b/src/core/client/stream/test/comments/postReply.spec.tsx @@ -43,6 +43,11 @@ beforeEach(() => { edited: false, editableUntil: "2018-07-06T18:24:30.000Z", }, + actionCounts: { + reaction: { + total: 0, + }, + }, }, }, clientMutationId: "0", From 5acd19790a558d0162c9ad02a9815ceb0a1d24f7 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 26 Sep 2018 16:06:35 -0600 Subject: [PATCH 39/53] feat: replaced respect with reaction and added some options! --- .../comments/components/PermalinkView.tsx | 11 ++++- .../comments/components/ReactionButton.tsx | 43 +++++++++++++++++++ .../tabs/comments/components/ReplyList.tsx | 2 + .../comments/components/RespectButton.tsx | 39 ----------------- .../tabs/comments/components/Stream.tsx | 4 ++ .../comments/containers/CommentContainer.tsx | 19 ++++++-- .../containers/LocalReplyListContainer.tsx | 8 ++++ .../containers/PermalinkViewContainer.tsx | 10 ++++- ...tainer.tsx => ReactionButtonContainer.tsx} | 39 ++++++++++++----- .../containers/ReplyListContainer.tsx | 34 +++++++++++++++ .../comments/containers/StreamContainer.tsx | 9 ++++ .../comments/queries/PermalinkViewQuery.tsx | 4 ++ .../tabs/comments/queries/StreamQuery.tsx | 11 ++++- .../server/graph/tenant/schema/schema.graphql | 22 ++++++++-- src/core/server/models/tenant.ts | 3 +- src/locales/en-US/stream.ftl | 3 -- 16 files changed, 199 insertions(+), 62 deletions(-) create mode 100644 src/core/client/stream/tabs/comments/components/ReactionButton.tsx delete mode 100644 src/core/client/stream/tabs/comments/components/RespectButton.tsx rename src/core/client/stream/tabs/comments/containers/{RespectButtonContainer.tsx => ReactionButtonContainer.tsx} (57%) diff --git a/src/core/client/stream/tabs/comments/components/PermalinkView.tsx b/src/core/client/stream/tabs/comments/components/PermalinkView.tsx index ceb58f0d1..475483bb1 100644 --- a/src/core/client/stream/tabs/comments/components/PermalinkView.tsx +++ b/src/core/client/stream/tabs/comments/components/PermalinkView.tsx @@ -10,6 +10,7 @@ import * as styles from "./PermalinkView.css"; export interface PermalinkViewProps { me: PropTypesOf["me"]; asset: PropTypesOf["asset"]; + settings: PropTypesOf["settings"]; comment: PropTypesOf["comment"] | null; showAllCommentsHref: string | null; onShowAllComments: (e: MouseEvent) => void; @@ -18,6 +19,7 @@ export interface PermalinkViewProps { const PermalinkView: StatelessComponent = ({ showAllCommentsHref, comment, + settings, asset, onShowAllComments, me, @@ -46,7 +48,14 @@ const PermalinkView: StatelessComponent = ({ Comment not found )} - {comment && } + {comment && ( + + )}
); }; diff --git a/src/core/client/stream/tabs/comments/components/ReactionButton.tsx b/src/core/client/stream/tabs/comments/components/ReactionButton.tsx new file mode 100644 index 000000000..ec8df5749 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/ReactionButton.tsx @@ -0,0 +1,43 @@ +import React from "react"; + +import { Button, ButtonIcon, MatchMedia } from "talk-ui/components"; + +interface ReactionButtonProps { + onButtonClick: () => {}; + totalReactions: number; + reacted: boolean | null; + label: string; + labelActive: string | null; + icon: string; + iconActive: string | null; + // color: string; +} + +class ReactionButton extends React.Component { + public render() { + const { totalReactions, reacted } = this.props; + return ( + + ); + } +} + +export default ReactionButton; diff --git a/src/core/client/stream/tabs/comments/components/ReplyList.tsx b/src/core/client/stream/tabs/comments/components/ReplyList.tsx index 896120b91..504ac09d0 100644 --- a/src/core/client/stream/tabs/comments/components/ReplyList.tsx +++ b/src/core/client/stream/tabs/comments/components/ReplyList.tsx @@ -17,6 +17,7 @@ export interface ReplyListProps { comments: ReadonlyArray< { id: string } & PropTypesOf["comment"] >; + settings: PropTypesOf["settings"]; onShowAll?: () => void; hasMore?: boolean; disableShowAll?: boolean; @@ -49,6 +50,7 @@ const ReplyList: StatelessComponent = props => { me={props.me} comment={comment} asset={props.asset} + settings={props.settings} indentLevel={props.indentLevel} localReply={props.localReply} disableReplies={props.disableReplies} diff --git a/src/core/client/stream/tabs/comments/components/RespectButton.tsx b/src/core/client/stream/tabs/comments/components/RespectButton.tsx deleted file mode 100644 index 4102359c2..000000000 --- a/src/core/client/stream/tabs/comments/components/RespectButton.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Localized } from "fluent-react/compat"; -import React from "react"; - -import { Button, ButtonIcon, MatchMedia } from "talk-ui/components"; - -interface RespectButtonProps { - onButtonClick: () => {}; - totalReactions: number; - reacted: boolean | null; -} - -class RespectButton extends React.Component { - public render() { - const { totalReactions, reacted } = this.props; - return reacted ? ( - - ) : ( - - ); - } -} - -export default RespectButton; diff --git a/src/core/client/stream/tabs/comments/components/Stream.tsx b/src/core/client/stream/tabs/comments/components/Stream.tsx index 52c08e645..fcc9c474e 100644 --- a/src/core/client/stream/tabs/comments/components/Stream.tsx +++ b/src/core/client/stream/tabs/comments/components/Stream.tsx @@ -18,6 +18,8 @@ export interface StreamProps { isClosed?: boolean; } & PropTypesOf["asset"] & PropTypesOf["asset"]; + settings: PropTypesOf["settings"] & + PropTypesOf["settings"]; comments: ReadonlyArray< { id: string } & PropTypesOf["comment"] & PropTypesOf["comment"] @@ -52,10 +54,12 @@ const Stream: StatelessComponent = props => { { public render() { const { comment, + settings, asset, indentLevel, localReply, @@ -166,7 +169,12 @@ export class CommentContainer extends Component { /> )} - {this.props.me && } + {this.props.me && ( + + )} } /> @@ -211,7 +219,12 @@ const enhanced = withShowAuthPopupMutation( pending ...ReplyCommentFormContainer_comment ...EditCommentFormContainer_comment - ...RespectButtonContainer_comment + ...ReactionButtonContainer_comment + } + `, + settings: graphql` + fragment CommentContainer_settings on Settings { + ...ReactionButtonContainer_settings } `, })(CommentContainer) diff --git a/src/core/client/stream/tabs/comments/containers/LocalReplyListContainer.tsx b/src/core/client/stream/tabs/comments/containers/LocalReplyListContainer.tsx index 025160cf9..aa4711093 100644 --- a/src/core/client/stream/tabs/comments/containers/LocalReplyListContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/LocalReplyListContainer.tsx @@ -6,6 +6,7 @@ import { PropTypesOf } from "talk-framework/types"; import { LocalReplyListContainer_asset as AssetData } from "talk-stream/__generated__/LocalReplyListContainer_asset.graphql"; import { LocalReplyListContainer_comment as CommentData } from "talk-stream/__generated__/LocalReplyListContainer_comment.graphql"; import { LocalReplyListContainer_me as MeData } from "talk-stream/__generated__/LocalReplyListContainer_me.graphql"; +import { LocalReplyListContainer_settings as SettingsData } from "talk-stream/__generated__/LocalReplyListContainer_settings.graphql"; import ReplyList from "../components/ReplyList"; @@ -14,6 +15,7 @@ interface InnerProps { me: MeData; asset: AssetData; comment: CommentData; + settings: SettingsData; } /** @@ -30,6 +32,7 @@ export class LocalReplyListContainer extends Component { return ( ({ } } `, + settings: graphql` + fragment LocalReplyListContainer_settings on Settings { + ...CommentContainer_settings + } + `, })(LocalReplyListContainer); export type LocalReplyListContainerProps = PropTypesOf; diff --git a/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx b/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx index a21057c04..234b7d888 100644 --- a/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx @@ -8,6 +8,7 @@ import { buildURL, parseURL } from "talk-framework/utils"; import { PermalinkViewContainer_asset as AssetData } from "talk-stream/__generated__/PermalinkViewContainer_asset.graphql"; import { PermalinkViewContainer_comment as CommentData } from "talk-stream/__generated__/PermalinkViewContainer_comment.graphql"; import { PermalinkViewContainer_me as MeData } from "talk-stream/__generated__/PermalinkViewContainer_me.graphql"; +import { PermalinkViewContainer_settings as SettingsData } from "talk-stream/__generated__/PermalinkViewContainer_settings.graphql"; import { SetCommentIDMutation, withSetCommentIDMutation, @@ -18,6 +19,7 @@ import PermalinkView from "../components/PermalinkView"; interface PermalinkViewContainerProps { comment: CommentData | null; asset: AssetData; + settings: SettingsData; me: MeData | null; setCommentID: SetCommentIDMutation; pym: PymChild | undefined; @@ -53,12 +55,13 @@ class PermalinkViewContainer extends React.Component< } public render() { - const { comment, asset, me } = this.props; + const { comment, asset, me, settings } = this.props; return ( @@ -87,6 +90,11 @@ const enhanced = withContext(ctx => ({ ...CommentContainer_me } `, + settings: graphql` + fragment PermalinkViewContainer_settings on Settings { + ...CommentContainer_settings + } + `, })(PermalinkViewContainer) ) ); diff --git a/src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx b/src/core/client/stream/tabs/comments/containers/ReactionButtonContainer.tsx similarity index 57% rename from src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx rename to src/core/client/stream/tabs/comments/containers/ReactionButtonContainer.tsx index 2da162157..17fe8ccba 100644 --- a/src/core/client/stream/tabs/comments/containers/RespectButtonContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/ReactionButtonContainer.tsx @@ -1,24 +1,26 @@ import React from "react"; import { graphql } from "react-relay"; import { withFragmentContainer } from "talk-framework/lib/relay"; -import { RespectButtonContainer_comment as CommentData } from "talk-stream/__generated__/RespectButtonContainer_comment.graphql"; +import { ReactionButtonContainer_comment as CommentData } from "talk-stream/__generated__/ReactionButtonContainer_comment.graphql"; +import { ReactionButtonContainer_settings as SettingsData } from "talk-stream/__generated__/ReactionButtonContainer_settings.graphql"; import { CreateCommentReactionMutation, DeleteCommentReactionMutation, withCreateCommentReactionMutation, withDeleteCommentReactionMutation, -} from "../../../mutations"; -import RespectButton from "../components/RespectButton"; +} from "talk-stream/mutations"; +import ReactionButton from "talk-stream/tabs/comments/components/ReactionButton"; -interface RespectButtonContainerProps { +interface ReactionButtonContainerProps { createCommentReaction: CreateCommentReactionMutation; deleteCommentReaction: DeleteCommentReactionMutation; comment: CommentData; + settings: SettingsData; } -class RespectButtonContainer extends React.Component< - RespectButtonContainerProps +class ReactionButtonContainer extends React.Component< + ReactionButtonContainerProps > { private onButtonClick = () => { const input = { @@ -40,16 +42,23 @@ class RespectButtonContainer extends React.Component< reaction: { total: totalReactions }, }, } = this.props.comment; + const { + reaction: { label, labelActive, icon, iconActive }, + } = this.props.settings; const reacted = this.props.comment.myActionPresence && this.props.comment.myActionPresence.reaction; return ( - ); } @@ -57,9 +66,9 @@ class RespectButtonContainer extends React.Component< export default withDeleteCommentReactionMutation( withCreateCommentReactionMutation( - withFragmentContainer({ + withFragmentContainer({ comment: graphql` - fragment RespectButtonContainer_comment on Comment { + fragment ReactionButtonContainer_comment on Comment { id myActionPresence { reaction @@ -71,6 +80,16 @@ export default withDeleteCommentReactionMutation( } } `, - })(RespectButtonContainer) + settings: graphql` + fragment ReactionButtonContainer_settings on Settings { + reaction { + label + labelActive + icon + iconActive + } + } + `, + })(ReactionButtonContainer) ) ); diff --git a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx index 0dfe3a80f..130a355a8 100644 --- a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx @@ -7,6 +7,7 @@ import { PropTypesOf } from "talk-framework/types"; import { ReplyListContainer1_asset as AssetData } from "talk-stream/__generated__/ReplyListContainer1_asset.graphql"; import { ReplyListContainer1_comment as CommentData } from "talk-stream/__generated__/ReplyListContainer1_comment.graphql"; import { ReplyListContainer1_me as MeData } from "talk-stream/__generated__/ReplyListContainer1_me.graphql"; +import { ReplyListContainer1_settings as SettingsData } from "talk-stream/__generated__/ReplyListContainer1_settings.graphql"; import { COMMENT_SORT, ReplyListContainer1PaginationQueryVariables, @@ -20,6 +21,7 @@ export interface InnerProps { me: MeData | null; asset: AssetData; comment: CommentData; + settings: SettingsData; relay: RelayPaginationProp; indentLevel: number; ReplyListComponent: React.ComponentType | undefined; @@ -51,6 +53,7 @@ export class ReplyListContainer extends React.Component { comment={this.props.comment} comments={comments} asset={this.props.asset} + settings={this.props.settings} onShowAll={this.showAll} hasMore={this.props.relay.hasMore()} disableShowAll={this.state.disableShowAll} @@ -86,6 +89,7 @@ function createReplyListContainer( me: GraphQLTaggedNode; asset: GraphQLTaggedNode; comment: GraphQLTaggedNode; + settings: GraphQLTaggedNode; }, query: GraphQLTaggedNode, ReplyListComponent?: React.ComponentType, @@ -137,6 +141,12 @@ const ReplyListContainer5 = createReplyListContainer( ...LocalReplyListContainer_me } `, + settings: graphql` + fragment ReplyListContainer5_settings on Settings { + ...LocalReplyListContainer_settings + ...CommentContainer_settings + } + `, asset: graphql` fragment ReplyListContainer5_asset on Asset { ...CommentContainer_asset @@ -192,6 +202,12 @@ const ReplyListContainer4 = createReplyListContainer( ...CommentContainer_me } `, + settings: graphql` + fragment ReplyListContainer4_settings on Settings { + ...ReplyListContainer5_settings + ...CommentContainer_settings + } + `, asset: graphql` fragment ReplyListContainer4_asset on Asset { ...ReplyListContainer5_asset @@ -246,6 +262,12 @@ const ReplyListContainer3 = createReplyListContainer( ...CommentContainer_me } `, + settings: graphql` + fragment ReplyListContainer3_settings on Settings { + ...ReplyListContainer4_settings + ...CommentContainer_settings + } + `, asset: graphql` fragment ReplyListContainer3_asset on Asset { ...ReplyListContainer4_asset @@ -300,6 +322,12 @@ const ReplyListContainer2 = createReplyListContainer( ...CommentContainer_me } `, + settings: graphql` + fragment ReplyListContainer2_settings on Settings { + ...ReplyListContainer3_settings + ...CommentContainer_settings + } + `, asset: graphql` fragment ReplyListContainer2_asset on Asset { ...ReplyListContainer3_asset @@ -354,6 +382,12 @@ const ReplyListContainer1 = createReplyListContainer( ...CommentContainer_me } `, + settings: graphql` + fragment ReplyListContainer1_settings on Settings { + ...ReplyListContainer2_settings + ...CommentContainer_settings + } + `, asset: graphql` fragment ReplyListContainer1_asset on Asset { ...ReplyListContainer2_asset diff --git a/src/core/client/stream/tabs/comments/containers/StreamContainer.tsx b/src/core/client/stream/tabs/comments/containers/StreamContainer.tsx index 5ca2286bc..e9127c913 100644 --- a/src/core/client/stream/tabs/comments/containers/StreamContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/StreamContainer.tsx @@ -5,6 +5,7 @@ import { withPaginationContainer } from "talk-framework/lib/relay"; import { PropTypesOf } from "talk-framework/types"; import { StreamContainer_asset as AssetData } from "talk-stream/__generated__/StreamContainer_asset.graphql"; import { StreamContainer_me as MeData } from "talk-stream/__generated__/StreamContainer_me.graphql"; +import { StreamContainer_settings as SettingsData } from "talk-stream/__generated__/StreamContainer_settings.graphql"; import { COMMENT_SORT, StreamContainerPaginationQueryVariables, @@ -14,6 +15,7 @@ import Stream from "../components/Stream"; interface InnerProps { asset: AssetData; + settings: SettingsData; me: MeData | null; relay: RelayPaginationProp; } @@ -38,6 +40,7 @@ export class StreamContainer extends React.Component { @@ -63,6 +64,9 @@ const PermalinkViewQuery: StatelessComponent = ({ comment(id: $commentID) { ...PermalinkViewContainer_comment } + settings { + ...PermalinkViewContainer_settings + } } `} variables={{ diff --git a/src/core/client/stream/tabs/comments/queries/StreamQuery.tsx b/src/core/client/stream/tabs/comments/queries/StreamQuery.tsx index da7879f8e..4a7f2c2ed 100644 --- a/src/core/client/stream/tabs/comments/queries/StreamQuery.tsx +++ b/src/core/client/stream/tabs/comments/queries/StreamQuery.tsx @@ -31,7 +31,13 @@ export const render = ({ ); } - return ; + return ( + + ); } return ; @@ -49,6 +55,9 @@ const StreamQuery: StatelessComponent = ({ asset(id: $assetID, url: $assetURL) { ...StreamContainer_asset } + settings { + ...StreamContainer_settings + } } `} variables={{ diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 63b82301a..961a88702 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -498,15 +498,31 @@ ReactionConfiguration stores the configuration for reactions used across this Tenant. """ type ReactionConfiguration { + """ + icon is the string representing the icon to be used for the reactions. + """ + icon: String! + + """ + + """ + iconActive: String + """ label is the string placed beside the reaction icon to provide better context. """ label: String! """ - icon is the string representing the icon to be used for the reactions. + labelActive is the string placed beside the reaction icon to provide better + context when it has been selected. """ - icon: String! + labelActive: String + + """ + color is the hex color code that can be used to change the color of the button. + """ + color: String } ################################################################################ @@ -653,7 +669,7 @@ type Settings { """ reaction specifies the configuration for reactions. """ - reaction: ReactionConfiguration! @auth(roles: [ADMIN]) + reaction: ReactionConfiguration! } ################################################################################ diff --git a/src/core/server/models/tenant.ts b/src/core/server/models/tenant.ts index 0449de1c2..325a49d1b 100644 --- a/src/core/server/models/tenant.ts +++ b/src/core/server/models/tenant.ts @@ -121,7 +121,8 @@ export async function createTenant(mongo: Db, input: CreateTenantInput) { // By default, the standard reaction style will use the Respect with the // handshake. label: "Respect", - icon: "handshake", + labelActive: "Respected", + icon: "thumb_up", }, }; diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index 2cd155fc6..a62d6723a 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -68,6 +68,3 @@ comments-editCommentForm-rte = comments-editCommentForm-editRemainingTime = Edit: remaining comments-editCommentForm-editTimeExpired = Edit time has expired. You can no longer edit this comment. Why not post another one? comments-editedMarker-edited = Edited - -comments-respectButton-respect = Respect -comments-respectButton-respected = Respected From bc447dbde0031a7ea19b8f5079b8eaf158a9e026 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 26 Sep 2018 17:48:24 -0600 Subject: [PATCH 40/53] fix: test updates --- .../comments/components/ReplyList.spec.tsx | 12 ++ .../tabs/comments/components/Stream.spec.tsx | 18 +++ .../__snapshots__/ReplyList.spec.tsx.snap | 48 +++++++ .../__snapshots__/Stream.spec.tsx.snap | 128 ++++++++++++++++++ .../containers/CommentContainer.spec.tsx | 18 +++ .../containers/ReplyListContainer.spec.tsx | 18 +++ .../containers/StreamContainer.spec.tsx | 12 ++ .../ReplyListContainer.spec.tsx.snap | 32 +++++ .../StreamContainer.spec.tsx.snap | 32 +++++ .../stream/test/comments/editComment.spec.tsx | 6 +- .../stream/test/comments/loadMore.spec.tsx | 6 +- .../test/comments/permalinkView.spec.tsx | 6 +- .../permalinkViewAssetNotFound.spec.tsx | 6 + .../permalinkViewCommentNotFound.spec.tsx | 6 +- .../stream/test/comments/postComment.spec.tsx | 6 +- .../test/comments/postLocalReply.spec.tsx | 5 +- .../stream/test/comments/postReply.spec.tsx | 6 +- .../test/comments/renderReplies.spec.tsx | 6 +- .../test/comments/renderStream.spec.tsx | 6 +- .../test/comments/showAllReplies.spec.tsx | 6 +- src/core/client/stream/test/fixtures.ts | 7 + 21 files changed, 379 insertions(+), 11 deletions(-) diff --git a/src/core/client/stream/tabs/comments/components/ReplyList.spec.tsx b/src/core/client/stream/tabs/comments/components/ReplyList.spec.tsx index c8b3dcbb2..a444c95a3 100644 --- a/src/core/client/stream/tabs/comments/components/ReplyList.spec.tsx +++ b/src/core/client/stream/tabs/comments/components/ReplyList.spec.tsx @@ -22,6 +22,12 @@ it("renders correctly", () => { me: null, localReply: false, disableReplies: false, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, }; const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); @@ -37,6 +43,12 @@ describe("when there is more", () => { disableShowAll: false, indentLevel: 1, me: null, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, }; const wrapper = shallow(); diff --git a/src/core/client/stream/tabs/comments/components/Stream.spec.tsx b/src/core/client/stream/tabs/comments/components/Stream.spec.tsx index 6a717731c..b0043b2fe 100644 --- a/src/core/client/stream/tabs/comments/components/Stream.spec.tsx +++ b/src/core/client/stream/tabs/comments/components/Stream.spec.tsx @@ -17,6 +17,12 @@ it("renders correctly", () => { isClosed: false, }, comments: [{ id: "comment-1" }, { id: "comment-2" }], + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, onLoadMore: noop, disableLoadMore: false, hasMore: false, @@ -38,6 +44,12 @@ describe("when use is logged in", () => { disableLoadMore: false, hasMore: false, me: {}, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, }; const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); @@ -51,6 +63,12 @@ describe("when there is more", () => { isClosed: false, }, comments: [{ id: "comment-1" }, { id: "comment-2" }], + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, onLoadMore: sinon.spy(), disableLoadMore: false, hasMore: true, diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap index 0b4cbcd15..b334741dc 100644 --- a/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap @@ -24,6 +24,14 @@ exports[`renders correctly 1`] = ` key="comment-1" localReply={false} me={null} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> @@ -72,6 +88,14 @@ exports[`when there is more disables load more button 1`] = ` indentLevel={1} key="comment-1" me={null} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> @@ -120,6 +152,14 @@ exports[`when there is more disables load more button 1`] = ` } } me={null} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> diff --git a/src/core/client/stream/tabs/comments/containers/CommentContainer.spec.tsx b/src/core/client/stream/tabs/comments/containers/CommentContainer.spec.tsx index 049062795..0461e95d5 100644 --- a/src/core/client/stream/tabs/comments/containers/CommentContainer.spec.tsx +++ b/src/core/client/stream/tabs/comments/containers/CommentContainer.spec.tsx @@ -30,6 +30,12 @@ it("renders username and body", () => { }, pending: false, }, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, indentLevel: 1, showAuthPopup: noop as any, localReply: false, @@ -60,6 +66,12 @@ it("renders body only", () => { }, pending: false, }, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, indentLevel: 1, showAuthPopup: noop as any, }; @@ -88,6 +100,12 @@ it("hide reply button", () => { }, pending: false, }, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, indentLevel: 1, showAuthPopup: noop as any, localReply: false, diff --git a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx index dd9ac740a..6d440b197 100644 --- a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx +++ b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx @@ -22,6 +22,12 @@ it("renders correctly", () => { edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }], }, }, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, relay: { hasMore: noop, isLoading: noop, @@ -49,6 +55,12 @@ it("renders correctly when replies are empty", () => { isLoading: noop, } as any, me: null, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, indentLevel: 1, ReplyListComponent: undefined, localReply: false, @@ -69,6 +81,12 @@ describe("when has more replies", () => { edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }], }, }, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, relay: { hasMore: () => true, isLoading: () => false, diff --git a/src/core/client/stream/tabs/comments/containers/StreamContainer.spec.tsx b/src/core/client/stream/tabs/comments/containers/StreamContainer.spec.tsx index 3d3a51841..8ba69289f 100644 --- a/src/core/client/stream/tabs/comments/containers/StreamContainer.spec.tsx +++ b/src/core/client/stream/tabs/comments/containers/StreamContainer.spec.tsx @@ -21,6 +21,12 @@ it("renders correctly", () => { }, }, me: null, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, relay: { hasMore: noop, isLoading: noop, @@ -41,6 +47,12 @@ describe("when has more comments", () => { }, }, me: null, + settings: { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, + }, relay: { hasMore: () => true, isLoading: () => false, diff --git a/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap b/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap index fc88a3379..96ee0a0a4 100644 --- a/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap @@ -42,6 +42,14 @@ exports[`renders correctly 1`] = ` localReply={false} me={null} onShowAll={[Function]} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> `; @@ -89,6 +97,14 @@ exports[`when has more replies renders hasMore 1`] = ` localReply={false} me={null} onShowAll={[Function]} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> `; @@ -134,6 +150,14 @@ exports[`when has more replies when showing all disables show all button 1`] = ` localReply={false} me={null} onShowAll={[Function]} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> `; @@ -179,5 +203,13 @@ exports[`when has more replies when showing all enable show all button after loa localReply={false} me={null} onShowAll={[Function]} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> `; diff --git a/src/core/client/stream/tabs/comments/containers/__snapshots__/StreamContainer.spec.tsx.snap b/src/core/client/stream/tabs/comments/containers/__snapshots__/StreamContainer.spec.tsx.snap index d29f00fdf..990b9c2fe 100644 --- a/src/core/client/stream/tabs/comments/containers/__snapshots__/StreamContainer.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/containers/__snapshots__/StreamContainer.spec.tsx.snap @@ -35,6 +35,14 @@ exports[`renders correctly 1`] = ` disableLoadMore={false} me={null} onLoadMore={[Function]} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> `; @@ -74,6 +82,14 @@ exports[`when has more comments renders hasMore 1`] = ` hasMore={true} me={null} onLoadMore={[Function]} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> `; @@ -113,6 +129,14 @@ exports[`when has more comments when loading more disables load more button 1`] hasMore={true} me={null} onLoadMore={[Function]} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> `; @@ -152,5 +176,13 @@ exports[`when has more comments when loading more enable load more button after hasMore={true} me={null} onLoadMore={[Function]} + settings={ + Object { + "reaction": Object { + "icon": "thumb_up_alt", + "label": "Respect", + }, + } + } /> `; diff --git a/src/core/client/stream/test/comments/editComment.spec.tsx b/src/core/client/stream/test/comments/editComment.spec.tsx index 67684bc6f..f19d4f3f9 100644 --- a/src/core/client/stream/test/comments/editComment.spec.tsx +++ b/src/core/client/stream/test/comments/editComment.spec.tsx @@ -3,7 +3,7 @@ import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets, users } from "../fixtures"; +import { assets, settings, users } from "../fixtures"; import create from "./create"; function createTestRenderer() { @@ -20,6 +20,10 @@ function createTestRenderer() { s => s.throws(), s => s.withArgs(undefined).returns(users[0]) ), + settings: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined).returns(settings) + ), }, Mutation: { editComment: createSinonStub( diff --git a/src/core/client/stream/test/comments/loadMore.spec.tsx b/src/core/client/stream/test/comments/loadMore.spec.tsx index d51ea60fa..ee80d17fd 100644 --- a/src/core/client/stream/test/comments/loadMore.spec.tsx +++ b/src/core/client/stream/test/comments/loadMore.spec.tsx @@ -4,7 +4,7 @@ import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets, comments } from "../fixtures"; +import { assets, comments, settings } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -66,6 +66,10 @@ beforeEach(() => { ) .returns(assetStub) ), + settings: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined).returns(settings) + ), }, }; diff --git a/src/core/client/stream/test/comments/permalinkView.spec.tsx b/src/core/client/stream/test/comments/permalinkView.spec.tsx index 180abbe0b..61c211199 100644 --- a/src/core/client/stream/test/comments/permalinkView.spec.tsx +++ b/src/core/client/stream/test/comments/permalinkView.spec.tsx @@ -4,7 +4,7 @@ import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets, comments } from "../fixtures"; +import { assets, comments, settings } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -41,6 +41,10 @@ beforeEach(() => { .withArgs(undefined, { id: assetStub.id, url: null }) .returns(assetStub) ), + settings: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined).returns(settings) + ), }, }; diff --git a/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx b/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx index 9d2df883e..a5ff91b34 100644 --- a/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx +++ b/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx @@ -1,7 +1,9 @@ import { ReactTestRenderer } from "react-test-renderer"; import { timeout } from "talk-common/utils"; +import { createSinonStub } from "talk-framework/testHelpers"; +import { settings } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -10,6 +12,10 @@ beforeEach(() => { Query: { comment: () => null, asset: () => null, + settings: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined).returns(settings) + ), }, }; diff --git a/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx b/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx index 6ff259e93..abb24e1a2 100644 --- a/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx +++ b/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx @@ -4,7 +4,7 @@ import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets, comments } from "../fixtures"; +import { assets, comments, settings } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -38,6 +38,10 @@ beforeEach(() => { .withArgs(undefined, { id: assetStub.id, url: null }) .returns(assetStub) ), + settings: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined).returns(settings) + ), }, }; diff --git a/src/core/client/stream/test/comments/postComment.spec.tsx b/src/core/client/stream/test/comments/postComment.spec.tsx index 447604838..6da257f02 100644 --- a/src/core/client/stream/test/comments/postComment.spec.tsx +++ b/src/core/client/stream/test/comments/postComment.spec.tsx @@ -4,7 +4,7 @@ import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets, users } from "../fixtures"; +import { assets, settings, users } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -13,6 +13,10 @@ beforeEach(() => { Query: { asset: createSinonStub(s => s.throws(), s => s.returns(assets[0])), me: createSinonStub(s => s.throws(), s => s.returns(users[0])), + settings: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined).returns(settings) + ), }, Mutation: { createComment: createSinonStub( diff --git a/src/core/client/stream/test/comments/postLocalReply.spec.tsx b/src/core/client/stream/test/comments/postLocalReply.spec.tsx index c063118ae..be900f48b 100644 --- a/src/core/client/stream/test/comments/postLocalReply.spec.tsx +++ b/src/core/client/stream/test/comments/postLocalReply.spec.tsx @@ -4,7 +4,7 @@ import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assetWithDeepestReplies, users } from "../fixtures"; +import { assetWithDeepestReplies, settings, users } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -16,6 +16,7 @@ beforeEach(() => { s => s.returns(assetWithDeepestReplies) ), me: createSinonStub(s => s.throws(), s => s.returns(users[0])), + settings: createSinonStub(s => s.returns(settings)), }, Mutation: { createComment: createSinonStub( @@ -61,7 +62,7 @@ beforeEach(() => { ({ testRenderer } = create({ // Set this to true, to see graphql responses. - logNetwork: false, + logNetwork: true, resolvers, initLocalState: localRecord => { localRecord.setValue(assetWithDeepestReplies.id, "assetID"); diff --git a/src/core/client/stream/test/comments/postReply.spec.tsx b/src/core/client/stream/test/comments/postReply.spec.tsx index 19c105fc5..49da4a3fb 100644 --- a/src/core/client/stream/test/comments/postReply.spec.tsx +++ b/src/core/client/stream/test/comments/postReply.spec.tsx @@ -4,7 +4,7 @@ import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets, users } from "../fixtures"; +import { assets, settings, users } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -13,6 +13,10 @@ beforeEach(() => { Query: { asset: createSinonStub(s => s.throws(), s => s.returns(assets[0])), me: createSinonStub(s => s.throws(), s => s.returns(users[0])), + settings: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined).returns(settings) + ), }, Mutation: { createComment: createSinonStub( diff --git a/src/core/client/stream/test/comments/renderReplies.spec.tsx b/src/core/client/stream/test/comments/renderReplies.spec.tsx index 11ffcf000..d1c5301c9 100644 --- a/src/core/client/stream/test/comments/renderReplies.spec.tsx +++ b/src/core/client/stream/test/comments/renderReplies.spec.tsx @@ -3,7 +3,7 @@ import { ReactTestRenderer } from "react-test-renderer"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assetWithDeepReplies } from "../fixtures"; +import { assetWithDeepReplies, settings } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -17,6 +17,10 @@ beforeEach(() => { .withArgs(undefined, { id: assetWithDeepReplies.id, url: null }) .returns(assetWithDeepReplies) ), + settings: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined).returns(settings) + ), }, }; diff --git a/src/core/client/stream/test/comments/renderStream.spec.tsx b/src/core/client/stream/test/comments/renderStream.spec.tsx index 6a104ea6e..7fd042047 100644 --- a/src/core/client/stream/test/comments/renderStream.spec.tsx +++ b/src/core/client/stream/test/comments/renderStream.spec.tsx @@ -3,7 +3,7 @@ import { ReactTestRenderer } from "react-test-renderer"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets } from "../fixtures"; +import { assets, settings } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -17,6 +17,10 @@ beforeEach(() => { .withArgs(undefined, { id: assets[0].id, url: null }) .returns(assets[0]) ), + settings: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined).returns(settings) + ), }, }; diff --git a/src/core/client/stream/test/comments/showAllReplies.spec.tsx b/src/core/client/stream/test/comments/showAllReplies.spec.tsx index ac1c699e2..df08c0b55 100644 --- a/src/core/client/stream/test/comments/showAllReplies.spec.tsx +++ b/src/core/client/stream/test/comments/showAllReplies.spec.tsx @@ -4,7 +4,7 @@ import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets, comments } from "../fixtures"; +import { assets, comments, settings } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -76,6 +76,10 @@ beforeEach(() => { .withArgs(undefined, { id: assetStub.id, url: null }) .returns(assetStub) ), + settings: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined).returns(settings) + ), }, }; diff --git a/src/core/client/stream/test/fixtures.ts b/src/core/client/stream/test/fixtures.ts index d77b4dde3..6fd0f7338 100644 --- a/src/core/client/stream/test/fixtures.ts +++ b/src/core/client/stream/test/fixtures.ts @@ -1,3 +1,10 @@ +export const settings = { + reaction: { + icon: "thumb_up_alt", + label: "Respect", + }, +}; + export const users = [ { id: "user-0", From 9966d5a644212844b91274e0c0977e2d3879afb7 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 26 Sep 2018 17:49:01 -0600 Subject: [PATCH 41/53] fix: remove debug --- src/core/client/stream/test/comments/postLocalReply.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/client/stream/test/comments/postLocalReply.spec.tsx b/src/core/client/stream/test/comments/postLocalReply.spec.tsx index be900f48b..e279b4fd5 100644 --- a/src/core/client/stream/test/comments/postLocalReply.spec.tsx +++ b/src/core/client/stream/test/comments/postLocalReply.spec.tsx @@ -62,7 +62,7 @@ beforeEach(() => { ({ testRenderer } = create({ // Set this to true, to see graphql responses. - logNetwork: true, + logNetwork: false, resolvers, initLocalState: localRecord => { localRecord.setValue(assetWithDeepestReplies.id, "assetID"); From d49a30c50af6b26826d3e2b0a7545bfd8bbeddeb Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 27 Sep 2018 14:56:28 +0200 Subject: [PATCH 42/53] fix: pass settings to deeper ReplyListContainer --- .../tabs/comments/components/ReplyList.tsx | 17 ++----- .../containers/ReplyListContainer.tsx | 25 ++++++++-- .../ReplyListContainer.spec.tsx.snap | 49 ++++++++++++++++++- src/core/client/stream/test/fixtures.ts | 3 +- 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/src/core/client/stream/tabs/comments/components/ReplyList.tsx b/src/core/client/stream/tabs/comments/components/ReplyList.tsx index 504ac09d0..d0a2cb3a7 100644 --- a/src/core/client/stream/tabs/comments/components/ReplyList.tsx +++ b/src/core/client/stream/tabs/comments/components/ReplyList.tsx @@ -15,28 +15,19 @@ export interface ReplyListProps { id: string; }; comments: ReadonlyArray< - { id: string } & PropTypesOf["comment"] + { id: string; replyListElement?: React.ReactElement } & PropTypesOf< + typeof CommentContainer + >["comment"] >; settings: PropTypesOf["settings"]; onShowAll?: () => void; hasMore?: boolean; disableShowAll?: boolean; indentLevel?: number; - ReplyListComponent?: React.ComponentType; localReply?: boolean; disableReplies?: boolean; } -function getReplyListElement( - { ReplyListComponent, me, asset }: ReplyListProps, - comment: PropTypesOf["comment"] -) { - if (!ReplyListComponent) { - return null; - } - return ; -} - const ReplyList: StatelessComponent = props => { return ( = props => { localReply={props.localReply} disableReplies={props.disableReplies} /> - {getReplyListElement(props, comment)} + {comment.replyListElement} ))} {props.hasMore && ( diff --git a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx index 130a355a8..d646e2d1b 100644 --- a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx @@ -14,20 +14,26 @@ import { } from "talk-stream/__generated__/ReplyListContainer1PaginationQuery.graphql"; import { StatelessComponent } from "enzyme"; +import { FragmentKeys } from "talk-framework/lib/relay/types"; import ReplyList from "../components/ReplyList"; import LocalReplyListContainer from "./LocalReplyListContainer"; -export interface InnerProps { +export interface BaseProps { me: MeData | null; asset: AssetData; comment: CommentData; settings: SettingsData; relay: RelayPaginationProp; indentLevel: number; - ReplyListComponent: React.ComponentType | undefined; localReply: boolean | undefined; } +export type InnerProps = BaseProps & { + ReplyListComponent: + | React.ComponentType<{ [P in FragmentKeys]: any }> + | undefined; +}; + // TODO: (cvle) This should be autogenerated. interface FragmentVariables { count: number; @@ -46,7 +52,17 @@ export class ReplyListContainer extends React.Component { ) { return null; } - const comments = this.props.comment.replies.edges.map(edge => edge.node); + const comments = this.props.comment.replies.edges.map(edge => ({ + ...edge.node, + replyListElement: this.props.ReplyListComponent && ( + + ), + })); return ( { hasMore={this.props.relay.hasMore()} disableShowAll={this.state.disableShowAll} indentLevel={this.props.indentLevel} - ReplyListComponent={this.props.ReplyListComponent} localReply={this.props.localReply} /> ); @@ -92,7 +107,7 @@ function createReplyListContainer( settings: GraphQLTaggedNode; }, query: GraphQLTaggedNode, - ReplyListComponent?: React.ComponentType, + ReplyListComponent?: InnerProps["ReplyListComponent"], localReply?: boolean ) { return withProps({ indentLevel, ReplyListComponent, localReply })( diff --git a/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap b/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap index 96ee0a0a4..e9893372a 100644 --- a/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap @@ -2,7 +2,6 @@ exports[`renders correctly 1`] = ` , }, Object { "id": "comment-2", + "replyListElement": , }, ] } @@ -85,9 +126,11 @@ exports[`when has more replies renders hasMore 1`] = ` Array [ Object { "id": "comment-1", + "replyListElement": undefined, }, Object { "id": "comment-2", + "replyListElement": undefined, }, ] } @@ -138,9 +181,11 @@ exports[`when has more replies when showing all disables show all button 1`] = ` Array [ Object { "id": "comment-1", + "replyListElement": undefined, }, Object { "id": "comment-2", + "replyListElement": undefined, }, ] } @@ -191,9 +236,11 @@ exports[`when has more replies when showing all enable show all button after loa Array [ Object { "id": "comment-1", + "replyListElement": undefined, }, Object { "id": "comment-2", + "replyListElement": undefined, }, ] } diff --git a/src/core/client/stream/test/fixtures.ts b/src/core/client/stream/test/fixtures.ts index 6fd0f7338..3eee6a7eb 100644 --- a/src/core/client/stream/test/fixtures.ts +++ b/src/core/client/stream/test/fixtures.ts @@ -1,7 +1,8 @@ export const settings = { reaction: { - icon: "thumb_up_alt", + icon: "thumb_up", label: "Respect", + labelActive: "Respected", }, }; From 2e9d0a21fe896ec81bd899b63c6c4632af4c753b Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 27 Sep 2018 15:02:53 +0200 Subject: [PATCH 43/53] test: simplify tests --- .../client/stream/test/comments/editComment.spec.tsx | 11 +++-------- .../client/stream/test/comments/loadMore.spec.tsx | 5 +---- .../stream/test/comments/permalinkView.spec.tsx | 5 +---- .../comments/permalinkViewAssetNotFound.spec.tsx | 6 ++---- .../comments/permalinkViewCommentNotFound.spec.tsx | 5 +---- .../client/stream/test/comments/postComment.spec.tsx | 12 ++++++++---- .../stream/test/comments/postLocalReply.spec.tsx | 10 +++++++--- .../client/stream/test/comments/postReply.spec.tsx | 12 ++++++++---- .../stream/test/comments/renderReplies.spec.tsx | 6 ++---- .../stream/test/comments/renderStream.spec.tsx | 6 ++---- .../stream/test/comments/showAllReplies.spec.tsx | 5 +---- 11 files changed, 36 insertions(+), 47 deletions(-) diff --git a/src/core/client/stream/test/comments/editComment.spec.tsx b/src/core/client/stream/test/comments/editComment.spec.tsx index f19d4f3f9..c033133b1 100644 --- a/src/core/client/stream/test/comments/editComment.spec.tsx +++ b/src/core/client/stream/test/comments/editComment.spec.tsx @@ -1,3 +1,4 @@ +import sinon from "sinon"; import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; @@ -16,14 +17,8 @@ function createTestRenderer() { .withArgs(undefined, { id: assets[0].id, url: null }) .returns(assets[0]) ), - me: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined).returns(users[0]) - ), - settings: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined).returns(settings) - ), + me: sinon.stub().returns(users[0]), + settings: sinon.stub().returns(settings), }, Mutation: { editComment: createSinonStub( diff --git a/src/core/client/stream/test/comments/loadMore.spec.tsx b/src/core/client/stream/test/comments/loadMore.spec.tsx index ee80d17fd..bcbde1021 100644 --- a/src/core/client/stream/test/comments/loadMore.spec.tsx +++ b/src/core/client/stream/test/comments/loadMore.spec.tsx @@ -66,10 +66,7 @@ beforeEach(() => { ) .returns(assetStub) ), - settings: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined).returns(settings) - ), + settings: sinon.stub().returns(settings), }, }; diff --git a/src/core/client/stream/test/comments/permalinkView.spec.tsx b/src/core/client/stream/test/comments/permalinkView.spec.tsx index 61c211199..2e0ee1225 100644 --- a/src/core/client/stream/test/comments/permalinkView.spec.tsx +++ b/src/core/client/stream/test/comments/permalinkView.spec.tsx @@ -41,10 +41,7 @@ beforeEach(() => { .withArgs(undefined, { id: assetStub.id, url: null }) .returns(assetStub) ), - settings: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined).returns(settings) - ), + settings: sinon.stub().returns(settings), }, }; diff --git a/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx b/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx index a5ff91b34..6283ebd59 100644 --- a/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx +++ b/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx @@ -1,4 +1,5 @@ import { ReactTestRenderer } from "react-test-renderer"; +import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; @@ -12,10 +13,7 @@ beforeEach(() => { Query: { comment: () => null, asset: () => null, - settings: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined).returns(settings) - ), + settings: sinon.stub().returns(settings), }, }; diff --git a/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx b/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx index abb24e1a2..c2ffbd425 100644 --- a/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx +++ b/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx @@ -38,10 +38,7 @@ beforeEach(() => { .withArgs(undefined, { id: assetStub.id, url: null }) .returns(assetStub) ), - settings: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined).returns(settings) - ), + settings: sinon.stub().returns(settings), }, }; diff --git a/src/core/client/stream/test/comments/postComment.spec.tsx b/src/core/client/stream/test/comments/postComment.spec.tsx index 6da257f02..f4594c4dd 100644 --- a/src/core/client/stream/test/comments/postComment.spec.tsx +++ b/src/core/client/stream/test/comments/postComment.spec.tsx @@ -1,4 +1,5 @@ import { ReactTestRenderer } from "react-test-renderer"; +import sinon from "sinon"; import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; @@ -11,11 +12,14 @@ let testRenderer: ReactTestRenderer; beforeEach(() => { const resolvers = { Query: { - asset: createSinonStub(s => s.throws(), s => s.returns(assets[0])), - me: createSinonStub(s => s.throws(), s => s.returns(users[0])), - settings: createSinonStub( + settings: sinon.stub().returns(settings), + me: sinon.stub().returns(users[0]), + asset: createSinonStub( s => s.throws(), - s => s.withArgs(undefined).returns(settings) + s => + s + .withArgs(undefined, { id: assets[0].id, url: null }) + .returns(assets[0]) ), }, Mutation: { diff --git a/src/core/client/stream/test/comments/postLocalReply.spec.tsx b/src/core/client/stream/test/comments/postLocalReply.spec.tsx index e279b4fd5..ac69ce5ae 100644 --- a/src/core/client/stream/test/comments/postLocalReply.spec.tsx +++ b/src/core/client/stream/test/comments/postLocalReply.spec.tsx @@ -1,4 +1,5 @@ import { ReactTestRenderer } from "react-test-renderer"; +import sinon from "sinon"; import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; @@ -11,12 +12,15 @@ let testRenderer: ReactTestRenderer; beforeEach(() => { const resolvers = { Query: { + settings: sinon.stub().returns(settings), + me: sinon.stub().returns(users[0]), asset: createSinonStub( s => s.throws(), - s => s.returns(assetWithDeepestReplies) + s => + s + .withArgs(undefined, { id: assetWithDeepestReplies.id, url: null }) + .returns(assetWithDeepestReplies) ), - me: createSinonStub(s => s.throws(), s => s.returns(users[0])), - settings: createSinonStub(s => s.returns(settings)), }, Mutation: { createComment: createSinonStub( diff --git a/src/core/client/stream/test/comments/postReply.spec.tsx b/src/core/client/stream/test/comments/postReply.spec.tsx index 49da4a3fb..ad96fe03c 100644 --- a/src/core/client/stream/test/comments/postReply.spec.tsx +++ b/src/core/client/stream/test/comments/postReply.spec.tsx @@ -1,4 +1,5 @@ import { ReactTestRenderer } from "react-test-renderer"; +import sinon from "sinon"; import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; @@ -11,11 +12,14 @@ let testRenderer: ReactTestRenderer; beforeEach(() => { const resolvers = { Query: { - asset: createSinonStub(s => s.throws(), s => s.returns(assets[0])), - me: createSinonStub(s => s.throws(), s => s.returns(users[0])), - settings: createSinonStub( + settings: sinon.stub().returns(settings), + me: sinon.stub().returns(users[0]), + asset: createSinonStub( s => s.throws(), - s => s.withArgs(undefined).returns(settings) + s => + s + .withArgs(undefined, { id: assets[0].id, url: null }) + .returns(assets[0]) ), }, Mutation: { diff --git a/src/core/client/stream/test/comments/renderReplies.spec.tsx b/src/core/client/stream/test/comments/renderReplies.spec.tsx index d1c5301c9..abf029955 100644 --- a/src/core/client/stream/test/comments/renderReplies.spec.tsx +++ b/src/core/client/stream/test/comments/renderReplies.spec.tsx @@ -1,4 +1,5 @@ import { ReactTestRenderer } from "react-test-renderer"; +import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; @@ -17,10 +18,7 @@ beforeEach(() => { .withArgs(undefined, { id: assetWithDeepReplies.id, url: null }) .returns(assetWithDeepReplies) ), - settings: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined).returns(settings) - ), + settings: sinon.stub().returns(settings), }, }; diff --git a/src/core/client/stream/test/comments/renderStream.spec.tsx b/src/core/client/stream/test/comments/renderStream.spec.tsx index 7fd042047..301fc57dd 100644 --- a/src/core/client/stream/test/comments/renderStream.spec.tsx +++ b/src/core/client/stream/test/comments/renderStream.spec.tsx @@ -1,4 +1,5 @@ import { ReactTestRenderer } from "react-test-renderer"; +import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; @@ -17,10 +18,7 @@ beforeEach(() => { .withArgs(undefined, { id: assets[0].id, url: null }) .returns(assets[0]) ), - settings: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined).returns(settings) - ), + settings: sinon.stub().returns(settings), }, }; diff --git a/src/core/client/stream/test/comments/showAllReplies.spec.tsx b/src/core/client/stream/test/comments/showAllReplies.spec.tsx index df08c0b55..b5122028d 100644 --- a/src/core/client/stream/test/comments/showAllReplies.spec.tsx +++ b/src/core/client/stream/test/comments/showAllReplies.spec.tsx @@ -65,6 +65,7 @@ beforeEach(() => { const resolvers = { Query: { + settings: sinon.stub().returns(settings), comment: createSinonStub( s => s.throws(), s => s.withArgs(undefined, { id: commentStub.id }).returns(commentStub) @@ -76,10 +77,6 @@ beforeEach(() => { .withArgs(undefined, { id: assetStub.id, url: null }) .returns(assetStub) ), - settings: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined).returns(settings) - ), }, }; From 3ffdb09afe27bd3293d1e6fd58c74d2e8c53f254 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 27 Sep 2018 15:18:21 +0200 Subject: [PATCH 44/53] fix: lint --- .../stream/test/comments/permalinkViewAssetNotFound.spec.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx b/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx index 6283ebd59..84d161158 100644 --- a/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx +++ b/src/core/client/stream/test/comments/permalinkViewAssetNotFound.spec.tsx @@ -2,7 +2,6 @@ import { ReactTestRenderer } from "react-test-renderer"; import sinon from "sinon"; import { timeout } from "talk-common/utils"; -import { createSinonStub } from "talk-framework/testHelpers"; import { settings } from "../fixtures"; import create from "./create"; From ccb6f347ae87f3b85eba70d5fc74a5cb5bb6c203 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 27 Sep 2018 17:48:10 +0200 Subject: [PATCH 45/53] test: use baseComment and baseAsset in fixtures --- .../__snapshots__/editComment.spec.tsx.snap | 42 +-- .../__snapshots__/loadMore.spec.tsx.snap | 18 +- .../__snapshots__/postComment.spec.tsx.snap | 18 +- .../__snapshots__/postReply.spec.tsx.snap | 24 +- .../__snapshots__/renderReplies.spec.tsx.snap | 18 +- .../__snapshots__/renderStream.spec.tsx.snap | 6 +- .../showAllReplies.spec.tsx.snap | 18 +- .../stream/test/comments/postComment.spec.tsx | 16 +- .../test/comments/postLocalReply.spec.tsx | 24 +- .../stream/test/comments/postReply.spec.tsx | 19 +- src/core/client/stream/test/fixtures.ts | 263 +++++++----------- 11 files changed, 191 insertions(+), 275 deletions(-) diff --git a/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap index 3d4a0fb0d..ce0e2467f 100644 --- a/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap @@ -315,10 +315,10 @@ exports[`cancel edit: edit canceled 1`] = ` >
@@ -754,10 +754,10 @@ exports[`edit a comment: edit form 1`] = ` >
@@ -1193,10 +1193,10 @@ exports[`edit a comment: optimistic response 1`] = ` >
@@ -1568,10 +1568,10 @@ exports[`edit a comment: render stream 1`] = ` >
@@ -1952,10 +1952,10 @@ exports[`edit a comment: server response 1`] = ` >
@@ -2327,10 +2327,10 @@ exports[`shows expiry message: edit form closed 1`] = ` >
@@ -2743,10 +2743,10 @@ exports[`shows expiry message: edit time expired 1`] = ` >
diff --git a/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap index 7a6a13b76..704cfcf74 100644 --- a/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap @@ -245,10 +245,10 @@ exports[`loads more comments 1`] = ` >
@@ -315,10 +315,10 @@ exports[`loads more comments 1`] = ` >
@@ -605,10 +605,10 @@ exports[`renders comment stream 1`] = ` >
diff --git a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap index fa033c7d5..e4936901c 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap @@ -394,10 +394,10 @@ exports[`post a comment: optimistic response 1`] = ` >
@@ -838,10 +838,10 @@ exports[`post a comment: server response 1`] = ` >
@@ -1197,10 +1197,10 @@ exports[`renders comment stream 1`] = ` >
diff --git a/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap index 8dd6ae3f6..74de69a57 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap @@ -426,10 +426,10 @@ exports[`post a reply: open reply form 1`] = ` >
@@ -1013,10 +1013,10 @@ exports[`post a reply: optimistic response 1`] = ` >
@@ -1463,10 +1463,10 @@ exports[`post a reply: server response 1`] = ` >
@@ -1822,10 +1822,10 @@ exports[`renders comment stream 1`] = ` >
diff --git a/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap index 2266711cd..0c70f8956 100644 --- a/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap @@ -393,10 +393,10 @@ exports[`renders comment stream 1`] = ` >
@@ -463,10 +463,10 @@ exports[`renders comment stream 1`] = ` >
@@ -535,10 +535,10 @@ exports[`renders comment stream 1`] = ` >
diff --git a/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap index bfe500ef4..cce33a64d 100644 --- a/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap @@ -245,10 +245,10 @@ exports[`renders comment stream 1`] = ` >
diff --git a/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap index 27fc4c77a..5fe47021f 100644 --- a/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap @@ -249,10 +249,10 @@ exports[`renders comment stream 1`] = ` >
@@ -569,10 +569,10 @@ exports[`show all replies 1`] = ` >
@@ -639,10 +639,10 @@ exports[`show all replies 1`] = ` >
diff --git a/src/core/client/stream/test/comments/postComment.spec.tsx b/src/core/client/stream/test/comments/postComment.spec.tsx index f4594c4dd..35ce4c4f2 100644 --- a/src/core/client/stream/test/comments/postComment.spec.tsx +++ b/src/core/client/stream/test/comments/postComment.spec.tsx @@ -5,7 +5,7 @@ import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets, settings, users } from "../fixtures"; +import { assets, baseComment, settings, users } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -39,20 +39,10 @@ beforeEach(() => { edge: { cursor: null, node: { + ...baseComment, id: "comment-x", author: users[0], body: "Hello world! (from server)", - createdAt: "2018-07-06T18:24:00.000Z", - editing: { - edited: false, - editableUntil: "2018-07-06T18:24:30.000Z", - }, - replies: { edges: [], pageInfo: {} }, - actionCounts: { - reaction: { - total: 0, - }, - }, }, }, clientMutationId: "0", @@ -84,7 +74,7 @@ it("post a comment", async () => { .findByProps({ inputId: "comments-postCommentForm-field" }) .props.onChange({ html: "Hello world!" }); - timekeeper.freeze(new Date("2018-07-06T18:24:00.000Z")); + timekeeper.freeze(new Date(baseComment.createdAt)); testRenderer.root .findByProps({ id: "comments-postCommentForm-form" }) diff --git a/src/core/client/stream/test/comments/postLocalReply.spec.tsx b/src/core/client/stream/test/comments/postLocalReply.spec.tsx index ac69ce5ae..3e665cf80 100644 --- a/src/core/client/stream/test/comments/postLocalReply.spec.tsx +++ b/src/core/client/stream/test/comments/postLocalReply.spec.tsx @@ -5,7 +5,12 @@ import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assetWithDeepestReplies, settings, users } from "../fixtures"; +import { + assetWithDeepestReplies, + baseComment, + settings, + users, +} from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -39,23 +44,10 @@ beforeEach(() => { edge: { cursor: null, node: { + ...baseComment, id: "comment-x", author: users[0], body: "Hello world! (from server)", - createdAt: "2018-07-06T18:24:00.000Z", - replies: { - edges: [], - pageInfo: { endCursor: null, hasNextPage: false }, - }, - editing: { - edited: false, - editableUntil: "2018-07-06T18:24:30.000Z", - }, - actionCounts: { - reaction: { - total: 0, - }, - }, }, }, clientMutationId: "0", @@ -102,7 +94,7 @@ it("post a reply", async () => { }) .props.onChange({ html: "Hello world!" }); - timekeeper.freeze(new Date("2018-07-06T18:24:00.000Z")); + timekeeper.freeze(new Date(baseComment.createdAt)); testRenderer.root .findByProps({ id: "comments-replyCommentForm-form-comment-with-deepest-replies-5", diff --git a/src/core/client/stream/test/comments/postReply.spec.tsx b/src/core/client/stream/test/comments/postReply.spec.tsx index ad96fe03c..ee7b34566 100644 --- a/src/core/client/stream/test/comments/postReply.spec.tsx +++ b/src/core/client/stream/test/comments/postReply.spec.tsx @@ -5,7 +5,7 @@ import timekeeper from "timekeeper"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets, settings, users } from "../fixtures"; +import { assets, baseComment, settings, users } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; @@ -39,23 +39,10 @@ beforeEach(() => { edge: { cursor: null, node: { + ...baseComment, id: "comment-x", author: users[0], body: "Hello world! (from server)", - createdAt: "2018-07-06T18:24:00.000Z", - replies: { - edges: [], - pageInfo: { endCursor: null, hasNextPage: false }, - }, - editing: { - edited: false, - editableUntil: "2018-07-06T18:24:30.000Z", - }, - actionCounts: { - reaction: { - total: 0, - }, - }, }, }, clientMutationId: "0", @@ -97,7 +84,7 @@ it("post a reply", async () => { .findByProps({ inputId: "comments-replyCommentForm-rte-comment-0" }) .props.onChange({ html: "Hello world!" }); - timekeeper.freeze(new Date("2018-07-06T18:24:00.000Z")); + timekeeper.freeze(new Date(baseComment.createdAt)); testRenderer.root .findByProps({ id: "comments-replyCommentForm-form-comment-0" }) .props.onSubmit(); diff --git a/src/core/client/stream/test/fixtures.ts b/src/core/client/stream/test/fixtures.ts index 3eee6a7eb..0b3cc05f9 100644 --- a/src/core/client/stream/test/fixtures.ts +++ b/src/core/client/stream/test/fixtures.ts @@ -21,127 +21,66 @@ export const users = [ }, ]; +export const baseComment = { + author: users[0], + body: "Comment Body", + createdAt: "2018-07-06T18:24:00.000Z", + replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } }, + editing: { + edited: false, + editableUntil: "2018-07-06T18:24:30.000Z", + }, + actionCounts: { + reaction: { + total: 0, + }, + }, +}; + export const comments = [ { + ...baseComment, id: "comment-0", author: users[0], body: "Joining Too", - createdAt: "2018-07-06T18:24:00.000Z", - replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } }, - editing: { - edited: false, - editableUntil: "2018-07-06T18:24:30.000Z", - }, - actionCounts: { - reaction: { - total: 0, - }, - }, }, { + ...baseComment, id: "comment-1", author: users[1], body: "What's up?", - createdAt: "2018-07-06T18:20:00.000Z", - replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } }, - editing: { - edited: false, - editableUntil: "2018-07-06T18:20:30.000Z", - }, - actionCounts: { - reaction: { - total: 0, - }, - }, }, { + ...baseComment, id: "comment-2", author: users[2], body: "Hey!", - createdAt: "2018-07-06T18:14:00.000Z", - replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } }, - editing: { - edited: false, - editableUntil: "2018-07-06T18:14:30.000Z", - }, - actionCounts: { - reaction: { - total: 0, - }, - }, }, { + ...baseComment, id: "comment-3", author: users[2], body: "Comment Body 3", - createdAt: "2018-07-06T18:14:00.000Z", - replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } }, - editing: { - edited: false, - editableUntil: "2018-07-06T18:14:30.000Z", - }, - actionCounts: { - reaction: { - total: 0, - }, - }, }, { + ...baseComment, id: "comment-4", author: users[2], body: "Comment Body 4", - createdAt: "2018-07-06T18:14:00.000Z", - replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } }, - editing: { - edited: false, - editableUntil: "2018-07-06T18:14:30.000Z", - }, - actionCounts: { - reaction: { - total: 0, - }, - }, }, { + ...baseComment, id: "comment-5", author: users[2], body: "Comment Body 5", - createdAt: "2018-07-06T18:14:00.000Z", - replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } }, - editing: { - edited: false, - editableUntil: "2018-07-06T18:14:30.000Z", - }, - actionCounts: { - reaction: { - total: 0, - }, - }, - }, -]; - -export const assets = [ - { - id: "asset-1", - url: "http://localhost/assets/asset-1", - isClosed: false, - comments: { - edges: [ - { node: comments[0], cursor: comments[0].createdAt }, - { node: comments[1], cursor: comments[1].createdAt }, - ], - pageInfo: { - hasNextPage: false, - }, - }, }, ]; export const commentWithReplies = { + ...baseComment, id: "comment-with-replies", author: users[0], body: "I like yoghurt", - createdAt: "2018-07-06T18:24:00.000Z", replies: { edges: [ { node: comments[3], cursor: comments[3].createdAt }, @@ -151,22 +90,13 @@ export const commentWithReplies = { hasNextPage: false, }, }, - editing: { - edited: false, - editableUntil: "2018-07-06T18:24:30.000Z", - }, - actionCounts: { - reaction: { - total: 0, - }, - }, }; export const commentWithDeepReplies = { + ...baseComment, id: "comment-with-deep-replies", author: users[0], body: "I like yoghurt", - createdAt: "2018-07-06T18:24:00.000Z", replies: { edges: [ { node: commentWithReplies, cursor: commentWithReplies.createdAt }, @@ -176,112 +106,69 @@ export const commentWithDeepReplies = { hasNextPage: false, }, }, - editing: { - edited: false, - editableUntil: "2018-07-06T18:24:30.000Z", - }, - actionCounts: { - reaction: { - total: 0, - }, - }, -}; - -export const assetWithReplies = { - id: "asset-with-replies", - url: "http://localhost/assets/asset-with-replies", - isClosed: false, - comments: { - edges: [ - { node: comments[0], cursor: comments[0].createdAt }, - { node: commentWithReplies, cursor: commentWithReplies.createdAt }, - ], - pageInfo: { - hasNextPage: false, - }, - }, -}; - -export const assetWithDeepReplies = { - id: "asset-with-deep-replies", - url: "http://localhost/assets/asset-with-replies", - isClosed: false, - comments: { - edges: [ - { node: comments[0], cursor: comments[0].createdAt }, - { - node: commentWithDeepReplies, - cursor: commentWithDeepReplies.createdAt, - }, - ], - pageInfo: { - hasNextPage: false, - }, - }, }; export const commentWithDeepestReplies = { - ...commentWithReplies, + ...baseComment, id: "comment-with-deepest-replies", body: "body 0", replies: { - ...commentWithReplies.replies, + ...baseComment.replies, edges: [ { - cursor: commentWithReplies.createdAt, + cursor: baseComment.createdAt, node: { - ...commentWithReplies, + ...baseComment, id: "comment-with-deepest-replies-1", body: "body 1", replies: { - ...commentWithReplies.replies, + ...baseComment.replies, edges: [ { - cursor: commentWithReplies.createdAt, + cursor: baseComment.createdAt, node: { - ...commentWithReplies, + ...baseComment, id: "comment-with-deepest-replies-2", body: "body 2", replies: { - ...commentWithReplies.replies, + ...baseComment.replies, edges: [ { - cursor: commentWithReplies.createdAt, + cursor: baseComment.createdAt, node: { - ...commentWithReplies, + ...baseComment, id: "comment-with-deepest-replies-3", body: "body 3", replies: { - ...commentWithReplies.replies, + ...baseComment.replies, edges: [ { - cursor: commentWithReplies.createdAt, + cursor: baseComment.createdAt, node: { - ...commentWithReplies, + ...baseComment, id: "comment-with-deepest-replies-4", body: "body 4", replies: { - ...commentWithReplies.replies, + ...baseComment.replies, edges: [ { - cursor: commentWithReplies.createdAt, + cursor: baseComment.createdAt, node: { - ...commentWithReplies, + ...baseComment, id: "comment-with-deepest-replies-5", body: "body 5", replies: { - ...commentWithReplies.replies, + ...baseComment.replies, edges: [ { - cursor: - commentWithReplies.createdAt, + cursor: baseComment.createdAt, node: { - ...commentWithReplies, + ...baseComment, id: "comment-with-deepest-replies-6", body: "body 6", replies: { - ...commentWithReplies.replies, + ...baseComment.replies, edges: [], }, }, @@ -310,10 +197,70 @@ export const commentWithDeepestReplies = { }, }; +export const baseAsset = { + isClosed: false, + comments: { + edges: [], + pageInfo: { + hasNextPage: false, + }, + }, +}; + +export const assets = [ + { + ...baseAsset, + id: "asset-1", + url: "http://localhost/assets/asset-1", + comments: { + edges: [ + { node: comments[0], cursor: comments[0].createdAt }, + { node: comments[1], cursor: comments[1].createdAt }, + ], + pageInfo: { + hasNextPage: false, + }, + }, + }, +]; + +export const assetWithReplies = { + ...baseAsset, + id: "asset-with-replies", + url: "http://localhost/assets/asset-with-replies", + comments: { + edges: [ + { node: comments[0], cursor: comments[0].createdAt }, + { node: commentWithReplies, cursor: commentWithReplies.createdAt }, + ], + pageInfo: { + hasNextPage: false, + }, + }, +}; + +export const assetWithDeepReplies = { + ...baseAsset, + id: "asset-with-deep-replies", + url: "http://localhost/assets/asset-with-replies", + comments: { + edges: [ + { node: comments[0], cursor: comments[0].createdAt }, + { + node: commentWithDeepReplies, + cursor: commentWithDeepReplies.createdAt, + }, + ], + pageInfo: { + hasNextPage: false, + }, + }, +}; + export const assetWithDeepestReplies = { + ...baseAsset, id: "asset-with-deepest-replies", url: "http://localhost/assets/asset-with-replies", - isClosed: false, comments: { edges: [ { From 7948ed72ee5c756b59858137bb55090a806f2b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Tue, 2 Oct 2018 13:14:50 -0300 Subject: [PATCH 46/53] Changes --- .../mutations/CreateCommentReactionMutation.ts | 17 ++++++++--------- .../mutations/DeleteCommentReactionMutation.ts | 10 +--------- .../tabs/comments/components/ReactionButton.tsx | 2 +- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/core/client/stream/mutations/CreateCommentReactionMutation.ts b/src/core/client/stream/mutations/CreateCommentReactionMutation.ts index 2a044d758..f0e079e7d 100644 --- a/src/core/client/stream/mutations/CreateCommentReactionMutation.ts +++ b/src/core/client/stream/mutations/CreateCommentReactionMutation.ts @@ -18,15 +18,7 @@ const mutation = graphql` mutation CreateCommentReactionMutation($input: CreateCommentReactionInput!) { createCommentReaction(input: $input) { comment { - id - actionCounts { - reaction { - total - } - } - myActionPresence { - reaction - } + ...ReactionButtonContainer_comment } clientMutationId } @@ -36,6 +28,12 @@ const mutation = graphql` let clientMutationId = 0; function commit(environment: Environment, input: CreateCommentReactionInput) { + const source = environment.getStore().getSource(); + + const currentCount = source.get( + source.get(source.get(input.commentID)!.actionCounts.__ref)!.reaction.__ref + )!.total; + return commitMutationPromiseNormalized(environment, { mutation, variables: { @@ -51,6 +49,7 @@ function commit(environment: Environment, input: CreateCommentReactionInput) { myActionPresence: { reaction: true, }, + actionCounts: currentCount, }, clientMutationId: (clientMutationId++).toString(), }, diff --git a/src/core/client/stream/mutations/DeleteCommentReactionMutation.ts b/src/core/client/stream/mutations/DeleteCommentReactionMutation.ts index d05e8faf0..ea1a0acde 100644 --- a/src/core/client/stream/mutations/DeleteCommentReactionMutation.ts +++ b/src/core/client/stream/mutations/DeleteCommentReactionMutation.ts @@ -13,15 +13,7 @@ const mutation = graphql` mutation DeleteCommentReactionMutation($input: CreateCommentReactionInput!) { deleteCommentReaction(input: $input) { comment { - id - actionCounts { - reaction { - total - } - } - myActionPresence { - reaction - } + ...ReactionButtonContainer_comment } clientMutationId } diff --git a/src/core/client/stream/tabs/comments/components/ReactionButton.tsx b/src/core/client/stream/tabs/comments/components/ReactionButton.tsx index ec8df5749..206c0e2ab 100644 --- a/src/core/client/stream/tabs/comments/components/ReactionButton.tsx +++ b/src/core/client/stream/tabs/comments/components/ReactionButton.tsx @@ -18,8 +18,8 @@ class ReactionButton extends React.Component { const { totalReactions, reacted } = this.props; return (