[CORL-645] Add Slack support for v5 (#2713)

* Create preliminary schema for setting Slack channels

CORL-645

* Implement preliminary slack notification using tenant slack channels

CORL-645

* Very preliminarily get data loading with FieldArray's for slack channels

CORL-645

* Update settings input on schema to allow saving Slack settings to tenant

CORL-645

* Filter off UNMODERATED queue events from slack channels

We don't send these to slack through our filters, we only care
about pending, reported, and featured

CORL-645

* Include a moderation link in comments pushed to Slack

CORL-645

* Hook up proper callback functions for adding/removing slack channels

CORL-645

* Add missing translation for Slack navigation item

CORL-645

* Update snapshots for preliminary Slack configuration changes

CORL-645

* Add some FormField wrappers around slack config elements

Makes the UI appear a little nicer

CORL-645

* Set up slack config to only provide one slack channel

We need to do this until we can get ArrayField's working
in final-form.

CORL-645

* Disable the other trigger checkboxes when "All Comments" is checked

CORL-645

* Clean up the formatting of Slack messages

CORL-645

* Add error handling around sending comments to Slack

CORL-645

* Add links to external Slack setup documentation

CORL-645

* Replace form state with wrapped field element

CORL-645

* Clean up fetch request sending Slack notifications

CORL-645

* Prefer global string replacement over RegEx

CORL-645

* Use URL class to construct comment URL's

CORL-645

* Require slack configuration in schema

CORL-645

* Initialize Slack in fixtures

Also fix up a flaky test that wasn't waiting
on form submission and on-change events.

CORL-645

* Preliminarily fix up styles to match other config pages

CORL-645

* Create placeholder add/remove buttons

* Convert SlackConfigContainer to FunctionalComponent

CORL-645

* Add name field to slack channels

CORL-645

* Disable inner fields on Slack channel when not enabled

CORL-645

* Improve the delete channel button

CORL-645

* Use pureMerge to extract slack channel settings

CORL-645

* Do a bit of cleanup on the add channel button

CORL-645
This commit is contained in:
Nick Funk
2019-12-03 16:06:38 -05:00
committed by Kim Gardner
parent c109188efe
commit 54296fa484
34 changed files with 1057 additions and 19 deletions
+7 -5
View File
@@ -12,6 +12,7 @@ import { MailerQueue } from "coral-server/queue/tasks/mailer";
import { NotifierQueue } from "coral-server/queue/tasks/notifier";
import { ScraperQueue } from "coral-server/queue/tasks/scraper";
import { JWTSigningConfig } from "coral-server/services/jwt";
import createSlackPublisher from "coral-server/services/slack/publisher";
import TenantCache from "coral-server/services/tenant/cache";
import loaders from "./loaders";
@@ -58,12 +59,13 @@ export default class TenantContext extends CommonContext {
this.mailerQueue = options.mailerQueue;
this.signingConfig = options.signingConfig;
this.clientID = options.clientID;
this.publisher = createPublisher(
this.pubsub,
this.publisher = createPublisher({
pubsub: this.pubsub,
slackPublisher: createSlackPublisher(this.mongo, this.tenant),
notifierQueue,
this.tenant.id,
this.clientID
);
tenantID: this.tenant.id,
clientID: this.clientID,
});
this.loaders = loaders(this);
this.mutators = mutators(this);
}
@@ -2,5 +2,5 @@ import { GQLSettingsTypeResolver } from "coral-server/graph/tenant/schema/__gene
import { Tenant } from "coral-server/models/tenant";
export const Settings: GQLSettingsTypeResolver<Tenant> = {
staticURI: (tenant, args, ctx) => ctx.config.get("static_uri") || null,
slack: ({ slack = {} }) => slack,
};
@@ -0,0 +1,11 @@
import {
GQLSlackConfiguration,
GQLSlackConfigurationTypeResolver,
} from "coral-server/graph/tenant/schema/__generated__/types";
export const SlackConfiguration: GQLSlackConfigurationTypeResolver<
GQLSlackConfiguration
> = {
// TODO: Remove this when we create a migration to generate slack channels on existing tenants
channels: ({ channels = [] }) => channels,
};
@@ -37,6 +37,8 @@ import { Profile } from "./Profile";
import { Query } from "./Query";
import { RecentCommentHistory } from "./RecentCommentHistory";
import { RejectCommentPayload } from "./RejectCommentPayload";
import { Settings } from "./Settings";
import { SlackConfiguration } from "./SlackConfiguration";
import { SSOAuthIntegration } from "./SSOAuthIntegration";
import { Story } from "./Story";
import { StorySettings } from "./StorySettings";
@@ -97,6 +99,8 @@ const Resolvers: GQLResolver = {
UsernameHistory,
UsernameStatus,
UserStatus,
Settings,
SlackConfiguration,
};
export default Resolvers;
@@ -936,6 +936,64 @@ type EmailConfiguration {
smtp: SMTP! @auth(roles: [ADMIN])
}
################################################################################
## Slack Configuration
################################################################################
type SlackConfiguration {
"""
channels is the set of Slack channels configured to receive comments
"""
channels: [SlackChannel!]!
}
type SlackChannelTriggers {
"""
allComments is whether this channel will receive all comments entering moderation
"""
allComments: Boolean!
"""
reportedComments is whether this channel will receive reported comments
"""
reportedComments: Boolean!
"""
pendingComments is whether this channel will receive pending comments
"""
pendingComments: Boolean!
"""
featuredComments is whether this channel will receive featured comments
"""
featuredComments: Boolean!
}
type SlackChannel {
"""
enabled is whether this Slack channel is enabled to send comments to its
correlated web hook
"""
enabled: Boolean!
"""
name is the name assigned to the Slack channel
"""
name: String
"""
hookURL defines the URL that comments will be sent to
"""
hookURL: String
"""
triggers are the filters of types of comments that will be sent to
the correlated channel
"""
triggers: SlackChannelTriggers
}
################################################################################
## ReactionConfiguration
################################################################################
@@ -1217,6 +1275,11 @@ type Settings {
"""
email: EmailConfiguration! @auth(roles: [ADMIN, MODERATOR])
"""
slack is the configuration for slack communication
"""
slack: SlackConfiguration! @auth(roles: [ADMIN, MODERATOR])
"""
wordList will return a given list of words.
"""
@@ -3397,6 +3460,60 @@ input CommenterAccountFeaturesInput {
deleteAccount: Boolean
}
input SlackTriggersConfigurationInput {
"""
allComments is whether this channel will receive all comments entering moderation
"""
allComments: Boolean
"""
reportedComments is whether this channel will receive reported comments
"""
reportedComments: Boolean
"""
pendingComments is whether this channel will receive pending comments
"""
pendingComments: Boolean
"""
featuredComments is whether this channel will receive featured comments
"""
featuredComments: Boolean
}
input SlackChannelConfigurationInput {
"""
enabled is whether this Slack channel is enabled to send comments to its
correlated web hook
"""
enabled: Boolean
"""
name is the name assigned to the Slack channel
"""
name: String
"""
hookURL defines the URL that comments will be sent to
"""
hookURL: String
"""
triggers are the filters of types of comments that will be sent to
the correlated channel
"""
triggers: SlackTriggersConfigurationInput
}
input SlackConfigurationInput {
"""
channels are the channels configured for Slack integration
"""
channels: [SlackChannelConfigurationInput!]
}
"""
SettingsInput is the partial type of the Settings type for performing mutations.
"""
@@ -3507,6 +3624,11 @@ input SettingsInput {
"""
accountFeatures: CommenterAccountFeaturesInput
"""
slack specifies the configuration for Slack integration.
"""
slack: SlackConfigurationInput
"""
locale specifies the locale for this Tenant.
"""
@@ -4,24 +4,35 @@ import { createSubscriptionChannelName } from "coral-server/graph/tenant/resolve
import { SUBSCRIPTION_INPUT } from "coral-server/graph/tenant/resolvers/Subscription/types";
import logger from "coral-server/logger";
import { NotifierQueue } from "coral-server/queue/tasks/notifier";
import { SlackPublisher } from "coral-server/services/slack/publisher";
export type Publisher = (input: SUBSCRIPTION_INPUT) => Promise<void>;
export interface PublisherOptions {
pubsub: RedisPubSub;
slackPublisher: SlackPublisher;
notifierQueue: NotifierQueue;
tenantID: string;
clientID?: string;
}
/**
* createPublisher will create a new Publisher that can be used to send events
* over the pubsub broker to facilitate live updates and notifications.
*
* TODO: Update
* @param pubsub the pubsub broker to be used to facilitate the publish action
* @param notifier
* @param tenantID the ID of the Tenant where the event will be published with
* @param clientID the ID of the client to de-duplicate mutation responses
*/
export const createPublisher = (
pubsub: RedisPubSub,
notifier: NotifierQueue,
tenantID: string,
clientID?: string
): Publisher => async input => {
export const createPublisher = ({
pubsub,
slackPublisher,
notifierQueue,
tenantID,
clientID,
}: PublisherOptions): Publisher => async input => {
const { channel, payload } = input;
logger.trace({ channel, tenantID, clientID }, "publishing event");
@@ -33,8 +44,11 @@ export const createPublisher = (
...payload,
clientID,
}),
slackPublisher(channel, payload),
// Notify the notifications queue so we can offload notification processing
// to it.
notifier.add({ tenantID, input }),
notifierQueue.add({ tenantID, input }),
]);
};