mirror of
https://github.com/wassname/talk.git
synced 2026-07-26 13:37:38 +08:00
[CORL 547] org-wide announcements (#2813)
* CRUD announcements * only show announcement if not disabled * make announcements dismissable * add announcement mutations * update announcement form logic * style announcements on stream * update snap * localize strings * close form if announcement is removed * move announcement config below sitewide commenting config * move date calculation inside useMemo * move announcementconfig code to announcementconfigcontainer * use coralContext for localStorage * fix type of announcement createdAt * move announcement form to modal * remove payload pruning from configure route * simplify announcement display logic * make validation message full width Co-authored-by: Kim Gardner <kgardnr@gmail.com>
This commit is contained in:
co-authored by
Kim Gardner
parent
a7b2af85fc
commit
a1a8652f7e
@@ -1,6 +1,8 @@
|
||||
import GraphContext from "coral-server/graph/context";
|
||||
import { Tenant } from "coral-server/models/tenant";
|
||||
import {
|
||||
createAnnouncement,
|
||||
deleteAnnouncement,
|
||||
disableFeatureFlag,
|
||||
enableFeatureFlag,
|
||||
regenerateSSOKey,
|
||||
@@ -8,6 +10,7 @@ import {
|
||||
} from "coral-server/services/tenant";
|
||||
|
||||
import {
|
||||
GQLCreateAnnouncementInput,
|
||||
GQLFEATURE_FLAG,
|
||||
GQLUpdateSettingsInput,
|
||||
} from "coral-server/graph/schema/__generated__/types";
|
||||
@@ -28,4 +31,8 @@ export const Settings = ({
|
||||
enableFeatureFlag(mongo, redis, tenantCache, tenant, flag),
|
||||
disableFeatureFlag: (flag: GQLFEATURE_FLAG) =>
|
||||
disableFeatureFlag(mongo, redis, tenantCache, tenant, flag),
|
||||
createAnnouncement: (input: GQLCreateAnnouncementInput) =>
|
||||
createAnnouncement(mongo, redis, tenantCache, tenant, input, now),
|
||||
deleteAnnouncement: () =>
|
||||
deleteAnnouncement(mongo, redis, tenantCache, tenant),
|
||||
});
|
||||
|
||||
@@ -237,4 +237,12 @@ export const Mutation: Required<GQLMutationTypeResolver<void>> = {
|
||||
flags: await ctx.mutators.Settings.disableFeatureFlag(input.flag),
|
||||
clientMutationId: input.clientMutationId,
|
||||
}),
|
||||
createAnnouncement: async (source, { input }, ctx) => ({
|
||||
settings: await ctx.mutators.Settings.createAnnouncement(input),
|
||||
clientMutationId: input.clientMutationId,
|
||||
}),
|
||||
deleteAnnouncement: async (source, { input }, ctx) => ({
|
||||
settings: await ctx.mutators.Settings.deleteAnnouncement(),
|
||||
clientMutationId: input.clientMutationId,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Tenant } from "coral-server/models/tenant";
|
||||
import {
|
||||
retrieveAnnouncementIfEnabled,
|
||||
Tenant,
|
||||
} from "coral-server/models/tenant";
|
||||
|
||||
import {
|
||||
GQLFEATURE_FLAG,
|
||||
@@ -18,4 +21,6 @@ export const Settings: GQLSettingsTypeResolver<Tenant> = {
|
||||
slack: ({ slack = {} }) => slack,
|
||||
featureFlags: ({ featureFlags = [] }) =>
|
||||
featureFlags.filter(filterValidFeatureFlags()),
|
||||
announcement: ({ announcement }) =>
|
||||
retrieveAnnouncementIfEnabled(announcement),
|
||||
};
|
||||
|
||||
@@ -1215,6 +1215,37 @@ type NewCommentersConfiguration {
|
||||
approvedCommentsThreshold: Int!
|
||||
}
|
||||
|
||||
"""
|
||||
Announcement is an organization-wide announcement displayed above the stream
|
||||
for a set duration
|
||||
"""
|
||||
type Announcement {
|
||||
"""
|
||||
id is a canonical identifier for this Annoucnement, used to dismiss on front-end.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
createdAt is the creation date.
|
||||
"""
|
||||
createdAt: Time!
|
||||
|
||||
"""
|
||||
disableAt is the computed date at which announcement will be invalid.
|
||||
"""
|
||||
disableAt: Time!
|
||||
|
||||
"""
|
||||
duration determines how long the announcement will be valid for.
|
||||
"""
|
||||
duration: Int!
|
||||
|
||||
"""
|
||||
content is the content displayed for the announcement.
|
||||
"""
|
||||
content: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Settings stores the global settings for a given Tenant.
|
||||
"""
|
||||
@@ -1365,6 +1396,8 @@ type Settings {
|
||||
newCommenters is the configuration for how new commenters comments are treated.
|
||||
"""
|
||||
newCommenters: NewCommentersConfiguration! @auth(roles: [ADMIN])
|
||||
|
||||
announcement: Announcement
|
||||
}
|
||||
|
||||
################################################################################
|
||||
@@ -3353,6 +3386,44 @@ input RecentCommentHistoryConfigurationInput {
|
||||
triggerRejectionRate: Float
|
||||
}
|
||||
|
||||
input CreateAnnouncementInput {
|
||||
"""
|
||||
content of the announcement.
|
||||
"""
|
||||
content: String!
|
||||
|
||||
duration: Int!
|
||||
"""
|
||||
clientMutationId is required for Relay support.
|
||||
"""
|
||||
clientMutationId: String!
|
||||
}
|
||||
|
||||
input DeleteAnnouncementInput {
|
||||
"""
|
||||
clientMutationId is required for Relay support.
|
||||
"""
|
||||
clientMutationId: String!
|
||||
}
|
||||
|
||||
type CreateAnnouncementPayload {
|
||||
"""
|
||||
clientMutationId is required for Relay support.
|
||||
"""
|
||||
clientMutationId: String!
|
||||
|
||||
settings: Settings!
|
||||
}
|
||||
|
||||
type DeleteAnnouncementPayload {
|
||||
"""
|
||||
clientMutationId is required for Relay support.
|
||||
"""
|
||||
clientMutationId: String!
|
||||
|
||||
settings: Settings!
|
||||
}
|
||||
|
||||
input SettingsCommunityGuidelinesInput {
|
||||
"""
|
||||
enable set to true will show the guidelines above the message box.
|
||||
@@ -5763,6 +5834,14 @@ type Mutation {
|
||||
disableFeatureFlag(
|
||||
input: DisableFeatureFlagInput!
|
||||
): DisableFeatureFlagPayload! @auth(roles: [ADMIN])
|
||||
|
||||
createAnnouncement(
|
||||
input: CreateAnnouncementInput!
|
||||
): CreateAnnouncementPayload! @auth(roles: [ADMIN])
|
||||
|
||||
deleteAnnouncement(
|
||||
input: DeleteAnnouncementInput!
|
||||
): DeleteAnnouncementPayload! @auth(roles: [ADMIN])
|
||||
}
|
||||
|
||||
##################
|
||||
|
||||
@@ -146,6 +146,7 @@ export type Settings = GlobalModerationSettings &
|
||||
| "stories"
|
||||
| "createdAt"
|
||||
| "slack"
|
||||
| "announcement"
|
||||
> & {
|
||||
/**
|
||||
* auth is the set of configured authentication integrations.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isEmpty } from "lodash";
|
||||
import { DateTime } from "luxon";
|
||||
import { Db } from "mongodb";
|
||||
import uuid from "uuid";
|
||||
|
||||
@@ -6,12 +7,14 @@ 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 { isBeforeDate } from "coral-common/utils";
|
||||
import { dotize } from "coral-common/utils/dotize";
|
||||
import { Settings } from "coral-server/models/settings";
|
||||
import { I18n } from "coral-server/services/i18n";
|
||||
import { tenants as collection } from "coral-server/services/mongodb/collections";
|
||||
|
||||
import {
|
||||
GQLAnnouncement,
|
||||
GQLFEATURE_FLAG,
|
||||
GQLMODERATION_MODE,
|
||||
GQLSettings,
|
||||
@@ -396,3 +399,67 @@ export async function disableTenantFeatureFlag(
|
||||
|
||||
return result.value || null;
|
||||
}
|
||||
|
||||
export interface CreateAnnouncementInput {
|
||||
content: string;
|
||||
duration: number;
|
||||
}
|
||||
|
||||
export async function createTenantAnnouncement(
|
||||
mongo: Db,
|
||||
id: string,
|
||||
input: CreateAnnouncementInput,
|
||||
now = new Date()
|
||||
) {
|
||||
const announcement = {
|
||||
id: uuid.v4(),
|
||||
...input,
|
||||
createdAt: now,
|
||||
};
|
||||
|
||||
const result = await collection(mongo).findOneAndUpdate(
|
||||
{ id },
|
||||
{
|
||||
$set: {
|
||||
announcement,
|
||||
},
|
||||
},
|
||||
{
|
||||
returnOriginal: false,
|
||||
}
|
||||
);
|
||||
return result.value;
|
||||
}
|
||||
|
||||
export async function deleteTenantAnnouncement(mongo: Db, id: string) {
|
||||
const result = await collection(mongo).findOneAndUpdate(
|
||||
{ id },
|
||||
{
|
||||
$unset: {
|
||||
announcement: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
returnOriginal: false,
|
||||
}
|
||||
);
|
||||
return result.value;
|
||||
}
|
||||
|
||||
export function retrieveAnnouncementIfEnabled(
|
||||
announcement?: GQLAnnouncement | null
|
||||
) {
|
||||
if (!announcement) {
|
||||
return null;
|
||||
}
|
||||
const disableAt = DateTime.fromJSDate(announcement.createdAt)
|
||||
.plus({ seconds: announcement.duration })
|
||||
.toJSDate();
|
||||
if (isBeforeDate(disableAt)) {
|
||||
return {
|
||||
...announcement,
|
||||
disableAt,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -9,9 +9,12 @@ import { Config } from "coral-server/config";
|
||||
import { TenantInstalledAlreadyError } from "coral-server/errors";
|
||||
import logger from "coral-server/logger";
|
||||
import {
|
||||
CreateAnnouncementInput,
|
||||
createTenant,
|
||||
createTenantAnnouncement,
|
||||
CreateTenantInput,
|
||||
createTenantSSOKey,
|
||||
deleteTenantAnnouncement,
|
||||
disableTenantFeatureFlag,
|
||||
enableTenantFeatureFlag,
|
||||
rotateTenantSSOKey,
|
||||
@@ -253,3 +256,33 @@ export async function disableFeatureFlag(
|
||||
// begin with).
|
||||
return updated.featureFlags || [];
|
||||
}
|
||||
|
||||
export async function createAnnouncement(
|
||||
mongo: Db,
|
||||
redis: Redis,
|
||||
cache: TenantCache,
|
||||
tenant: Tenant,
|
||||
input: CreateAnnouncementInput,
|
||||
now = new Date()
|
||||
) {
|
||||
const updated = await createTenantAnnouncement(mongo, tenant.id, input);
|
||||
if (!updated) {
|
||||
throw new Error("tenant not found");
|
||||
}
|
||||
await cache.update(redis, updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
export async function deleteAnnouncement(
|
||||
mongo: Db,
|
||||
redis: Redis,
|
||||
cache: TenantCache,
|
||||
tenant: Tenant
|
||||
) {
|
||||
const updated = await deleteTenantAnnouncement(mongo, tenant.id);
|
||||
if (!updated) {
|
||||
throw new Error("tenant not found");
|
||||
}
|
||||
await cache.update(redis, updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user