mirror of
https://github.com/wassname/talk.git
synced 2026-07-19 11:28:50 +08:00
feat: support auto-pause live updates (#2947)
Co-authored-by: Vinh <vinh@vinh.tech>
This commit is contained in:
+17
@@ -136,6 +136,15 @@ const mutation = graphql`
|
||||
...AllCommentsTabContainer_comment @relay(mask: false)
|
||||
id
|
||||
status
|
||||
story {
|
||||
settings {
|
||||
# Load the story live settings so new comments can verify if live
|
||||
# updates are still enabled (and enable then if they are).
|
||||
live {
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
parent {
|
||||
id
|
||||
tags {
|
||||
@@ -234,6 +243,14 @@ async function commit(
|
||||
dontAgree: false,
|
||||
flag: false,
|
||||
},
|
||||
story: {
|
||||
id: input.storyID,
|
||||
settings: {
|
||||
live: {
|
||||
enabled: storySettings.live.enabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
replies: {
|
||||
edges: [],
|
||||
pageInfo: { endCursor: null, hasNextPage: false },
|
||||
|
||||
@@ -131,6 +131,15 @@ const mutation = graphql`
|
||||
node {
|
||||
...AllCommentsTabContainer_comment @relay(mask: false)
|
||||
status
|
||||
story {
|
||||
settings {
|
||||
# Load the story live settings so new comments can verify if live
|
||||
# updates are still enabled (and enable then if they are).
|
||||
live {
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clientMutationId
|
||||
@@ -219,6 +228,14 @@ async function commit(
|
||||
edges: [],
|
||||
pageInfo: { endCursor: null, hasNextPage: false },
|
||||
},
|
||||
story: {
|
||||
id: input.storyID,
|
||||
settings: {
|
||||
live: {
|
||||
enabled: storySettings.live.enabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
deleted: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -260,6 +260,41 @@ export const commenters = createFixtures<GQLUser>(
|
||||
baseUser
|
||||
);
|
||||
|
||||
export const baseStory = createFixture<GQLStory>({
|
||||
id: "story-0",
|
||||
metadata: {
|
||||
title: "title",
|
||||
},
|
||||
isClosed: false,
|
||||
comments: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
commentCounts: {
|
||||
totalPublished: 0,
|
||||
tags: {
|
||||
FEATURED: 0,
|
||||
UNANSWERED: 0,
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
moderation: GQLMODERATION_MODE.POST,
|
||||
premodLinksEnable: false,
|
||||
messageBox: {
|
||||
enabled: false,
|
||||
},
|
||||
live: {
|
||||
enabled: true,
|
||||
configurable: true,
|
||||
},
|
||||
mode: GQLSTORY_MODE.COMMENTS,
|
||||
experts: [],
|
||||
},
|
||||
site,
|
||||
});
|
||||
|
||||
export const baseComment = createFixture<GQLComment>({
|
||||
author: commenters[0],
|
||||
body: "Comment Body",
|
||||
@@ -279,6 +314,7 @@ export const baseComment = createFixture<GQLComment>({
|
||||
total: 0,
|
||||
},
|
||||
},
|
||||
story: baseStory,
|
||||
parent: undefined,
|
||||
viewerActionPresence: { reaction: false, dontAgree: false, flag: false },
|
||||
tags: [],
|
||||
@@ -514,40 +550,6 @@ export const commentWithDeepestReplies = denormalizeComment(
|
||||
})
|
||||
);
|
||||
|
||||
export const baseStory = createFixture<GQLStory>({
|
||||
metadata: {
|
||||
title: "title",
|
||||
},
|
||||
isClosed: false,
|
||||
comments: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
commentCounts: {
|
||||
totalPublished: 0,
|
||||
tags: {
|
||||
FEATURED: 0,
|
||||
UNANSWERED: 0,
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
moderation: GQLMODERATION_MODE.POST,
|
||||
premodLinksEnable: false,
|
||||
messageBox: {
|
||||
enabled: false,
|
||||
},
|
||||
live: {
|
||||
enabled: true,
|
||||
configurable: true,
|
||||
},
|
||||
mode: GQLSTORY_MODE.COMMENTS,
|
||||
experts: [],
|
||||
},
|
||||
site,
|
||||
});
|
||||
|
||||
export const moderators = createFixtures<GQLUser>(
|
||||
[
|
||||
{
|
||||
|
||||
@@ -217,6 +217,13 @@ const config = convict({
|
||||
default: false,
|
||||
env: "DISABLE_LIVE_UPDATES",
|
||||
},
|
||||
disable_live_updates_timeout: {
|
||||
doc:
|
||||
"Disables subscriptions for the comment stream for all stories across all tenants where a comment has not been left within the timeout",
|
||||
format: "ms",
|
||||
default: "2 weeks",
|
||||
env: "DISABLE_LIVE_UPDATES_TIMEOUT",
|
||||
},
|
||||
disable_client_routes: {
|
||||
doc:
|
||||
"Disables mounting of client routes for developing with Webpack Dev Server",
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { isUndefined } from "lodash";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
import { GQLLiveConfigurationTypeResolver } from "coral-server/graph/schema/__generated__/types";
|
||||
import * as settings from "coral-server/models/settings";
|
||||
|
||||
export type LiveConfigurationInput = settings.LiveConfiguration;
|
||||
import { GQLLiveConfigurationTypeResolver } from "coral-server/graph/schema/__generated__/types";
|
||||
|
||||
export interface LiveConfigurationInput extends settings.LiveConfiguration {
|
||||
lastCommentedAt?: Date;
|
||||
createdAt?: Date;
|
||||
}
|
||||
|
||||
export const LiveConfiguration: GQLLiveConfigurationTypeResolver<LiveConfigurationInput> = {
|
||||
configurable: (source, args, ctx) =>
|
||||
@@ -13,6 +18,32 @@ export const LiveConfiguration: GQLLiveConfigurationTypeResolver<LiveConfigurati
|
||||
return false;
|
||||
}
|
||||
|
||||
// This typecast is needed because the custom `ms` format does not return the
|
||||
// desired `number` type even though that's the only type it can output.
|
||||
const disableLiveUpdatesTimeout = (ctx.config.get(
|
||||
"disable_live_updates_timeout"
|
||||
) as unknown) as number;
|
||||
if (disableLiveUpdatesTimeout > 0) {
|
||||
// If one of these is available, use it to determine the time since the
|
||||
// last comment.
|
||||
const lastCommentedAt = source.lastCommentedAt || source.createdAt;
|
||||
if (
|
||||
// If a date is found...
|
||||
lastCommentedAt &&
|
||||
// And the date (when we add the timeout duration) is before the current
|
||||
// date...
|
||||
DateTime.fromJSDate(lastCommentedAt)
|
||||
.plus({
|
||||
milliseconds: disableLiveUpdatesTimeout,
|
||||
})
|
||||
.toJSDate() <= ctx.now
|
||||
) {
|
||||
// Then we know that the last comment (or lack there of) was left more
|
||||
// than the timeout specified in configuration.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isUndefined(source.enabled)) {
|
||||
return ctx.tenant.live.enabled;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
|
||||
import { CommentCountsInput } from "./CommentCounts";
|
||||
import { storyModerationInputResolver } from "./ModerationQueues";
|
||||
import { StorySettingsInput } from "./StorySettings";
|
||||
|
||||
export const Story: GQLStoryTypeResolver<story.Story> = {
|
||||
comments: (s, input, ctx) => ctx.loaders.Comments.forStory(s.id, input),
|
||||
@@ -26,7 +27,16 @@ export const Story: GQLStoryTypeResolver<story.Story> = {
|
||||
commentCounts: (s): CommentCountsInput => s,
|
||||
// Merge tenant settings into the story settings so we can easily inherit the
|
||||
// options if they exist.
|
||||
settings: (s, input, ctx) => defaultsDeep({}, s.settings, ctx.tenant),
|
||||
settings: (s, input, ctx): StorySettingsInput =>
|
||||
defaultsDeep(
|
||||
{
|
||||
// Pass these options as required by StorySettingsInput.
|
||||
lastCommentedAt: s.lastCommentedAt,
|
||||
createdAt: s.createdAt,
|
||||
},
|
||||
s.settings,
|
||||
ctx.tenant
|
||||
),
|
||||
moderationQueues: storyModerationInputResolver,
|
||||
site: (s, input, ctx) => ctx.loaders.Sites.site.load(s.siteID),
|
||||
};
|
||||
|
||||
@@ -7,8 +7,21 @@ import {
|
||||
GQLStorySettingsTypeResolver,
|
||||
} from "../schema/__generated__/types";
|
||||
|
||||
export const StorySettings: GQLStorySettingsTypeResolver<story.StorySettings> = {
|
||||
live: (s) => s.live || {},
|
||||
import { LiveConfigurationInput } from "./LiveConfiguration";
|
||||
|
||||
export interface StorySettingsInput extends story.StorySettings {
|
||||
lastCommentedAt?: Date;
|
||||
createdAt?: Date;
|
||||
}
|
||||
|
||||
export const StorySettings: GQLStorySettingsTypeResolver<StorySettingsInput> = {
|
||||
live: (s): LiveConfigurationInput => ({
|
||||
// Live may not be available sometimes, fix it here with the inline ||.
|
||||
...(s.live || { enabled: false }),
|
||||
// Pass these options as required by LiveConfigurationInput.
|
||||
lastCommentedAt: s.lastCommentedAt,
|
||||
createdAt: s.createdAt,
|
||||
}),
|
||||
moderation: (s, input, ctx) => s.moderation || ctx.tenant.moderation,
|
||||
premodLinksEnable: (s, input, ctx) =>
|
||||
s.premodLinksEnable || ctx.tenant.premodLinksEnable,
|
||||
|
||||
@@ -48,11 +48,9 @@ export interface StreamModeSettings {
|
||||
expertIDs?: string[];
|
||||
}
|
||||
|
||||
export type StorySettings = DeepPartial<
|
||||
StreamModeSettings &
|
||||
GlobalModerationSettings &
|
||||
Pick<GQLStorySettings, "messageBox" | "mode" | "experts">
|
||||
>;
|
||||
export type StorySettings = StreamModeSettings &
|
||||
GlobalModerationSettings &
|
||||
Pick<GQLStorySettings, "messageBox" | "mode" | "experts">;
|
||||
|
||||
export type StoryMetadata = GQLStoryMetadata;
|
||||
|
||||
@@ -83,7 +81,7 @@ export interface Story extends TenantResource {
|
||||
* settings provides a point where the settings can be overridden for a
|
||||
* specific Story.
|
||||
*/
|
||||
settings: StorySettings;
|
||||
settings: DeepPartial<StorySettings>;
|
||||
|
||||
/**
|
||||
* closedAt is the date that the Story was forced closed at, or false to
|
||||
@@ -378,7 +376,7 @@ export async function updateStory(
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
export type UpdateStorySettingsInput = StorySettings;
|
||||
export type UpdateStorySettingsInput = DeepPartial<StorySettings>;
|
||||
|
||||
export async function updateStorySettings(
|
||||
mongo: Db,
|
||||
|
||||
Reference in New Issue
Block a user