[CORL-623] add live updates for comments moving from premod to approved (#2571)

* add live updates for comments moving from premod to approved

* rename disposables

* check if comment is system withheld as well as premod to publish event
This commit is contained in:
Tessa Thornton
2019-09-20 16:11:05 -04:00
committed by Wyatt Johnson
parent e4bfbbc1f8
commit b3064c89de
10 changed files with 191 additions and 3 deletions
@@ -25,6 +25,7 @@ import IgnoredTombstoneOrHideContainer from "../../IgnoredTombstoneOrHideContain
import { ReplyListContainer } from "../../ReplyList";
import AllCommentsTabViewNewMutation from "./AllCommentsTabViewNewMutation";
import CommentCreatedSubscription from "./CommentCreatedSubscription";
import CommentReleasedSubscription from "./CommentReleasedSubscription";
import styles from "./AllCommentsTabContainer.css";
@@ -54,6 +55,9 @@ export const AllCommentsTabContainer: FunctionComponent<Props> = props => {
`
);
const subscribeToCommentCreated = useSubscription(CommentCreatedSubscription);
const subscribeToCommentReleased = useSubscription(
CommentReleasedSubscription
);
useEffect(() => {
if (!props.story.settings.live.enabled) {
return;
@@ -78,16 +82,22 @@ export const AllCommentsTabContainer: FunctionComponent<Props> = props => {
// Only chronological sort supports top level live updates of incoming comments.
return;
}
const disposable = subscribeToCommentCreated({
const newCommentDisposable = subscribeToCommentCreated({
storyID: props.story.id,
orderBy: commentsOrderBy,
});
const releasedCommentDisposable = subscribeToCommentReleased({
storyID: props.story.id,
orderBy: commentsOrderBy,
});
return () => {
disposable.dispose();
newCommentDisposable.dispose();
releasedCommentDisposable.dispose();
};
}, [
commentsOrderBy,
subscribeToCommentCreated,
subscribeToCommentReleased,
props.story.id,
props.relay.hasMore(),
props.story.settings.live.enabled,
@@ -0,0 +1,91 @@
import { graphql, requestSubscription } from "react-relay";
import {
ConnectionHandler,
Environment,
RecordProxy,
RecordSourceSelectorProxy,
} from "relay-runtime";
import {
createSubscription,
SubscriptionVariables,
} from "coral-framework/lib/relay";
import { GQLCOMMENT_SORT, GQLCOMMENT_SORT_RL } from "coral-framework/schema";
import { CommentReleasedSubscription } from "coral-stream/__generated__/CommentReleasedSubscription.graphql";
function updateForNewestFirst(
store: RecordSourceSelectorProxy,
storyID: string
) {
const rootField = store.getRootField("commentReleased");
if (!rootField) {
return;
}
const comment = rootField.getLinkedRecord("comment")!;
comment.setValue(true, "enteredLive");
const commentsEdge = store.create(
`edge-${comment.getValue("id")!}`,
"CommentsEdge"
);
commentsEdge.setValue(comment.getValue("createdAt"), "cursor");
commentsEdge.setLinkedRecord(comment, "node");
const story = store.get(storyID)!;
const connection = ConnectionHandler.getConnection(story, "Stream_comments", {
orderBy: GQLCOMMENT_SORT.CREATED_AT_DESC,
})!;
const linked = connection.getLinkedRecords("viewNewEdges") || [];
connection.setLinkedRecords(linked.concat(commentsEdge), "viewNewEdges");
}
function updateForOldestFirst(
store: RecordSourceSelectorProxy,
storyID: string
) {
const story = store.get(storyID)!;
const connection = ConnectionHandler.getConnection(story, "Stream_comments", {
orderBy: GQLCOMMENT_SORT.CREATED_AT_ASC,
})!;
const pageInfo = connection.getLinkedRecord("pageInfo") as RecordProxy;
pageInfo.setValue(true, "hasNextPage");
}
const CommentReleasedSubscription = createSubscription(
"subscribeToCommentReleased",
(
environment: Environment,
variables: SubscriptionVariables<CommentReleasedSubscription> & {
orderBy: GQLCOMMENT_SORT_RL;
}
) =>
requestSubscription(environment, {
subscription: graphql`
subscription CommentReleasedSubscription($storyID: ID!) {
commentReleased(storyID: $storyID) {
comment {
id
createdAt
...AllCommentsTabContainer_comment
}
}
}
`,
variables,
updater: store => {
if (variables.orderBy === GQLCOMMENT_SORT.CREATED_AT_DESC) {
updateForNewestFirst(store, variables.storyID);
return;
}
if (variables.orderBy === GQLCOMMENT_SORT.CREATED_AT_ASC) {
updateForOldestFirst(store, variables.storyID);
return;
}
throw new Error(
`Unsupport new top level comment live updates for sort ${
variables.orderBy
}`
);
},
})
);
export default CommentReleasedSubscription;
@@ -0,0 +1,11 @@
import { GQLCommentReleasedPayloadTypeResolver } from "coral-server/graph/tenant/schema/__generated__/types";
import { maybeLoadOnlyID } from "./Comment";
import { CommentReleasedInput } from "./Subscription/commentReleased";
export const CommentReleasedPayload: GQLCommentReleasedPayloadTypeResolver<
CommentReleasedInput
> = {
comment: ({ commentID }, args, ctx, info) =>
maybeLoadOnlyID(ctx, info, commentID),
};
@@ -0,0 +1,30 @@
import { SubscriptionToCommentReleasedResolver } from "coral-server/graph/tenant/schema/__generated__/types";
import { createIterator } from "./helpers";
import {
SUBSCRIPTION_CHANNELS,
SubscriptionPayload,
SubscriptionType,
} from "./types";
export interface CommentReleasedInput extends SubscriptionPayload {
storyID: string;
commentID: string;
}
export type CommentReleasedSubscription = SubscriptionType<
SUBSCRIPTION_CHANNELS.COMMENT_RELEASED,
CommentReleasedInput
>;
export const commentReleased: SubscriptionToCommentReleasedResolver<
CommentReleasedInput
> = createIterator(SUBSCRIPTION_CHANNELS.COMMENT_RELEASED, {
filter: (source, { storyID }) => {
if (source.storyID !== storyID) {
return false;
}
return true;
},
});
@@ -4,6 +4,7 @@ import { commentCreated } from "./commentCreated";
import { commentEnteredModerationQueue } from "./commentEnteredModerationQueue";
import { commentFeatured } from "./commentFeatured";
import { commentLeftModerationQueue } from "./commentLeftModerationQueue";
import { commentReleased } from "./commentReleased";
import { commentReplyCreated } from "./commentReplyCreated";
import { commentStatusUpdated } from "./commentStatusUpdated";
@@ -14,4 +15,5 @@ export const Subscription: GQLSubscriptionTypeResolver = {
commentReplyCreated,
commentStatusUpdated,
commentFeatured,
commentReleased,
};
@@ -2,6 +2,7 @@ import { CommentCreatedSubscription } from "./commentCreated";
import { CommentEnteredModerationQueueSubscription } from "./commentEnteredModerationQueue";
import { CommentFeaturedSubscription } from "./commentFeatured";
import { CommentLeftModerationQueueSubscription } from "./commentLeftModerationQueue";
import { CommentReleasedSubscription } from "./commentReleased";
import { CommentReplyCreatedSubscription } from "./commentReplyCreated";
import { CommentStatusUpdatedSubscription } from "./commentStatusUpdated";
@@ -12,6 +13,7 @@ export enum SUBSCRIPTION_CHANNELS {
COMMENT_REPLY_CREATED = "COMMENT_REPLY_CREATED",
COMMENT_CREATED = "COMMENT_CREATED",
COMMENT_FEATURED = "COMMENT_FEATURED",
COMMENT_RELEASED = "COMMENT_RELEASED",
}
export interface SubscriptionPayload {
@@ -32,4 +34,5 @@ export type SUBSCRIPTION_INPUT =
| CommentStatusUpdatedSubscription
| CommentReplyCreatedSubscription
| CommentCreatedSubscription
| CommentFeaturedSubscription;
| CommentFeaturedSubscription
| CommentReleasedSubscription;
@@ -14,6 +14,7 @@ import { CommentCreatedPayload } from "./CommentCreatedPayload";
import { CommentEnteredModerationQueuePayload } from "./CommentEnteredModerationQueuePayload";
import { CommentLeftModerationQueuePayload } from "./CommentLeftModerationQueuePayload";
import { CommentModerationAction } from "./CommentModerationAction";
import { CommentReleasedPayload } from "./CommentReleasedPayload";
import { CommentReplyCreatedPayload } from "./CommentReplyCreatedPayload";
import { CommentRevision } from "./CommentRevision";
import { CommentStatusUpdatedPayload } from "./CommentStatusUpdatedPayload";
@@ -52,6 +53,7 @@ const Resolvers: GQLResolver = {
Comment,
CommentCounts,
CommentCreatedPayload,
CommentReleasedPayload,
CommentEnteredModerationQueuePayload,
CommentLeftModerationQueuePayload,
CommentModerationAction,
@@ -5321,6 +5321,16 @@ type CommentFeaturedPayload {
comment: Comment!
}
"""
CommentReleasedPayload is returned when a new top level Comment is approved from premod stream
"""
type CommentReleasedPayload {
"""
comment is the new top level Comment that was approved on the Story.
"""
comment: Comment!
}
type Subscription {
"""
commentEnteredModerationQueue returns when a Comment enters a ModerationQueue.
@@ -5353,6 +5363,11 @@ type Subscription {
"""
commentCreated(storyID: ID!): CommentCreatedPayload!
"""
commentReleased returns when a Comment on a premoderated stream is approved
"""
commentReleased(storyID: ID!): CommentReleasedPayload!
"""
commentReplyCreated returns when a Comment is posted in the ancestor chain of
comments.
@@ -13,6 +13,7 @@ import { updateCommentStatus } from "coral-server/models/comment";
import { updateStoryCounts } from "coral-server/models/story";
import { Tenant } from "coral-server/models/tenant";
import {
publishCommentReleased,
publishCommentStatusChanges,
publishModerationQueueChanges,
} from "coral-server/services/events";
@@ -114,6 +115,14 @@ const moderate = (
result.comment.id,
input.moderatorID
);
if (
[GQLCOMMENT_STATUS.PREMOD, GQLCOMMENT_STATUS.SYSTEM_WITHHELD].includes(
result.oldStatus
) &&
status === "APPROVED"
) {
publishCommentReleased(publish, result.comment);
}
log.trace({ oldStatus: result.oldStatus }, "adjusted story comment counts");
@@ -57,6 +57,21 @@ export function publishCommentCreated(
}
}
export function publishCommentReleased(
publish: Publisher,
comment: Pick<Comment, "id" | "storyID" | "parentID" | "status">
) {
if (!comment.parentID && hasPublishedStatus(comment)) {
publish({
channel: SUBSCRIPTION_CHANNELS.COMMENT_RELEASED,
payload: {
commentID: comment.id,
storyID: comment.storyID,
},
});
}
}
export function publishCommentFeatured(
publish: Publisher,
comment: Pick<Comment, "id" | "status" | "storyID">