[CORL-416] Disable Live Updates (#2391)

* feat: initial implementation

* fix: docs
This commit is contained in:
Wyatt Johnson
2019-07-05 23:10:19 +00:00
committed by GitHub
parent da1fa9c9fc
commit e7745a85aa
41 changed files with 626 additions and 28 deletions
@@ -53,4 +53,5 @@ class ConfigureRoute extends React.Component<Props, State> {
}
const enhanced = withMutation(UpdateSettingsMutation)(ConfigureRoute);
export default enhanced;
@@ -3,13 +3,16 @@ import React, { FunctionComponent } from "react";
import { PropTypesOf } from "coral-framework/types";
import { HorizontalGutter } from "coral-ui/components";
import CommentStreamLiveUpdatesContainer from "./CommentStreamLiveUpdatesContainer";
import CustomCSSConfigContainer from "./CustomCSSConfigContainer";
import PermittedDomainsConfigContainer from "./PermittedDomainsConfigContainer";
interface Props {
disabled: boolean;
settings: PropTypesOf<typeof CustomCSSConfigContainer>["settings"] &
PropTypesOf<typeof PermittedDomainsConfigContainer>["settings"];
PropTypesOf<typeof PermittedDomainsConfigContainer>["settings"] &
PropTypesOf<typeof CommentStreamLiveUpdatesContainer>["settings"] &
PropTypesOf<typeof CommentStreamLiveUpdatesContainer>["settingsReadOnly"];
onInitValues: (values: any) => void;
}
@@ -24,6 +27,12 @@ const AdvancedConfig: FunctionComponent<Props> = ({
settings={settings}
onInitValues={onInitValues}
/>
<CommentStreamLiveUpdatesContainer
disabled={disabled}
settings={settings}
settingsReadOnly={settings}
onInitValues={onInitValues}
/>
<PermittedDomainsConfigContainer
disabled={disabled}
settings={settings}
@@ -3,7 +3,7 @@ import { RouteProps } from "found";
import React from "react";
import { graphql } from "react-relay";
import { AdvancedConfigContainer_settings as SettingsData } from "coral-admin/__generated__/AdvancedConfigContainer_settings.graphql";
import { AdvancedConfigContainer_settings } from "coral-admin/__generated__/AdvancedConfigContainer_settings.graphql";
import { pureMerge } from "coral-common/utils";
import { withFragmentContainer } from "coral-framework/lib/relay";
@@ -12,7 +12,7 @@ import AdvancedConfig from "./AdvancedConfig";
interface Props {
form: FormApi;
submitting: boolean;
settings: SettingsData;
settings: AdvancedConfigContainer_settings;
}
class AdvancedConfigContainer extends React.Component<Props> {
@@ -47,6 +47,8 @@ const enhanced = withFragmentContainer<Props>({
fragment AdvancedConfigContainer_settings on Settings {
...CustomCSSConfigContainer_settings
...PermittedDomainsConfigContainer_settings
...CommentStreamLiveUpdatesContainer_settings
...CommentStreamLiveUpdatesContainer_settingsReadOnly
}
`,
})(AdvancedConfigContainer);
@@ -0,0 +1,35 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { FormField, HorizontalGutter, Typography } from "coral-ui/components";
import Header from "../../Header";
import OnOffField from "../../OnOffField";
interface Props {
disabled: boolean;
}
const CommentStreamLiveUpdates: FunctionComponent<Props> = ({ disabled }) => (
<FormField>
<HorizontalGutter size="full">
<Localized id="configure-advanced-liveUpdates">
<Header container={<label htmlFor="configure-advanced-liveUpdates" />}>
Comment Stream Live Updates
</Header>
</Localized>
<Localized
id="configure-advanced-liveUpdates-explanation"
strong={<strong />}
>
<Typography variant="detail">
When enabled, there will be real-time loading and updating of comments
as new comments and replies are published
</Typography>
</Localized>
<OnOffField name="live.enabled" disabled={disabled} />
</HorizontalGutter>
</FormField>
);
export default CommentStreamLiveUpdates;
@@ -0,0 +1,56 @@
import React from "react";
import { graphql } from "react-relay";
import { CommentStreamLiveUpdatesContainer_settings } from "coral-admin/__generated__/CommentStreamLiveUpdatesContainer_settings.graphql";
import { CommentStreamLiveUpdatesContainer_settingsReadOnly } from "coral-admin/__generated__/CommentStreamLiveUpdatesContainer_settingsReadOnly.graphql";
import { withFragmentContainer } from "coral-framework/lib/relay";
import CommentStreamLiveUpdates from "./CommentStreamLiveUpdates";
interface Props {
settingsReadOnly: CommentStreamLiveUpdatesContainer_settingsReadOnly;
settings: CommentStreamLiveUpdatesContainer_settings;
onInitValues: (values: CommentStreamLiveUpdatesContainer_settings) => void;
disabled: boolean;
}
class CommentStreamLiveUpdatesContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const {
disabled,
settingsReadOnly: {
live: { configurable },
},
} = this.props;
if (!configurable) {
return null;
}
return <CommentStreamLiveUpdates disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment CommentStreamLiveUpdatesContainer_settings on Settings {
live {
enabled
}
}
`,
settingsReadOnly: graphql`
fragment CommentStreamLiveUpdatesContainer_settingsReadOnly on Settings {
live {
configurable
}
}
`,
})(CommentStreamLiveUpdatesContainer);
export default enhanced;
@@ -144,6 +144,75 @@ exports[`renders configure advanced 1`] = `
</div>
</div>
</div>
<div
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<div
className="Box-root HorizontalGutter-root HorizontalGutter-full"
>
<label
className="Box-root Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
htmlFor="configure-advanced-liveUpdates"
>
Comment Stream Live Updates
</label>
<p
className="Box-root Typography-root Typography-detail Typography-colorTextPrimary"
>
When enabled, there will be real-time loading and updating of comments as new comments and replies are published
</p>
<div>
<div
className="Box-root Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={true}
className="RadioButton-input"
disabled={false}
id="live.enabled-true"
name="live.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={true}
/>
<label
className="RadioButton-label"
htmlFor="live.enabled-true"
>
<span>
On
</span>
</label>
</div>
<div
className="Box-root Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="live.enabled-false"
name="live.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={false}
/>
<label
className="RadioButton-label"
htmlFor="live.enabled-false"
>
<span>
Off
</span>
</label>
</div>
</div>
</div>
</div>
<div
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
>
@@ -141,6 +141,32 @@ it("remove custom css", async () => {
});
});
it("renders with live configuration when configurable", async () => {
const { advancedContainer } = await createTestRenderer();
expect(
within(advancedContainer).queryByLabelText("Comment Stream Live Updates")
).toBeDefined();
});
it("renders without live configuration when not configurable", async () => {
const resolvers = createResolversStub<GQLResolver>({
Query: {
settings: () =>
pureMerge<typeof settings>(settings, {
live: { configurable: false },
}),
},
});
const { advancedContainer } = await createTestRenderer({
resolvers,
});
expect(
within(advancedContainer).queryByLabelText("Comment Stream Live Updates")
).toEqual(null);
});
it("change permitted domains to be empty", async () => {
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
+4
View File
@@ -23,6 +23,10 @@ export const settings = createFixture<GQLSettings>({
id: "settings",
moderation: GQLMODERATION_MODE.POST,
premodLinksEnable: false,
live: {
enabled: true,
configurable: true,
},
wordList: {
suspect: ["idiot", "stupid"],
banned: ["fuck"],
@@ -7,6 +7,7 @@ import {
import { SubscriptionClient } from "subscriptions-transport-ws";
import { ACCESS_TOKEN_PARAM, CLIENT_ID_PARAM } from "coral-common/constants";
import { ERROR_CODES } from "coral-common/errors";
/**
* SubscriptionRequest containts the subscription
@@ -88,6 +89,19 @@ export default function createManagedSubscriptionClient(
if (!subscriptionClient) {
subscriptionClient = new SubscriptionClient(url, {
reconnect: true,
connectionCallback: err => {
if (err) {
// If an error is thrown as a result of live updates being
// disabled, then just close the subscription client.
if (
((err as unknown) as Error).message ===
ERROR_CODES.LIVE_UPDATES_DISABLED &&
subscriptionClient
) {
subscriptionClient.close();
}
}
},
connectionParams: {
[ACCESS_TOKEN_PARAM]: accessToken,
[CLIENT_ID_PARAM]: clientID,
@@ -15,6 +15,7 @@ it("renders correctly", () => {
const props: PropTypesOf<typeof ReplyListContainerN> = {
story: {
isClosed: false,
settings: { live: { enabled: true } },
},
comment: {
id: "comment-id",
@@ -50,6 +51,11 @@ it("renders correctly when replies are empty", () => {
const props: PropTypesOf<typeof ReplyListContainerN> = {
story: {
isClosed: false,
settings: {
live: {
enabled: true,
},
},
},
comment: {
id: "comment-id",
@@ -80,6 +86,11 @@ describe("when has more replies", () => {
const props: PropTypesOf<typeof ReplyListContainerN> = {
story: {
isClosed: false,
settings: {
live: {
enabled: true,
},
},
},
comment: {
id: "comment-id",
@@ -61,9 +61,10 @@ export const ReplyListContainer: React.FunctionComponent<Props> = props => {
CommentReplyCreatedSubscription
);
useEffect(() => {
// TODO: (cvle) check for story or settings state
// for whether or not we should turn on subscriptions:
// e.g. `if (!props.story.settings.live) { return; }`
if (!props.story.settings.live.enabled) {
return;
}
if (props.story.isClosed || props.settings.disableCommenting.enabled) {
return;
}
@@ -83,6 +84,7 @@ export const ReplyListContainer: React.FunctionComponent<Props> = props => {
props.indentLevel,
props.relay.hasMore(),
props.liveDirectRepliesInsertion,
props.story.settings.live.enabled,
]);
const viewNew = useMutation(ReplyListViewNewMutation);
@@ -208,6 +210,11 @@ const ReplyListContainer5 = createReplyListContainer(
story: graphql`
fragment ReplyListContainer5_story on Story {
isClosed
settings {
live {
enabled
}
}
...CommentContainer_story
...LocalReplyListContainer_story
}
@@ -282,6 +289,11 @@ const ReplyListContainer4 = createReplyListContainer(
story: graphql`
fragment ReplyListContainer4_story on Story {
isClosed
settings {
live {
enabled
}
}
...ReplyListContainer5_story
...CommentContainer_story
}
@@ -354,6 +366,11 @@ const ReplyListContainer3 = createReplyListContainer(
story: graphql`
fragment ReplyListContainer3_story on Story {
isClosed
settings {
live {
enabled
}
}
...ReplyListContainer4_story
...CommentContainer_story
}
@@ -426,6 +443,11 @@ const ReplyListContainer2 = createReplyListContainer(
story: graphql`
fragment ReplyListContainer2_story on Story {
isClosed
settings {
live {
enabled
}
}
...ReplyListContainer3_story
...CommentContainer_story
}
@@ -498,6 +520,11 @@ const ReplyListContainer1 = createReplyListContainer(
story: graphql`
fragment ReplyListContainer1_story on Story {
isClosed
settings {
live {
enabled
}
}
...ReplyListContainer2_story
...CommentContainer_story
}
@@ -48,6 +48,11 @@ exports[`renders correctly 1`] = `
story={
Object {
"isClosed": false,
"settings": Object {
"live": Object {
"enabled": true,
},
},
}
}
viewer={null}
@@ -74,6 +79,11 @@ exports[`renders correctly 1`] = `
story={
Object {
"isClosed": false,
"settings": Object {
"live": Object {
"enabled": true,
},
},
}
}
viewer={null}
@@ -97,6 +107,11 @@ exports[`renders correctly 1`] = `
story={
Object {
"isClosed": false,
"settings": Object {
"live": Object {
"enabled": true,
},
},
}
}
viewNewCount={0}
@@ -164,6 +179,11 @@ exports[`when has more replies renders hasMore 1`] = `
story={
Object {
"isClosed": false,
"settings": Object {
"live": Object {
"enabled": true,
},
},
}
}
viewNewCount={0}
@@ -229,6 +249,11 @@ exports[`when has more replies when showing all disables show all button 1`] = `
story={
Object {
"isClosed": false,
"settings": Object {
"live": Object {
"enabled": true,
},
},
}
}
viewNewCount={0}
@@ -294,6 +319,11 @@ exports[`when has more replies when showing all enable show all button after loa
story={
Object {
"isClosed": false,
"settings": Object {
"live": Object {
"enabled": true,
},
},
}
}
viewNewCount={0}
@@ -52,9 +52,10 @@ export const AllCommentsTabContainer: FunctionComponent<Props> = props => {
);
const subscribeToCommentCreated = useSubscription(CommentCreatedSubscription);
useEffect(() => {
// TODO: (cvle) check for story or settings state
// for whether or not we should turn on subscriptions:
// e.g. `if (!props.story.settings.live) { return; }`
if (!props.story.settings.live.enabled) {
return;
}
if (props.story.isClosed || props.settings.disableCommenting.enabled) {
return;
}
@@ -86,6 +87,7 @@ export const AllCommentsTabContainer: FunctionComponent<Props> = props => {
subscribeToCommentCreated,
props.story.id,
props.relay.hasMore(),
props.story.settings.live.enabled,
]);
const [loadMore, isLoadingMore] = useLoadMore(props.relay, 10);
const viewMore = useMutation(AllCommentsTabViewNewMutation);
@@ -183,6 +185,11 @@ const enhanced = withPaginationContainer<
) {
id
isClosed
settings {
live {
enabled
}
}
comments(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "Stream_comments") {
viewNewEdges {
@@ -13,6 +13,7 @@ import {
Typography,
} from "coral-ui/components";
import { LiveUpdatesConfigContainer } from "./LiveUpdatesConfig";
import MessageBoxConfigContainer from "./MessageBoxConfig";
import PremodConfigContainer from "./PremodConfig";
import PremodLinksConfigContainer from "./PremodLinksConfig";
@@ -23,7 +24,9 @@ interface Props {
onSubmit: (settings: any, form: FormApi) => void;
storySettings: PropTypesOf<typeof PremodConfigContainer>["storySettings"] &
PropTypesOf<typeof PremodLinksConfigContainer>["storySettings"] &
PropTypesOf<typeof MessageBoxConfigContainer>["storySettings"];
PropTypesOf<typeof MessageBoxConfigContainer>["storySettings"] &
PropTypesOf<typeof LiveUpdatesConfigContainer>["storySettings"] &
PropTypesOf<typeof LiveUpdatesConfigContainer>["storySettingsReadOnly"];
}
const ConfigureStream: FunctionComponent<Props> = ({
@@ -58,6 +61,12 @@ const ConfigureStream: FunctionComponent<Props> = ({
</Flex>
<HorizontalGutter size="double">
{submitError && <CallOut color="error">{submitError}</CallOut>}
<LiveUpdatesConfigContainer
onInitValues={onInitValues}
storySettings={storySettings}
storySettingsReadOnly={storySettings}
disabled={submitting}
/>
<PremodConfigContainer
onInitValues={onInitValues}
storySettings={storySettings}
@@ -51,8 +51,11 @@ const enhanced = withFragmentContainer<Props>({
...PremodConfigContainer_storySettings
...PremodLinksConfigContainer_storySettings
...MessageBoxConfigContainer_storySettings
...LiveUpdatesConfigContainer_storySettings
...LiveUpdatesConfigContainer_storySettingsReadOnly
}
}
`,
})(withUpdateStorySettingsMutation(ConfigureStreamContainer));
export default enhanced;
@@ -0,0 +1,41 @@
import { parseBool } from "coral-framework/lib/form";
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import ToggleConfig from "../ToggleConfig";
import WidthLimitedDescription from "../WidthLimitedDescription";
interface Props {
disabled: boolean;
}
const LiveUpdatesConfig: FunctionComponent<Props> = ({ disabled }) => (
<Field name="live.enabled" type="checkbox" parse={parseBool}>
{({ input }) => (
<ToggleConfig
id={input.name}
name={input.name}
onChange={input.onChange}
onFocus={input.onFocus}
onBlur={input.onBlur}
checked={input.checked}
disabled={disabled}
title={
<Localized id="configure-liveUpdates-title">
<span>Enable Live Updates for this Story</span>
</Localized>
}
>
<Localized id="configure-liveUpdates-description">
<WidthLimitedDescription>
When enabled, there will be real-time loading and updating of
comments as new comments and replies are published.
</WidthLimitedDescription>
</Localized>
</ToggleConfig>
)}
</Field>
);
export default LiveUpdatesConfig;
@@ -0,0 +1,56 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { LiveUpdatesConfigContainer_storySettings } from "coral-stream/__generated__/LiveUpdatesConfigContainer_storySettings.graphql";
import { LiveUpdatesConfigContainer_storySettingsReadOnly } from "coral-stream/__generated__/LiveUpdatesConfigContainer_storySettingsReadOnly.graphql";
import LiveUpdatesConfig from "./LiveUpdatesConfig";
interface Props {
storySettings: LiveUpdatesConfigContainer_storySettings;
storySettingsReadOnly: LiveUpdatesConfigContainer_storySettingsReadOnly;
onInitValues: (values: LiveUpdatesConfigContainer_storySettings) => void;
disabled: boolean;
}
class LiveUpdatesConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.storySettings);
}
public render() {
const {
disabled,
storySettingsReadOnly: {
live: { configurable },
},
} = this.props;
if (!configurable) {
return null;
}
return <LiveUpdatesConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
storySettings: graphql`
fragment LiveUpdatesConfigContainer_storySettings on StorySettings {
live {
enabled
}
}
`,
storySettingsReadOnly: graphql`
fragment LiveUpdatesConfigContainer_storySettingsReadOnly on StorySettings {
live {
configurable
}
}
`,
})(LiveUpdatesConfigContainer);
export default enhanced;
@@ -0,0 +1,4 @@
export {
default,
default as LiveUpdatesConfigContainer,
} from "./LiveUpdatesConfigContainer";
@@ -79,6 +79,48 @@ exports[`renders configure 1`] = `
<div
className="Box-root HorizontalGutter-root HorizontalGutter-double"
>
<div>
<div
className="CheckBox-root"
>
<input
checked={true}
className="CheckBox-input"
disabled={false}
id="live.enabled"
name="live.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="checkbox"
/>
<label
className="CheckBox-label"
htmlFor="live.enabled"
>
<span
className="CheckBox-labelSpan"
>
<span
className="Box-root Typography-root Typography-heading3 Typography-colorTextPrimary"
>
<span>
Enable Live Updates for this Story
</span>
</span>
</span>
</label>
</div>
<div
className="ToggleConfig-details"
>
<p
className="Box-root Typography-root Typography-detail Typography-colorTextSecondary WidthLimitedDescription-root"
>
When enabled, there will be real-time loading and updating of comments as new comments and replies are published.
</p>
</div>
</div>
<div>
<div
className="CheckBox-root"
+8
View File
@@ -23,6 +23,10 @@ export const settings = createFixture<GQLSettings>({
id: "settings",
moderation: GQLMODERATION_MODE.POST,
premodLinksEnable: false,
live: {
enabled: true,
configurable: true,
},
communityGuidelines: {
enabled: false,
content: "",
@@ -358,6 +362,10 @@ export const baseStory = createFixture<GQLStory>({
messageBox: {
enabled: false,
},
live: {
enabled: true,
configurable: true,
},
},
});