[CORL-116] Configure sitewide commenting (#2193)

* feat: configure sitewide commenting

* fix: repaired snapshots

* fix: updated snapshots

* test: update snapshots
This commit is contained in:
Kiwi
2019-02-13 18:36:37 +01:00
committed by GitHub
parent 51880bcfc9
commit c91a0fafa5
14 changed files with 454 additions and 76 deletions
@@ -11,48 +11,56 @@ interface Props {
name: string;
disabled: boolean;
invert?: boolean;
onLabel?: React.ReactNode;
offLabel?: React.ReactNode;
}
const OnOffField: StatelessComponent<Props> = ({
name,
disabled,
onLabel,
offLabel,
invert = false,
}) => (
<div>
<Field name={name} type="radio" parse={parseStringBool} value={!invert}>
{({ input }) => (
<Localized id="configure-onOffField-on">
<RadioButton
id={`${input.name}-true`}
name={input.name}
onChange={input.onChange}
onFocus={input.onFocus}
onBlur={input.onBlur}
checked={input.checked}
disabled={disabled}
value={input.value}
>
On
</RadioButton>
</Localized>
<RadioButton
id={`${input.name}-true`}
name={input.name}
onChange={input.onChange}
onFocus={input.onFocus}
onBlur={input.onBlur}
checked={input.checked}
disabled={disabled}
value={input.value}
>
{onLabel || (
<Localized id="configure-onOffField-on">
<span>On</span>
</Localized>
)}
</RadioButton>
)}
</Field>
<Field name={name} type="radio" parse={parseStringBool} value={invert}>
{({ input }) => (
<Localized id="configure-onOffField-off">
<RadioButton
id={`${input.name}-fase`}
name={input.name}
onChange={input.onChange}
onFocus={input.onFocus}
onBlur={input.onBlur}
checked={input.checked}
disabled={disabled}
value={input.value}
>
Off
</RadioButton>
</Localized>
<RadioButton
id={`${input.name}-false`}
name={input.name}
onChange={input.onChange}
onFocus={input.onFocus}
onBlur={input.onBlur}
checked={input.checked}
disabled={disabled}
value={input.value}
>
{offLabel || (
<Localized id="configure-onOffField-off">
<span>Off</span>
</Localized>
)}
</RadioButton>
)}
</Field>
</div>
@@ -8,6 +8,7 @@ import ClosingCommentStreamsConfigContainer from "../containers/ClosingCommentSt
import CommentEditingConfigContainer from "../containers/CommentEditingConfigContainer";
import CommentLengthConfigContainer from "../containers/CommentLengthConfigContainer";
import GuidelinesConfigContainer from "../containers/GuidelinesConfigContainer";
import SitewideCommentingConfigContainer from "../containers/SitewideCommentingConfigContainer";
interface Props {
disabled: boolean;
@@ -15,7 +16,8 @@ interface Props {
PropTypesOf<typeof CommentLengthConfigContainer>["settings"] &
PropTypesOf<typeof CommentEditingConfigContainer>["settings"] &
PropTypesOf<typeof ClosedStreamMessageConfigContainer>["settings"] &
PropTypesOf<typeof ClosingCommentStreamsConfigContainer>["settings"];
PropTypesOf<typeof ClosingCommentStreamsConfigContainer>["settings"] &
PropTypesOf<typeof SitewideCommentingConfigContainer>["settings"];
onInitValues: (values: any) => void;
}
@@ -25,6 +27,11 @@ const General: StatelessComponent<Props> = ({
onInitValues,
}) => (
<HorizontalGutter size="double" data-testid="configure-generalContainer">
<SitewideCommentingConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<GuidelinesConfigContainer
disabled={disabled}
settings={settings}
@@ -0,0 +1,95 @@
import { Localized } from "fluent-react/compat";
import React, { StatelessComponent, Suspense } from "react";
import { Field } from "react-final-form";
import {
FormField,
HorizontalGutter,
InputDescription,
InputLabel,
Spinner,
Typography,
ValidationMessage,
} from "talk-ui/components";
import OnOffField from "talk-admin/routes/configure/components/OnOffField";
import Header from "../../../components/Header";
import LazyMarkdown from "./LazyMarkdown";
interface Props {
disabled: boolean;
}
const SitewideCommentingConfig: StatelessComponent<Props> = ({ disabled }) => (
<HorizontalGutter size="oneAndAHalf" container="fieldset">
<Localized id="configure-general-sitewideCommenting-title">
<Header container="legend">Sitewide Commenting</Header>
</Localized>
<Localized id="configure-general-sitewideCommenting-explanation">
<Typography variant="detail">
Open or close comment streams for new comments sitewide. When new
comments are turned off sitewide, new comments cannot be submitted, but
existing comments can continue to receive Respect reactions, be
reported, and be shared.
</Typography>
</Localized>
<FormField container="fieldset">
<Localized id="configure-general-sitewideCommenting-enableNewCommentsSitewide">
<InputLabel container="legend">Enable New Comments Sitewide</InputLabel>
</Localized>
<OnOffField
name="disableCommenting.enabled"
disabled={disabled}
invert
onLabel={
<Localized id="configure-general-sitewideCommenting-onCommentStreamsOpened">
<span>On - Comment streams opened for new comments</span>
</Localized>
}
offLabel={
<Localized id="configure-general-sitewideCommenting-offCommentStreamsClosed">
<span>Off - Comment streams closed for new comments</span>
</Localized>
}
/>
</FormField>
<FormField>
<Localized id="configure-general-sitewideCommenting-message">
<InputLabel htmlFor="configure-general-sitewideCommenting-message">
Sitewide Closed Comments Message
</InputLabel>
</Localized>
<Localized id="configure-general-sitewideCommenting-messageExplanation">
<InputDescription>
Write a message that will be displayed when comment streams are closed
sitewide
</InputDescription>
</Localized>
</FormField>
<Field name="disableCommenting.message">
{({ input, meta }) => (
<>
<Suspense fallback={<Spinner />}>
<LazyMarkdown
id="configure-general-sitewideCommenting-message"
name={input.name}
onChange={input.onChange}
value={input.value}
/>
</Suspense>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
</HorizontalGutter>
);
export default SitewideCommentingConfig;
@@ -50,6 +50,7 @@ const enhanced = withFragmentContainer<Props>({
...CommentEditingConfigContainer_settings
...ClosedStreamMessageConfigContainer_settings
...ClosingCommentStreamsConfigContainer_settings
...SitewideCommentingConfigContainer_settings
}
`,
})(GeneralConfigContainer);
@@ -0,0 +1,38 @@
import React from "react";
import { graphql } from "react-relay";
import { SitewideCommentingConfigContainer_settings as SettingsData } from "talk-admin/__generated__/SitewideCommentingConfigContainer_settings.graphql";
import { withFragmentContainer } from "talk-framework/lib/relay";
import SitewideCommentingConfig from "../components/SitewideCommentingConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class SitewideCommentingConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <SitewideCommentingConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment SitewideCommentingConfigContainer_settings on Settings {
disableCommenting {
enabled
message
}
}
`,
})(SitewideCommentingConfigContainer);
export default enhanced;
@@ -107,6 +107,107 @@ exports[`renders configure general 1`] = `
className="HorizontalGutter-root HorizontalGutter-double"
data-testid="configure-generalContainer"
>
<fieldset
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<legend
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
>
Sitewide Commenting
</legend>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Open or close comment streams for new comments sitewide. When new comments
are turned off sitewide, new comments cannot be submitted, but existing
comments can continue to receive “Respect” reactions, be reported, and be
shared.
</p>
<fieldset
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<legend
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
>
Enable New Comments Sitewide
</legend>
<div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={true}
className="RadioButton-input"
disabled={false}
id="disableCommenting.enabled-true"
name="disableCommenting.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={false}
/>
<label
className="RadioButton-label"
htmlFor="disableCommenting.enabled-true"
>
<span>
On - Comment streams opened for new comments
</span>
</label>
</div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="disableCommenting.enabled-false"
name="disableCommenting.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={true}
/>
<label
className="RadioButton-label"
htmlFor="disableCommenting.enabled-false"
>
<span>
Off - Comment streams closed for new comments
</span>
</label>
</div>
</div>
</fieldset>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-general-sitewideCommenting-message"
>
Sitewide Closed Comments Message
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
Write a message that will be displayed when comment streams are closed sitewide
</p>
</div>
<div
className="MarkdownEditor-wrapper"
>
<textarea
id="configure-general-sitewideCommenting-message"
name="disableCommenting.message"
onChange={[Function]}
value=""
/>
</div>
</fieldset>
<fieldset
className="FieldSet-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
@@ -143,7 +244,9 @@ exports[`renders configure general 1`] = `
className="RadioButton-label"
htmlFor="communityGuidelines.enabled-true"
>
On
<span>
On
</span>
</label>
</div>
<div
@@ -153,7 +256,7 @@ exports[`renders configure general 1`] = `
checked={true}
className="RadioButton-input"
disabled={false}
id="communityGuidelines.enabled-fase"
id="communityGuidelines.enabled-false"
name="communityGuidelines.enabled"
onBlur={[Function]}
onChange={[Function]}
@@ -163,9 +266,11 @@ exports[`renders configure general 1`] = `
/>
<label
className="RadioButton-label"
htmlFor="communityGuidelines.enabled-fase"
htmlFor="communityGuidelines.enabled-false"
>
Off
<span>
Off
</span>
</label>
</div>
</div>
@@ -248,7 +353,9 @@ Markdown can be found
className="RadioButton-label"
htmlFor="charCount.enabled-true"
>
On
<span>
On
</span>
</label>
</div>
<div
@@ -258,7 +365,7 @@ Markdown can be found
checked={true}
className="RadioButton-input"
disabled={false}
id="charCount.enabled-fase"
id="charCount.enabled-false"
name="charCount.enabled"
onBlur={[Function]}
onChange={[Function]}
@@ -268,9 +375,11 @@ Markdown can be found
/>
<label
className="RadioButton-label"
htmlFor="charCount.enabled-fase"
htmlFor="charCount.enabled-false"
>
Off
<span>
Off
</span>
</label>
</div>
</div>
@@ -471,7 +580,9 @@ moderation panel.
className="RadioButton-label"
htmlFor="autoCloseStream-true"
>
On
<span>
On
</span>
</label>
</div>
<div
@@ -481,7 +592,7 @@ moderation panel.
checked={true}
className="RadioButton-input"
disabled={false}
id="autoCloseStream-fase"
id="autoCloseStream-false"
name="autoCloseStream"
onBlur={[Function]}
onChange={[Function]}
@@ -491,9 +602,11 @@ moderation panel.
/>
<label
className="RadioButton-label"
htmlFor="autoCloseStream-fase"
htmlFor="autoCloseStream-false"
>
Off
<span>
Off
</span>
</label>
</div>
</div>
@@ -158,7 +158,9 @@ the
className="RadioButton-label"
htmlFor="integrations.perspective.enabled-true"
>
On
<span>
On
</span>
</label>
</div>
<div
@@ -168,7 +170,7 @@ the
checked={true}
className="RadioButton-input"
disabled={false}
id="integrations.perspective.enabled-fase"
id="integrations.perspective.enabled-false"
name="integrations.perspective.enabled"
onBlur={[Function]}
onChange={[Function]}
@@ -178,9 +180,11 @@ the
/>
<label
className="RadioButton-label"
htmlFor="integrations.perspective.enabled-fase"
htmlFor="integrations.perspective.enabled-false"
>
Off
<span>
Off
</span>
</label>
</div>
</div>
@@ -424,7 +428,9 @@ the comment will be published.
className="RadioButton-label"
htmlFor="integrations.akismet.enabled-true"
>
On
<span>
On
</span>
</label>
</div>
<div
@@ -434,7 +440,7 @@ the comment will be published.
checked={true}
className="RadioButton-input"
disabled={false}
id="integrations.akismet.enabled-fase"
id="integrations.akismet.enabled-false"
name="integrations.akismet.enabled"
onBlur={[Function]}
onChange={[Function]}
@@ -444,9 +450,11 @@ the comment will be published.
/>
<label
className="RadioButton-label"
htmlFor="integrations.akismet.enabled-fase"
htmlFor="integrations.akismet.enabled-false"
>
Off
<span>
Off
</span>
</label>
</div>
</div>
@@ -68,6 +68,67 @@ it("renders configure general", async () => {
expect(within(configureContainer).toJSON()).toMatchSnapshot();
});
it("change site wide commenting", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.disableCommenting).toEqual({
enabled: true,
message: "Closing message",
});
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const sitewideCommentingContainer = within(generalContainer).getAllByText(
"Sitewide Commenting",
{ selector: "fieldset" }
)[0];
const offField = within(sitewideCommentingContainer).getByLabelText(
"Off - Comment streams closed for new comments"
);
const contentField = within(sitewideCommentingContainer).getByLabelText(
"Sitewide Closed Comments Message"
);
// Let's enable it.
offField.props.onChange(offField.props.value.toString());
// Let's change the content.
contentField.props.onChange("Closing message");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(offField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(offField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
});
it("change community guidlines", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
+3
View File
@@ -11,6 +11,9 @@ export const settings = {
max: 1000,
min: 3,
},
disableCommenting: {
enabled: false,
},
closedTimeout: 604800,
autoCloseStream: false,
closedMessage: null,
@@ -1,3 +1,4 @@
import { uniq } from "lodash";
import { ReactTestInstance } from "react-test-renderer";
import { queryAllByText } from "./byText";
@@ -62,6 +63,7 @@ export function queryAllByLabelText(
options?: TextMatchOptions
) {
const matches = container.findAll(ariaLabelMatcher(pattern, options));
// Find matching aria-labelledby and id pairs.
queryAllByText(container, pattern, options).forEach(i => {
if (typeof i.type !== "string") {
return;
@@ -77,15 +79,20 @@ export function queryAllByLabelText(
);
} catch {} // tslint:disable-line:no-empty
}
if (i.type === "label" && i.props.htmlFor) {
try {
matches.push(
container.find(
x => typeof x.type === "string" && x.props.id === i.props.htmlFor
)
);
} catch {} // tslint:disable-line:no-empty
}
});
return matches;
// Find matching labels.
queryAllByText(container, pattern, { ...options, selector: "label" }).forEach(
i => {
if (i.props.htmlFor) {
try {
matches.push(
container.find(
x => typeof x.type === "string" && x.props.id === i.props.htmlFor
)
);
} catch {} // tslint:disable-line:no-empty
}
}
);
return uniq(matches);
}
@@ -764,6 +764,23 @@ type CharCount {
max: Int
}
################################################################################
## DisableCommenting
################################################################################
type DisableCommenting {
"""
enabled when true will disable commenting site-wide.
"""
enabled: Boolean!
"""
message will be shown above the comment stream while
commenting is disabled site-wide.
"""
message: String
}
################################################################################
## Email
################################################################################
@@ -937,13 +954,7 @@ type Settings {
"""
disableCommenting will disable commenting site-wide.
"""
disableCommenting: Boolean!
"""
disableCommentingMessage will be shown above the comment stream while
commenting is disabled site-wide.
"""
disableCommentingMessage: String
disableCommenting: DisableCommenting!
"""
editCommentWindowLength is the length of time (in seconds) after a comment is
@@ -2125,6 +2136,19 @@ input SettingsCharCountInput {
max: Int
}
input SettingsDisableCommentingInput {
"""
enabled when true will disable commenting site-wide.
"""
enabled: Boolean
"""
message will be shown above the comment stream while
commenting is disabled site-wide.
"""
message: String
}
"""
SettingsInput is the partial type of the Settings type for performing mutations.
"""
@@ -2196,13 +2220,7 @@ input SettingsInput {
"""
disableCommenting will disable commenting site-wide.
"""
disableCommenting: Boolean
"""
disableCommentingMessage will be shown above the comment stream while
commenting is disabled site-wide.
"""
disableCommentingMessage: String
disableCommenting: SettingsDisableCommentingInput
"""
editCommentWindowLength is the length of time (in seconds) after a comment is
+2 -2
View File
@@ -2,6 +2,7 @@ import { Omit } from "talk-common/types";
import {
GQLAuthDisplayNameConfiguration,
GQLCharCount,
GQLDisableCommenting,
GQLEmail,
GQLExternalIntegrations,
GQLFacebookAuthIntegration,
@@ -34,8 +35,7 @@ export interface ModerationSettings {
*/
closedTimeout: number;
closedMessage?: string;
disableCommenting: boolean;
disableCommentingMessage?: string;
disableCommenting: GQLDisableCommenting;
charCount: GQLCharCount;
}
+3 -1
View File
@@ -92,7 +92,9 @@ export async function createTenant(mongo: Db, input: CreateTenantInput) {
questionBoxEnable: false,
premodLinksEnable: false,
autoCloseStream: false,
disableCommenting: false,
disableCommenting: {
enabled: false,
},
// 2 weeks timeout.
closedTimeout: 60 * 60 * 24 * 7 * 2,
+17
View File
@@ -68,6 +68,23 @@ configure-general-guidelines-explanation =
Markdown can be found <externalLink>here</externalLink>.
configure-general-guidelines-showCommunityGuidelines = Show Community Guidelines Summary
### Sitewide Commenting
configure-general-sitewideCommenting-title = Sitewide Commenting
configure-general-sitewideCommenting-explanation =
Open or close comment streams for new comments sitewide. When new comments
are turned off sitewide, new comments cannot be submitted, but existing
comments can continue to receive “Respect” reactions, be reported, and be
shared.
configure-general-sitewideCommenting-enableNewCommentsSitewide =
Enable New Comments Sitewide
configure-general-sitewideCommenting-onCommentStreamsOpened =
On - Comment streams opened for new comments
configure-general-sitewideCommenting-offCommentStreamsClosed =
Off - Comment streams closed for new comments
configure-general-sitewideCommenting-message = Sitewide Closed Comments Message
configure-general-sitewideCommenting-messageExplanation =
Write a message that will be displayed when comment streams are closed sitewide
### Closing Comment Streams
configure-general-closingCommentStreams-title = Closing Comment Streams
configure-general-closingCommentStreams-explanation = Set comment streams to close after a defined period of time after a storys publication