5.4.0 Release Bug Fixes (#2789)

* fix: addresses CORL-848

Fixed copy for new commenters feature.

* fix: address CORL-847

Revert the line hight changes on select fields for now.

* fix: addressed CORL-851

Changed copy on CSS field.

* fix: addressed CORL-840

Changed deletion window to 24 hours.
Refactored durations to use TIME enum.
This commit is contained in:
Wyatt Johnson
2020-01-14 16:39:21 +00:00
committed by GitHub
parent a88644d98e
commit 9b8ab6de5f
36 changed files with 1061 additions and 1063 deletions
+3
View File
@@ -55,4 +55,7 @@
"__generated__"
],
"debug.node.autoAttach": "on",
"search.exclude": {
"package-lock.json": true
},
}
@@ -2,7 +2,8 @@ import { Localized } from "@fluent/react/compat";
import cn from "classnames";
import React, { FunctionComponent } from "react";
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
import { reduceSeconds } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import {
Flex,
HorizontalGutter,
@@ -25,7 +26,7 @@ const RecentHistory: FunctionComponent<Props> = ({
rejectionRate,
submitted,
}) => {
const { scaled, unit } = reduceSeconds(timeFrame, [UNIT.DAYS]);
const { scaled, unit } = reduceSeconds(timeFrame, [TIME.DAY]);
return (
<HorizontalGutter spacing={2}>
@@ -30,13 +30,10 @@ const CustomCSSConfig: FunctionComponent<Props> = ({ disabled }) => (
}
>
<FormField>
<Localized
id="configure-advanced-customCSS-explanation"
strong={<strong />}
>
<Localized id="configure-advanced-customCSS-override" strong={<strong />}>
<FormFieldDescription>
URL of a CSS stylesheet that will override default Embed Stream
styles. Can be internal or external.
styles.
</FormFieldDescription>
</Localized>
<Field name="customCSSURL" parse={parseEmptyAsNull} format={formatEmpty}>
@@ -80,9 +80,9 @@ const ClosingCommentStreamsConfig: FunctionComponent<Props> = ({
<DurationField
{...input}
units={[
DURATION_UNIT.HOURS,
DURATION_UNIT.DAYS,
DURATION_UNIT.WEEKS,
DURATION_UNIT.HOUR,
DURATION_UNIT.DAY,
DURATION_UNIT.WEEK,
]}
disabled={disabled}
color={colorFromMeta(meta)}
@@ -67,9 +67,9 @@ const CommentEditingConfig: FunctionComponent<Props> = ({ disabled }) => (
<DurationField
{...input}
units={[
DURATION_UNIT.SECONDS,
DURATION_UNIT.MINUTES,
DURATION_UNIT.HOURS,
DURATION_UNIT.SECOND,
DURATION_UNIT.MINUTE,
DURATION_UNIT.HOUR,
]}
color={colorFromMeta(meta)}
disabled={disabled}
@@ -60,7 +60,7 @@ const NewCommentersConfig: FunctionComponent<Props> = ({ disabled }) => {
</FormField>
<FormField>
<Localized id="configure-moderation-newCommenters-approvedCommentsThreshold">
<Label>Number of first comments sent for approval</Label>
<Label>Number of comments that must be approved</Label>
</Localized>
<Field
name="newCommenters.approvedCommentsThreshold"
@@ -77,7 +77,7 @@ const RecentCommentHistoryConfig: FunctionComponent<Props> = ({ disabled }) => {
{({ input, meta }) => (
<>
<DurationField
units={[DURATION_UNIT.DAYS]}
units={[DURATION_UNIT.DAY]}
disabled={disabled}
color={hasError(meta) ? "error" : "regular"}
{...input}
@@ -234,7 +234,7 @@ each of your sites stories.
<p
className="FormFieldDescription-root"
>
URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.
URL of a CSS stylesheet that will override default Embed Stream styles.
</p>
<div
className="Box-root HorizontalGutter-root HorizontalGutter-spacing-2"
@@ -367,7 +367,7 @@ for moderator approval before publication.
<label
className="Label-root"
>
Number of first comments sent for approval
Number of comments that must be approved
</label>
<div
className="TextField-root"
+5 -5
View File
@@ -1,7 +1,8 @@
import {
DEFAULT_SESSION_LENGTH,
DEFAULT_SESSION_DURATION,
TOXICITY_THRESHOLD_DEFAULT,
} from "coral-common/constants";
import TIME from "coral-common/time";
import { pureMerge } from "coral-common/utils";
import {
GQLComment,
@@ -77,8 +78,7 @@ export const settings = createFixture<GQLSettings>({
},
recentCommentHistory: {
enabled: false,
// 7 days in seconds.
timeFrame: 604800,
timeFrame: 7 * TIME.DAY,
// Rejection rate defaulting to 30%, once exceeded, comments will be
// pre-moderated.
triggerRejectionRate: 0.3,
@@ -94,7 +94,7 @@ export const settings = createFixture<GQLSettings>({
},
},
auth: {
sessionDuration: DEFAULT_SESSION_LENGTH,
sessionDuration: DEFAULT_SESSION_DURATION,
integrations: {
local: {
enabled: true,
@@ -175,7 +175,7 @@ export const settingsWithEmptyAuth = createFixture<GQLSettings>(
{
id: "settings",
auth: {
sessionDuration: DEFAULT_SESSION_LENGTH,
sessionDuration: DEFAULT_SESSION_DURATION,
integrations: {
local: {
enabled: true,
@@ -24,7 +24,7 @@ it("renders correctly with specified units", () => {
value: "",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -37,7 +37,7 @@ it("use best matching unit", () => {
value: "3600",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -50,7 +50,7 @@ it("use initial unit if 0", () => {
value: "0",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -63,7 +63,7 @@ it("accepts invalid input", () => {
value: "this is so invalid",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -8,7 +8,7 @@ import React, {
useState,
} from "react";
import { UNIT } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import {
Flex,
Option,
@@ -23,14 +23,14 @@ import styles from "./DurationField.css";
* DURATION_UNIT are units that can be used in the
* DurationField components.
*/
export const DURATION_UNIT = UNIT;
export const DURATION_UNIT = TIME;
const DURATION_UNIT_MAP = {
[DURATION_UNIT.SECONDS]: "second",
[DURATION_UNIT.MINUTES]: "minute",
[DURATION_UNIT.HOURS]: "hour",
[DURATION_UNIT.DAYS]: "day",
[DURATION_UNIT.WEEKS]: "week",
[DURATION_UNIT.SECOND]: "second",
[DURATION_UNIT.MINUTE]: "minute",
[DURATION_UNIT.HOUR]: "hour",
[DURATION_UNIT.DAY]: "day",
[DURATION_UNIT.WEEK]: "week",
};
interface Props {
@@ -39,18 +39,18 @@ interface Props {
disabled: boolean;
onChange: (v: string) => void;
/** Specifiy units to include */
units?: ReadonlyArray<UNIT>;
units?: ReadonlyArray<TIME>;
}
function convertToSeconds(value: string, unit?: UNIT) {
function convertToSeconds(value: string, unit?: TIME) {
const parsed = parseInt(value, 10);
return (isNaN(parsed) || !unit ? value : parsed * unit).toString();
}
function convertFromSeconds(
value: string,
units: ReadonlyArray<UNIT>,
unit?: UNIT
units: ReadonlyArray<TIME>,
unit?: TIME
) {
const parsed = parseInt(value, 10);
@@ -82,7 +82,7 @@ function convertFromSeconds(
*/
const DurationField: FunctionComponent<Props> = ({
value,
units = [UNIT.HOURS, UNIT.DAYS, UNIT.WEEKS],
units = [TIME.HOUR, TIME.DAY, TIME.WEEK],
onChange,
disabled,
name,
@@ -7,6 +7,8 @@ import {
urlMiddleware,
} from "react-relay-network-modern/es";
import TIME from "coral-common/time";
import clientIDMiddleware from "./clientIDMiddleware";
import { ManagedSubscriptionClient } from "./createManagedSubscriptionClient";
import customErrorMiddleware from "./customErrorMiddleware";
@@ -45,7 +47,7 @@ export default function createNetwork(
customErrorMiddleware,
cacheMiddleware({
size: 100, // max 100 requests
ttl: 900000, // 15 minutes
ttl: 15 * TIME.MINUTE,
clearOnMutation: true,
}),
urlMiddleware({
@@ -2,7 +2,7 @@ import { Localized } from "@fluent/react/compat";
import React, { FunctionComponent, useCallback } from "react";
import { graphql } from "react-relay";
import { SCHEDULED_DELETION_TIMESPAN_DAYS } from "coral-common/constants";
import { SCHEDULED_DELETION_WINDOW_DURATION } from "coral-common/constants";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
import CLASSES from "coral-stream/classes";
@@ -24,8 +24,8 @@ interface Props {
viewer: StreamDeletionRequestCalloutContainer_viewer;
}
const formatter = (locales: string[], date: Date) => {
return Intl.DateTimeFormat(locales, {
const formatter = (locales: string[], date: Date) =>
Intl.DateTimeFormat(locales, {
year: "numeric",
month: "numeric",
day: "numeric",
@@ -33,11 +33,9 @@ const formatter = (locales: string[], date: Date) => {
minute: "numeric",
second: "numeric",
}).format(date);
};
const subtractDays = (date: Date, days: number) => {
const millisecondsInADay = 86400000;
return new Date(date.getTime() - days * millisecondsInADay);
const subtractSeconds = (date: Date, seconds: number) => {
return new Date(date.getTime() - seconds * 1000);
};
const StreamDeletionRequestCalloutContainer: FunctionComponent<Props> = ({
@@ -57,9 +55,9 @@ const StreamDeletionRequestCalloutContainer: FunctionComponent<Props> = ({
const requestDate = viewer.scheduledDeletionDate
? formatter(
locales,
subtractDays(
subtractSeconds(
new Date(viewer.scheduledDeletionDate),
SCHEDULED_DELETION_TIMESPAN_DAYS
SCHEDULED_DELETION_WINDOW_DURATION
)
)
: null;
@@ -9,8 +9,9 @@ import React, {
} from "react";
import { Field, Form } from "react-final-form";
import { ALLOWED_USERNAME_CHANGE_FREQUENCY } from "coral-common/constants";
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
import { ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION } from "coral-common/constants";
import { reduceSeconds } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import getAuthenticationIntegrations from "coral-framework/helpers/getAuthenticationIntegrations";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { InvalidRequestError } from "coral-framework/lib/errors";
@@ -50,9 +51,10 @@ import UpdateUsernameMutation from "./UpdateUsernameMutation";
import styles from "./ChangeUsernameContainer.css";
const FREQUENCYSCALED = reduceSeconds(ALLOWED_USERNAME_CHANGE_FREQUENCY, [
UNIT.DAYS,
]);
const FREQUENCYSCALED = reduceSeconds(
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION,
[TIME.DAY]
);
interface Props {
viewer: ViewerData;
@@ -107,7 +109,8 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({
if (username && username.history.length > 1) {
const lastUsernameEditAllowed = new Date();
lastUsernameEditAllowed.setSeconds(
lastUsernameEditAllowed.getSeconds() - ALLOWED_USERNAME_CHANGE_FREQUENCY
lastUsernameEditAllowed.getSeconds() -
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION
);
const lastUsernameEdit =
username.history[username.history.length - 1].createdAt;
@@ -122,7 +125,9 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({
const date = new Date(
username.history[username.history.length - 1].createdAt
);
date.setSeconds(date.getSeconds() + ALLOWED_USERNAME_CHANGE_FREQUENCY);
date.setSeconds(
date.getSeconds() + ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION
);
return date;
}
return null;
@@ -1,8 +1,7 @@
import { DateTime } from "luxon";
import { graphql } from "react-relay";
import { Environment } from "relay-runtime";
import { SCHEDULED_DELETION_TIMESPAN_DAYS } from "coral-common/constants";
import { SCHEDULED_DELETION_WINDOW_DURATION } from "coral-common/constants";
import { getViewer } from "coral-framework/helpers";
import {
commitMutationPromiseNormalized,
@@ -49,9 +48,9 @@ const RequestAccountDeletionMutation = createMutation(
},
optimisticUpdater: store => {
const viewer = getViewer(environment)!;
const deletionDate = DateTime.fromJSDate(new Date())
.plus({ days: SCHEDULED_DELETION_TIMESPAN_DAYS })
.toISO();
const deletionDate = new Date(
Date.now() + SCHEDULED_DELETION_WINDOW_DURATION * 1000
).toISOString();
const viewerProxy = store.get(viewer.id);
if (viewerProxy !== null) {
viewerProxy.setValue(deletionDate, "scheduledDeletionDate");
@@ -3,8 +3,9 @@ import cn from "classnames";
import React, { FunctionComponent, useCallback } from "react";
import { graphql } from "react-relay";
import { DOWNLOAD_LIMIT_TIMEFRAME } from "coral-common/constants";
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
import { DOWNLOAD_LIMIT_TIMEFRAME_DURATION } from "coral-common/constants";
import { reduceSeconds } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
import CLASSES from "coral-stream/classes";
@@ -34,8 +35,8 @@ const DownloadCommentsContainer: FunctionComponent<Props> = ({ viewer }) => {
? Math.ceil((Date.now() - lastDownloadedAt.getTime()) / 1000)
: 0;
const canDownload =
!lastDownloadedAt || sinceLastDownload >= DOWNLOAD_LIMIT_TIMEFRAME;
const tilCanDownload = DOWNLOAD_LIMIT_TIMEFRAME - sinceLastDownload;
!lastDownloadedAt || sinceLastDownload >= DOWNLOAD_LIMIT_TIMEFRAME_DURATION;
const tilCanDownload = DOWNLOAD_LIMIT_TIMEFRAME_DURATION - sinceLastDownload;
const formatter = new Intl.DateTimeFormat(locales, {
day: "2-digit",
month: "2-digit",
@@ -46,9 +47,9 @@ const DownloadCommentsContainer: FunctionComponent<Props> = ({ viewer }) => {
timeZoneName: "short",
});
const { scaled, unit } = reduceSeconds(tilCanDownload, [
UNIT.DAYS,
UNIT.HOURS,
UNIT.MINUTES,
TIME.DAY,
TIME.HOUR,
TIME.MINUTE,
]);
return (
@@ -24,7 +24,7 @@ it("renders correctly with specified units", () => {
value: "",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -37,7 +37,7 @@ it("use best matching unit", () => {
value: "3600",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -50,7 +50,7 @@ it("use initial unit if 0", () => {
value: "0",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -63,7 +63,7 @@ it("accepts invalid input", () => {
value: "this is so invalid",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -8,7 +8,7 @@ import React, {
useState,
} from "react";
import { UNIT } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import { Flex } from "coral-ui/components";
import { Option, SelectField } from "../SelectField";
@@ -20,32 +20,32 @@ import styles from "./DurationField.css";
* DURATION_UNIT are units that can be used in the
* DurationField components.
*/
export const DURATION_UNIT = UNIT;
export const DURATION_UNIT = TIME;
const DURATION_UNIT_MAP = {
[DURATION_UNIT.SECONDS]: "second",
[DURATION_UNIT.MINUTES]: "minute",
[DURATION_UNIT.HOURS]: "hour",
[DURATION_UNIT.DAYS]: "day",
[DURATION_UNIT.WEEKS]: "week",
[DURATION_UNIT.SECOND]: "second",
[DURATION_UNIT.MINUTE]: "minute",
[DURATION_UNIT.HOUR]: "hour",
[DURATION_UNIT.DAY]: "day",
[DURATION_UNIT.WEEK]: "week",
};
interface Props extends Pick<TextFieldProps, "color" | "name" | "disabled"> {
value: string;
onChange: (v: string) => void;
/** Specifiy units to include */
units?: ReadonlyArray<UNIT>;
units?: ReadonlyArray<TIME>;
}
function convertToSeconds(value: string, unit?: UNIT) {
function convertToSeconds(value: string, unit?: TIME) {
const parsed = parseInt(value, 10);
return (isNaN(parsed) || !unit ? value : parsed * unit).toString();
}
function convertFromSeconds(
value: string,
units: ReadonlyArray<UNIT>,
unit?: UNIT
units: ReadonlyArray<TIME>,
unit?: TIME
) {
const parsed = parseInt(value, 10);
@@ -77,7 +77,7 @@ function convertFromSeconds(
*/
const DurationField: FunctionComponent<Props> = ({
value,
units = [UNIT.HOURS, UNIT.DAYS, UNIT.WEEKS],
units = [TIME.HOUR, TIME.DAY, TIME.WEEK],
onChange,
disabled,
name,
@@ -25,7 +25,7 @@
font-family: var(--v2-font-family-primary);
font-weight: var(--v2-font-weight-primary-regular);
font-size: var(--v2-font-size-3);
line-height: var(--v2-line-height-tall);
line-height: var(--v2-line-height-reset);
appearance: none;
outline: none;
color: var(--v2-palette-input-value);
-1
View File
@@ -277,7 +277,6 @@ const variables2 = {
bodyShort: 1.3,
reset: 1,
title: 1.15,
tall: 1.3,
},
fontSize: {
1: "0.75rem",
+18 -14
View File
@@ -1,3 +1,5 @@
import TIME from "./time";
/**
* CLIENT_ID_HEADER references the name of the header used to extract/send the
* client ID to enable automatic de-duplication.
@@ -36,30 +38,32 @@ export const TOXICITY_ENDPOINT_DEFAULT =
"https://commentanalyzer.googleapis.com/v1alpha1";
/**
* DOWNLOAD_LIMIT_TIMEFRAME is the number of seconds that a given download may
* be made within.
* DOWNLOAD_LIMIT_TIMEFRAME_DURATION is the number of seconds that a given
* download may be made within.
*/
export const DOWNLOAD_LIMIT_TIMEFRAME = 14 * 86400;
export const DOWNLOAD_LIMIT_TIMEFRAME_DURATION = 14 * TIME.DAY;
/**
* ALLOWED_USERNAME_CHANGE_FREQUENCY is the length of time in seconds a user must wait after changing their username to change it again.
* ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION is the length of time in seconds
* a user must wait after changing their username to change it again.
*/
export const ALLOWED_USERNAME_CHANGE_FREQUENCY = 14 * 86400;
export const ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION = 14 * TIME.DAY;
/**
* SCHEDULED_DELETION_TIMESPAN_DAYS is the length of time in days a user
* will have to wait for their account to be deleted after requesting a
* deletion.
* SCHEDULED_DELETION_TIMEFRAME is the length of time in seconds a user will
* have to wait for their account to be deleted after requesting a deletion.
*/
export const SCHEDULED_DELETION_TIMESPAN_DAYS = 14;
export const SCHEDULED_DELETION_WINDOW_DURATION = 1 * TIME.DAY;
/**
* DEFAULT_SESSION_LENTTH is the length of time in seconds a session is valid for unless configured in tenant.
* DEFAULT_SESSION_DURATION is the length of time in seconds a session is valid
* for unless configured in tenant.
*/
export const DEFAULT_SESSION_LENGTH = 7776000;
export const DEFAULT_SESSION_DURATION = 90 * TIME.DAY;
/**
* COMMENT_REPEAT_POST_TIMESPAN is the length of time in seconds that a previous comment ID is stored for a
* user to prevent them from posting the same comment repeatedly.
* COMMENT_REPEAT_POST_DURATION is the length of time in seconds that a
* previous comment ID is stored for a user to prevent them from posting the
* same comment repeatedly.
*/
export const COMMENT_REPEAT_POST_TIMESPAN = 21600;
export const COMMENT_REPEAT_POST_DURATION = 6 * TIME.MINUTE;
+13 -23
View File
@@ -1,29 +1,19 @@
/**
* UNIT are units that can be used in the
* DurationField components.
*/
export enum UNIT {
SECONDS = 1,
MINUTES = 60,
HOURS = 3600,
DAYS = 86400,
WEEKS = 604800,
}
import TIME from "coral-common/time";
export const UNIT_MAP = {
[UNIT.SECONDS]: "second",
[UNIT.MINUTES]: "minute",
[UNIT.HOURS]: "hour",
[UNIT.DAYS]: "day",
[UNIT.WEEKS]: "week",
[TIME.SECOND]: "second",
[TIME.MINUTE]: "minute",
[TIME.HOUR]: "hour",
[TIME.DAY]: "day",
[TIME.WEEK]: "week",
};
export const DEFAULT_UNITS = [
UNIT.WEEKS,
UNIT.DAYS,
UNIT.HOURS,
UNIT.MINUTES,
UNIT.SECONDS,
TIME.WEEK,
TIME.DAY,
TIME.HOUR,
TIME.MINUTE,
TIME.SECOND,
];
type ValueOf<T> = T[keyof T];
@@ -37,11 +27,11 @@ export interface ScaledUnit {
export default function reduceSeconds(
value: number,
units: UNIT[] = DEFAULT_UNITS
units: TIME[] = DEFAULT_UNITS
): ScaledUnit {
// Find the largest match for the smallest number.
const unit: keyof typeof UNIT_MAP =
units.find(compare => value >= compare) || UNIT.SECONDS;
units.find(compare => value >= compare) || TIME.SECOND;
// Scale the value to the unit.
const scaled = Math.round((value / unit) * 100) / 100;
+12
View File
@@ -0,0 +1,12 @@
/**
* TIME represends various constants for second representations of times.
*/
enum TIME {
SECOND = 1,
MINUTE = 60 * TIME.SECOND,
HOUR = 60 * TIME.MINUTE,
DAY = 24 * TIME.HOUR,
WEEK = 7 * TIME.DAY,
}
export default TIME;
+9 -6
View File
@@ -5,13 +5,15 @@ import { MongoError } from "mongodb";
import uuid from "uuid";
import { VError } from "verror";
import { ALLOWED_USERNAME_CHANGE_FREQUENCY } from "coral-common/constants";
import { ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION } from "coral-common/constants";
import { ERROR_CODES, ERROR_TYPES } from "coral-common/errors";
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
import { reduceSeconds } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import { Writable } from "coral-common/types";
import { translate } from "coral-server/services/i18n";
import { Writable } from "coral-common/types";
import { GQLUSER_AUTH_CONDITIONS } from "coral-server/graph/schema/__generated__/types";
import { ERROR_TRANSLATIONS } from "./translations";
/**
@@ -315,9 +317,10 @@ export class UsernameAlreadySetError extends CoralError {
export class UsernameUpdatedWithinWindowError extends CoralError {
constructor(lastUpdate: Date) {
const { scaled, unit } = reduceSeconds(ALLOWED_USERNAME_CHANGE_FREQUENCY, [
UNIT.DAYS,
]);
const { scaled, unit } = reduceSeconds(
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION,
[TIME.DAY]
);
super({
code: ERROR_CODES.USERNAME_UPDATED_WITHIN_WINDOW,
context: {
+3 -7
View File
@@ -45,13 +45,9 @@ export function getStoryClosedAt(
if (tenant.closeCommenting.auto) {
// Auto-close stream has been enabled, convert the createdAt time into the
// closedAt time by adding the closedTimeout.
return (
DateTime.fromJSDate(story.createdAt)
// closedTimeout is in seconds, so multiply by 1000 to get
// milliseconds.
.plus(tenant.closeCommenting.timeout * 1000)
.toJSDate()
);
return DateTime.fromJSDate(story.createdAt)
.plus({ seconds: tenant.closeCommenting.timeout })
.toJSDate();
}
return;
+6 -10
View File
@@ -2,8 +2,9 @@ import { isEmpty } from "lodash";
import { Db } from "mongodb";
import uuid from "uuid";
import { DEFAULT_SESSION_LENGTH } from "coral-common/constants";
import { DEFAULT_SESSION_DURATION } from "coral-common/constants";
import { LanguageCode } from "coral-common/helpers/i18n/locales";
import TIME from "coral-common/time";
import { DeepPartial, Omit, Sub } from "coral-common/types";
import { dotize } from "coral-common/utils/dotize";
import { Settings } from "coral-server/models/settings";
@@ -99,16 +100,12 @@ export async function createTenant(
premodLinksEnable: false,
closeCommenting: {
auto: false,
// 2 weeks timeout.
timeout: 60 * 60 * 24 * 7 * 2,
timeout: 2 * TIME.WEEK,
},
disableCommenting: {
enabled: false,
},
// 30 seconds edit window length.
editCommentWindowLength: 30,
editCommentWindowLength: 30 * TIME.SECOND,
charCount: {
enabled: false,
},
@@ -117,7 +114,7 @@ export async function createTenant(
banned: [],
},
auth: {
sessionDuration: DEFAULT_SESSION_LENGTH,
sessionDuration: DEFAULT_SESSION_DURATION,
integrations: {
local: {
enabled: true,
@@ -169,8 +166,7 @@ export async function createTenant(
},
recentCommentHistory: {
enabled: false,
// 7 days in seconds.
timeFrame: 604800,
timeFrame: 7 * TIME.DAY,
// Rejection rate defaulting to 30%, once exceeded, comments will be
// pre-moderated.
triggerRejectionRate: 0.3,
+3 -3
View File
@@ -1,6 +1,7 @@
import Queue, { Job, Queue as QueueType } from "bull";
import Logger from "bunyan";
import TIME from "coral-common/time";
import logger from "coral-server/logger";
export interface TaskOptions<T, U = any> {
@@ -31,11 +32,10 @@ export default class Task<T, U = any> {
// with completed entries if we don't need to.
removeOnComplete: true,
// By default, configure jobs to use an exponential backoff
// strategy starting at a 10 second delay.
// By default, configure jobs to use an exponential backoff strategy.
backoff: {
type: "exponential",
delay: 10000,
delay: 10 * TIME.SECOND,
},
// Be default, try all jobs at least 5 times.
@@ -23,13 +23,9 @@ export function getCommentEditableUntilDate(
tenant: Pick<Tenant, "editCommentWindowLength">,
createdAt: Date
): Date {
return (
DateTime.fromJSDate(createdAt)
// editCommentWindowLength is in seconds, so multiply by 1000 to get
// milliseconds.
.plus(tenant.editCommentWindowLength * 1000)
.toJSDate()
);
return DateTime.fromJSDate(createdAt)
.plus({ seconds: tenant.editCommentWindowLength })
.toJSDate();
}
export async function addTag(
+2 -2
View File
@@ -7,7 +7,7 @@ import { DateTime } from "luxon";
import { Bearer, BearerOptions } from "permit";
import uuid from "uuid/v4";
import { DEFAULT_SESSION_LENGTH } from "coral-common/constants";
import { DEFAULT_SESSION_DURATION } from "coral-common/constants";
import { Omit } from "coral-common/types";
import {
AuthenticationError,
@@ -262,7 +262,7 @@ export const signTokenString = async (
secret,
{
jwtid: uuid(),
expiresIn: DEFAULT_SESSION_LENGTH,
expiresIn: DEFAULT_SESSION_DURATION,
...options,
issuer: tenant.id,
subject: user.id,
+8 -8
View File
@@ -2,11 +2,11 @@ import { DateTime } from "luxon";
import { Db } from "mongodb";
import {
ALLOWED_USERNAME_CHANGE_FREQUENCY,
COMMENT_REPEAT_POST_TIMESPAN,
DOWNLOAD_LIMIT_TIMEFRAME,
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION,
COMMENT_REPEAT_POST_DURATION,
DOWNLOAD_LIMIT_TIMEFRAME_DURATION,
} from "coral-common/constants";
import { SCHEDULED_DELETION_TIMESPAN_DAYS } from "coral-common/constants";
import { SCHEDULED_DELETION_WINDOW_DURATION } from "coral-common/constants";
import { Config } from "coral-server/config";
import {
DuplicateEmailError,
@@ -369,7 +369,7 @@ export async function requestAccountDeletion(
}
const deletionDate = DateTime.fromJSDate(now).plus({
days: SCHEDULED_DELETION_TIMESPAN_DAYS,
seconds: SCHEDULED_DELETION_WINDOW_DURATION,
});
const updatedUser = await scheduleDeletionDate(
@@ -528,7 +528,7 @@ export async function updateUsername(
// Get the earliest date that the username could have been edited before to/
// allow it now.
const lastUsernameEditAllowed = DateTime.fromJSDate(now)
.plus({ seconds: -ALLOWED_USERNAME_CHANGE_FREQUENCY })
.plus({ seconds: -ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION })
.toJSDate();
const { history } = user.status.username;
@@ -1118,7 +1118,7 @@ export async function requestCommentsDownload(
if (
user.lastDownloadedAt &&
DateTime.fromJSDate(user.lastDownloadedAt)
.plus({ seconds: DOWNLOAD_LIMIT_TIMEFRAME })
.plus({ seconds: DOWNLOAD_LIMIT_TIMEFRAME_DURATION })
.toSeconds() >= DateTime.fromJSDate(now).toSeconds()
) {
throw new Error("requested download too early");
@@ -1212,7 +1212,7 @@ export async function updateUserLastCommentID(
) {
const key = userLastCommentIDKey(tenant, user);
await redis.set(key, commentID, "EX", COMMENT_REPEAT_POST_TIMESPAN);
await redis.set(key, commentID, "EX", COMMENT_REPEAT_POST_DURATION);
}
/**
+3 -7
View File
@@ -42,13 +42,9 @@ function getLastCommentEditableUntilDate(
tenant: Pick<Tenant, "editCommentWindowLength">,
now = new Date()
): Date {
return (
DateTime.fromJSDate(now)
// editCommentWindowLength is in seconds, so multiply by 1000 to get
// milliseconds.
.minus(tenant.editCommentWindowLength * 1000)
.toJSDate()
);
return DateTime.fromJSDate(now)
.minus({ seconds: tenant.editCommentWindowLength })
.toJSDate();
}
export type EditComment = Omit<
+2 -2
View File
@@ -307,8 +307,8 @@ configure-wordList-suspect-wordList = Mistænkte ordliste
### Advanced
configure-advanced-customCSS = Tilpasset CSS
configure-advanced-customCSS-explanation =
URL til et CSS-stilark, der tilsidesætter standardindlejring af streams. Kan være intern eller ekstern.
configure-advanced-customCSS-override =
URL til et CSS-stilark, der tilsidesætter standardindlejring af streams.
configure-advanced-permittedDomains = Tilladte domæner
configure-advanced-liveUpdates = Kommentar Stream Live-opdateringer
+3 -3
View File
@@ -334,7 +334,7 @@ configure-moderation-newCommenters-description =
When this is active, initial comments by a new commenter will be sent to Pending
for moderator approval before publication.
configure-moderation-newCommenters-enable-description = Enable pre-moderation for new commenters
configure-moderation-newCommenters-approvedCommentsThreshold = Number of first comments sent for approval
configure-moderation-newCommenters-approvedCommentsThreshold = Number of comments that must be approved
configure-moderation-newCommenters-approvedCommentsThreshold-description =
The number of comments a user must have approved before they do
not have to be premoderated
@@ -361,8 +361,8 @@ configure-wordList-suspect-wordListDetailInstructions =
### Advanced
configure-advanced-customCSS = Custom CSS
configure-advanced-customCSS-explanation =
URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.
configure-advanced-customCSS-override =
URL of a CSS stylesheet that will override default Embed Stream styles.
configure-advanced-permittedDomains = Permitted domains
configure-advanced-permittedDomains-description =
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -343,8 +343,8 @@ configure-wordList-suspect-wordListDetailInstructions =
### Advanced
configure-advanced-customCSS = CSS Customizado
configure-advanced-customCSS-explanation =
URL de uma folha de estilo CSS que substituirá o estilo padrão dos fluxos de comentário das páginas. Pode ser interno ou externo.
configure-advanced-customCSS-override =
URL de uma folha de estilo CSS que substituirá o estilo padrão dos fluxos de comentário das páginas.
configure-advanced-permittedDomains = Domínios Permitidos
configure-advanced-permittedDomains-description =
@@ -366,7 +366,7 @@ configure-advanced-embedCode-comment =
Descomente estas linhas e substitua com o ID da
história e a URL do seu CMS
Substitua essas linhas pelo ID do ID e URL da história do seu CMS para fornecer a maior integração.
Consulte a nossa documentação em https://docs.coralproject.net para todas as
Consulte a nossa documentação em https://docs.coralproject.net para todas as
opções de configuração.
## Decision History
@@ -374,7 +374,7 @@ decisionHistory-popover =
.description = Uma caixa de diálogo mostrando o histórico de decisões
decisionHistory-youWillSeeAList =
Você verá uma lista de suas ações de moderação de postagens aqui.
decisionHistory-showMoreButton =
decisionHistory-showMoreButton =
Mostrar mais
decisionHistory-yourDecisionHistory = Seu Histórico de Decisão
decisionHistory-rejectedCommentBy = Comentário Rejeitado por <username></username>
@@ -386,7 +386,7 @@ decisionHistory-goToComment = Ir para o comentário
configure-slack-header-title = Integração com o Slack
configure-slack-description =
Encia automaticamente os comentários da fila de moderação do Coral para canais do Slack.
Você precisa de acesso admin do slack para realizar esta configuração. Para as etapas de
Você precisa de acesso admin do slack para realizar esta configuração. Para as etapas de
como criar uma app no Slack veja nossa <externalLink>documentação</externalLink>.
configure-slack-addChannel = Adicionar Canal
@@ -686,8 +686,8 @@ community-banModal-banUser = Banir Usuário
community-banModal-customize = Customizar mensagem de e-mail de banimento
community-suspendModal-areYouSure = Banir <strong>{ $username }</strong>?
community-suspendModal-consequence =
Uma vez banido, este usuário não poderá mais comentar, reagir
community-suspendModal-consequence =
Uma vez banido, este usuário não poderá mais comentar, reagir
ou reportar comentários
community-suspendModal-duration-3600 = 1 hora
community-suspendModal-duration-10800 = 3 horas
@@ -837,7 +837,7 @@ configure-account-features-no = Não
configure-account-features-download-comments = Fazer o download de seus comentários
configure-account-features-download-comments-details = Comentaristas podem fazer download de um csv do histórico de comentarista
configure-account-features-delete-account = Excluir suas contas.
configure-account-features-delete-account-details =
configure-account-features-delete-account-details =
Remover todos os dados de comentários, nome de usuário e endereço de email do site e do banco de dados
configure-account-features-delete-account-fieldDescriptions =