* Fix various Q&A moderation/tagging issues

- Allow Staff members to have their questions
answered and appropriately tagged
- Properly filter answering for only top level
questions (comments)
- Add documentation around various moderation
phases and comment creation steps
- Remove unnecessary status filter when setting
the status for a comment

* fix: abstracted out perspective configs

* fix: reworked tag injection

* Fix sorting/unused imports

Co-authored-by: Wyatt Johnson <accounts+github@wyattjoh.ca>
This commit is contained in:
Nick Funk
2020-02-27 14:40:01 -07:00
committed by GitHub
co-authored by Wyatt Johnson
parent c052d37a6f
commit 769ab2a910
22 changed files with 393 additions and 399 deletions
+1 -15
View File
@@ -1,10 +1,8 @@
import { Db } from "mongodb";
import { Config } from "coral-server/config";
import { CoralEventPublisherBroker } from "coral-server/events/publisher";
import { Tenant } from "coral-server/models/tenant";
import { moderate } from "coral-server/services/comments/moderation";
import { notifyPerspectiveModerationDecision } from "coral-server/services/perspective";
import { AugmentedRedis } from "coral-server/services/redis";
import { GQLCOMMENT_STATUS } from "coral-server/graph/schema/__generated__/types";
@@ -14,7 +12,6 @@ import { publishChanges, updateAllCommentCounts } from "./helpers";
const approveComment = async (
mongo: Db,
redis: AugmentedRedis,
config: Config,
broker: CoralEventPublisherBroker,
tenant: Tenant,
commentID: string,
@@ -48,19 +45,8 @@ const approveComment = async (
...result,
...counts,
moderatorID,
});
// We don't want to await on this so that
// we don't hold up the moderation flow and response
notifyPerspectiveModerationDecision(
mongo,
tenant,
config,
tenant.integrations.perspective,
result.after,
commentRevisionID,
GQLCOMMENT_STATUS.APPROVED
);
});
// Return the resulting comment.
return result.after;
+17 -33
View File
@@ -23,7 +23,6 @@ import {
retrieveComment,
} from "coral-server/models/comment";
import {
getLatestRevision,
hasAncestors,
hasPublishedStatus,
} from "coral-server/models/comment/helpers";
@@ -43,10 +42,6 @@ import {
PhaseResult,
processForModeration,
} from "coral-server/services/comments/pipeline";
import {
publishCommentCreated,
publishCommentReplyCreated,
} from "coral-server/services/events";
import { AugmentedRedis } from "coral-server/services/redis";
import { updateUserLastCommentID } from "coral-server/services/users";
import { Request } from "coral-server/types/express";
@@ -67,7 +62,6 @@ export type CreateComment = Omit<
const markCommentAsAnswered = async (
mongo: Db,
redis: AugmentedRedis,
config: Config,
broker: CoralEventPublisherBroker,
tenant: Tenant,
comment: Readonly<Comment>,
@@ -93,16 +87,19 @@ const markCommentAsAnswered = async (
return;
}
// If we have experts and this reply is created by
// one of them, then this is an expert's answer.
if (story.settings.expertIDs.some(id => id === author.id)) {
// We need to mark this question as answered now.
// We can now remove the unanswered tag.
if (
// If we are the export on this story...
story.settings.expertIDs.some(id => id === author.id) &&
// And this is the first reply (depth of 1)...
comment.ancestorIDs.length === 1
) {
// We need to mark the parent question as answered.
// - Remove the unanswered tag.
// - Approve it since an expert has replied to it.
await removeTag(mongo, tenant, comment.parentID, GQLTAG.UNANSWERED);
await approveComment(
mongo,
redis,
config,
broker,
tenant,
comment.parentID,
@@ -181,7 +178,7 @@ export default async function create(
nudge,
story,
tenant,
comment: input,
comment: { ...input, ancestorIDs },
author,
req,
now,
@@ -218,7 +215,7 @@ export default async function create(
}
// Create the comment!
const comment = await createComment(
const { comment, revision } = await createComment(
mongo,
tenant.id,
{
@@ -234,6 +231,11 @@ export default async function create(
now
);
log = log.child(
{ commentID: comment.id, status, revisionID: revision.id },
true
);
// Updating some associated data.
await Promise.all([
updateUserLastCommentID(redis, tenant, author, comment.id),
@@ -241,7 +243,6 @@ export default async function create(
markCommentAsAnswered(
mongo,
redis,
config,
broker,
tenant,
comment,
@@ -251,14 +252,6 @@ export default async function create(
),
]);
// Pull the revision out.
const revision = getLatestRevision(comment);
log = log.child(
{ commentID: comment.id, status, revisionID: revision.id },
true
);
log.trace("comment created");
if (input.parentID) {
@@ -305,17 +298,8 @@ export default async function create(
await publishChanges(broker, {
...counts,
after: comment,
commentRevisionID: revision.id,
});
// If this is a reply, publish it.
if (input.parentID) {
publishCommentReplyCreated(broker, comment);
}
// If this comment is visible (and not a reply), publish it.
if (!input.parentID && hasPublishedStatus(comment)) {
publishCommentCreated(broker, comment);
}
return comment;
}
+1
View File
@@ -195,6 +195,7 @@ export default async function edit(
await publishChanges(broker, {
...result,
...counts,
commentRevisionID: result.revision.id,
});
// Return the resulting comment.
@@ -6,7 +6,9 @@ import {
hasPublishedStatus,
} from "coral-server/models/comment";
import {
publishCommentCreated,
publishCommentReleased,
publishCommentReplyCreated,
publishCommentStatusChanges,
publishModerationQueueChanges,
} from "coral-server/services/events";
@@ -16,6 +18,7 @@ interface PublishChangesInput {
after: Readonly<Comment>;
moderationQueue: CommentModerationQueueCounts;
moderatorID?: string;
commentRevisionID: string;
}
export default async function publishChanges(
@@ -33,11 +36,24 @@ export default async function publishChanges(
input.before.status,
input.after.status,
input.after.id,
input.commentRevisionID,
input.moderatorID || null
);
if (hasModeratorStatus(input.before) && hasPublishedStatus(input.after)) {
publishCommentReleased(broker, input.after);
}
} else {
// This block is only hit if there is no before (if this is a new comment).
// If this is a reply, publish it.
if (input.after.parentID) {
publishCommentReplyCreated(broker, input.after);
}
// If this comment is visible (and not a reply), publish it.
if (!input.after.parentID && hasPublishedStatus(input.after)) {
publishCommentCreated(broker, input.after);
}
}
}
+1 -15
View File
@@ -1,12 +1,10 @@
import { Db } from "mongodb";
import { Config } from "coral-server/config";
import { CoralEventPublisherBroker } from "coral-server/events/publisher";
import { hasTag } from "coral-server/models/comment";
import { Tenant } from "coral-server/models/tenant";
import { removeTag } from "coral-server/services/comments";
import { moderate } from "coral-server/services/comments/moderation";
import { notifyPerspectiveModerationDecision } from "coral-server/services/perspective";
import { AugmentedRedis } from "coral-server/services/redis";
import {
@@ -19,7 +17,6 @@ import { publishChanges, updateAllCommentCounts } from "./helpers";
const rejectComment = async (
mongo: Db,
redis: AugmentedRedis,
config: Config,
broker: CoralEventPublisherBroker | null,
tenant: Tenant,
commentID: string,
@@ -56,6 +53,7 @@ const rejectComment = async (
...result,
...counts,
moderatorID,
commentRevisionID,
});
}
@@ -64,18 +62,6 @@ const rejectComment = async (
return removeTag(mongo, tenant, result.after.id, GQLTAG.FEATURED);
}
// We don't want to await on this so that
// we don't hold up the moderation flow and response
notifyPerspectiveModerationDecision(
mongo,
tenant,
config,
tenant.integrations.perspective,
result.after,
commentRevisionID,
GQLCOMMENT_STATUS.REJECTED
);
// Return the resulting comment.
return result.after;
};