[next] Flag Improvements (#2114)

* fix: applied fix for reaction mutation

* fix: removed comment length flag

* feat: added additionalDetails field to flags

* feat: added other flag type
This commit is contained in:
Wyatt Johnson
2018-12-12 22:49:51 +01:00
committed by Kiwi
parent 8a64e6f62d
commit e134b4de04
10 changed files with 111 additions and 54 deletions
@@ -5,12 +5,17 @@ import {
commitMutationPromiseNormalized,
createMutationContainer,
} from "talk-framework/lib/relay";
import { Omit } from "talk-framework/types";
import { RemoveCommentReactionMutation as MutationTypes } from "talk-stream/__generated__/RemoveCommentReactionMutation.graphql";
import { CreateCommentReactionInput } from "talk-stream/mutations/CreateCommentReactionMutation";
export type RemoveCommentReactionInput = Omit<
MutationTypes["variables"]["input"],
"clientMutationId"
>;
const mutation = graphql`
mutation RemoveCommentReactionMutation($input: CreateCommentReactionInput!) {
mutation RemoveCommentReactionMutation($input: RemoveCommentReactionInput!) {
removeCommentReaction(input: $input) {
comment {
...ReactionButtonContainer_comment
@@ -22,7 +27,7 @@ const mutation = graphql`
let clientMutationId = 0;
function commit(environment: Environment, input: CreateCommentReactionInput) {
function commit(environment: Environment, input: RemoveCommentReactionInput) {
const source = environment.getStore().getSource();
const currentCount = source.get(
source.get(source.get(input.commentID)!.actionCounts.__ref)!.reaction.__ref
@@ -60,5 +65,5 @@ export const withRemoveCommentReactionMutation = createMutationContainer(
);
export type RemoveCommentReactionMutation = (
input: CreateCommentReactionInput
input: RemoveCommentReactionInput
) => Promise<MutationTypes["response"]["removeCommentReaction"]>;
@@ -17,6 +17,13 @@ import {
removeDontAgree,
removeReaction,
} from "talk-server/services/comments/actions";
import { validateMaximumLength } from "./util";
/**
* MAX_ADDITIONAL_DETAILS_LENGTH defines the maximum length for the
* additionalDetails field.
*/
const MAX_ADDITIONAL_DETAILS_LENGTH = 500;
export const Comment = (ctx: TenantContext) => ({
create: ({
@@ -58,10 +65,16 @@ export const Comment = (ctx: TenantContext) => ({
createDontAgree: ({
commentID,
commentRevisionID,
additionalDetails,
}: GQLCreateCommentDontAgreeInput) =>
createDontAgree(ctx.mongo, ctx.redis, ctx.tenant, ctx.user!, {
commentID,
commentRevisionID,
// TODO: (wyattjoh) move this validation to the schema when bug is fixed: https://github.com/apollographql/graphql-tools/issues/842
additionalDetails: validateMaximumLength(
MAX_ADDITIONAL_DETAILS_LENGTH,
additionalDetails
),
}),
removeDontAgree: ({ commentID }: GQLRemoveCommentDontAgreeInput) =>
removeDontAgree(ctx.mongo, ctx.redis, ctx.tenant, ctx.user!, {
@@ -71,10 +84,16 @@ export const Comment = (ctx: TenantContext) => ({
commentID,
commentRevisionID,
reason,
additionalDetails,
}: GQLCreateCommentFlagInput) =>
createFlag(ctx.mongo, ctx.redis, ctx.tenant, ctx.user!, {
commentID,
commentRevisionID,
reason,
// TODO: (wyattjoh) move this validation to the schema when bug is fixed: https://github.com/apollographql/graphql-tools/issues/842
additionalDetails: validateMaximumLength(
MAX_ADDITIONAL_DETAILS_LENGTH,
additionalDetails
),
}),
});
@@ -0,0 +1,13 @@
import { validateMaximumLength } from "./util";
it("limits to maximum when string is provided", () => {
expect(() => validateMaximumLength(5, "123456")).toThrow();
});
it("limits to maximum when string is not provided", () => {
expect(validateMaximumLength(5)).toEqual(undefined);
});
it("returns the string when string is provided and is within the length allowances", () => {
expect(validateMaximumLength(5, "1234")).toEqual("1234");
});
@@ -0,0 +1,14 @@
/**
* validateMaximumLength will limit the value of an optional string to the
* specified amount, otherwise will throw an error.
*
* @param maxLength maximum length to limit a value to
* @param value the value that should be limited
*/
export function validateMaximumLength(maxLength: number, value?: string) {
if (value && value.length > maxLength) {
throw new Error(`Exceeded maximum length of ${maxLength} characters`);
}
return value;
}
@@ -46,6 +46,12 @@ enum COMMENT_FLAG_REPORTED_REASON {
spam.
"""
COMMENT_REPORTED_SPAM
"""
COMMENT_REPORTED_OTHER is used when a User reported a Comment that doesn't
fit into the other reported reasons.
"""
COMMENT_REPORTED_OTHER
}
"""
@@ -65,12 +71,6 @@ enum COMMENT_FLAG_DETECTED_REASON {
"""
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.
@@ -103,9 +103,9 @@ and COMMENT_FLAG_DETECTED_REASON types.
enum COMMENT_FLAG_REASON {
COMMENT_REPORTED_OFFENSIVE
COMMENT_REPORTED_SPAM
COMMENT_REPORTED_OTHER
COMMENT_DETECTED_TOXIC
COMMENT_DETECTED_SPAM
COMMENT_DETECTED_BODY_COUNT
COMMENT_DETECTED_TRUST
COMMENT_DETECTED_LINKS
COMMENT_DETECTED_BANNED_WORD
@@ -137,9 +137,9 @@ type DontAgreeActionCounts {
type FlagReasonActionCounts {
COMMENT_REPORTED_OFFENSIVE: Int!
COMMENT_REPORTED_SPAM: Int!
COMMENT_REPORTED_OTHER: 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!
@@ -2230,6 +2230,12 @@ input CreateCommentDontAgreeInput {
"""
commentRevisionID: ID!
"""
additionalDetails stores information from the User as to why the Flag was
created or is relevant.
"""
additionalDetails: String
"""
clientMutationId is required for Relay support.
"""
@@ -2297,6 +2303,12 @@ input CreateCommentFlagInput {
"""
reason: COMMENT_FLAG_REPORTED_REASON!
"""
additionalDetails stores information from the User as to why the Flag was
created or is relevant.
"""
additionalDetails: String
"""
clientMutationId is required for Relay support.
"""
@@ -2964,8 +2976,8 @@ type Mutation {
User on a Comment if it exists.
"""
removeCommentReaction(
input: CreateCommentReactionInput!
): CreateCommentReactionPayload @auth
input: RemoveCommentReactionInput!
): RemoveCommentReactionPayload @auth
"""
createCommentDontAgree will create a DontAgree authored by the current logged in
@@ -2980,8 +2992,8 @@ type Mutation {
User on a Comment if it exists.
"""
removeCommentDontAgree(
input: CreateCommentDontAgreeInput!
): CreateCommentDontAgreePayload @auth
input: RemoveCommentDontAgreeInput!
): RemoveCommentDontAgreePayload @auth
"""
createCommentFlag will create a Flag authored by the current logged in User on
@@ -5,7 +5,7 @@ Object {
"DONT_AGREE": 1,
"FLAG": 2,
"FLAG__COMMENT_DETECTED_BANNED_WORD": 1,
"FLAG__COMMENT_DETECTED_BODY_COUNT": 1,
"FLAG__COMMENT_REPORTED_OTHER": 1,
"REACTION": 3,
}
`;
@@ -18,13 +18,13 @@ Object {
"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_OTHER": 1,
"COMMENT_REPORTED_SPAM": 0,
},
"total": 2,
@@ -40,6 +40,6 @@ Object {
"DONT_AGREE": 1,
"FLAG": 2,
"FLAG__COMMENT_DETECTED_BANNED_WORD": 1,
"FLAG__COMMENT_DETECTED_BODY_COUNT": 1,
"FLAG__COMMENT_REPORTED_OTHER": 1,
}
`;
@@ -19,7 +19,7 @@ describe("#encodeActionCounts", () => {
},
{
actionType: ACTION_TYPE.FLAG,
reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT,
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OTHER,
},
];
const actionCounts = encodeActionCounts(...(actions as CommentAction[]));
@@ -41,7 +41,7 @@ describe("#decodeActionCounts", () => {
},
{
actionType: ACTION_TYPE.FLAG,
reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT,
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OTHER,
},
];
@@ -76,7 +76,7 @@ describe("#validateAction", () => {
},
{
actionType: ACTION_TYPE.FLAG,
reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT,
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OTHER,
},
{
actionType: ACTION_TYPE.FLAG,
@@ -109,7 +109,7 @@ describe("#validateAction", () => {
},
{
actionType: ACTION_TYPE.DONT_AGREE,
reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT,
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OTHER,
},
{
actionType: ACTION_TYPE.FLAG,
+8 -1
View File
@@ -75,6 +75,12 @@ export interface CommentAction extends TenantResource {
*/
reason?: FLAG_REASON;
/**
* additionalDetails stores information from the User as to why the Flag was
* created or is relevant.
*/
additionalDetails?: string;
/**
* storyID represents the ID of the Story where the comment was left on.
*/
@@ -168,7 +174,7 @@ export async function createAction(
tenantID: string,
input: CreateActionInput
): Promise<CreateActionResultObject> {
const { metadata, ...filter } = input;
const { metadata, additionalDetails, ...filter } = input;
// Create a new ID for the action.
const id = uuid.v4();
@@ -185,6 +191,7 @@ export async function createAction(
const action: Readonly<CommentAction> = {
...defaults,
...input,
additionalDetails,
};
// Create the upsert/update operation.
+4 -2
View File
@@ -226,7 +226,7 @@ export async function removeReaction(
export type CreateCommentDontAgree = Pick<
CreateActionInput,
"commentID" | "commentRevisionID"
"commentID" | "commentRevisionID" | "additionalDetails"
>;
export async function createDontAgree(
@@ -240,6 +240,7 @@ export async function createDontAgree(
actionType: ACTION_TYPE.DONT_AGREE,
commentID: input.commentID,
commentRevisionID: input.commentRevisionID,
additionalDetails: input.additionalDetails,
userID: author.id,
});
}
@@ -262,7 +263,7 @@ export async function removeDontAgree(
export type CreateCommentFlag = Pick<
CreateActionInput,
"commentID" | "commentRevisionID"
"commentID" | "commentRevisionID" | "additionalDetails"
> & {
reason: GQLCOMMENT_FLAG_REPORTED_REASON;
};
@@ -279,6 +280,7 @@ export async function createFlag(
reason: input.reason,
commentID: input.commentID,
commentRevisionID: input.commentRevisionID,
additionalDetails: input.additionalDetails,
userID: author.id,
});
}
@@ -1,11 +1,6 @@
import striptags from "striptags";
import { isNil } from "lodash";
import {
GQLCOMMENT_FLAG_REASON,
GQLCOMMENT_STATUS,
} from "talk-server/graph/tenant/schema/__generated__/types";
import { ACTION_TYPE } from "talk-server/models/action/comment";
import { ModerationSettings } from "talk-server/models/settings";
import {
IntermediateModerationPhase,
@@ -16,22 +11,26 @@ const testCharCount = (
settings: Partial<ModerationSettings>,
length: number
) => {
// settings.charCount.enable && settings.charCount && length > settings.charCount;
if (settings.charCount && settings.charCount.enabled) {
if (!isNil(settings.charCount.min)) {
if (length < settings.charCount.min) {
return true;
throw new Error(
`Body did not meet minimum length requirement of ${
settings.charCount.min
}`
);
}
}
if (!isNil(settings.charCount.max)) {
if (length > settings.charCount.max) {
return true;
throw new Error(
`Body exceeded maximum length requirement of ${
settings.charCount.max
}`
);
}
}
}
return false;
};
export const commentLength: IntermediateModerationPhase = ({
@@ -42,22 +41,8 @@ export const commentLength: IntermediateModerationPhase = ({
const length = comment.body ? striptags(comment.body).length : 0;
// Reject if the comment is too long or too short.
if (
testCharCount(tenant, length) ||
(story.settings && testCharCount(story.settings, length))
) {
return {
status: GQLCOMMENT_STATUS.REJECTED,
actions: [
{
userID: null,
actionType: ACTION_TYPE.FLAG,
reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT,
metadata: {
count: length,
},
},
],
};
testCharCount(tenant, length);
if (story.settings) {
testCharCount(story.settings, length);
}
};