diff --git a/client/coral-admin/src/routes/Moderation/components/Moderation.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js index a69100f9d..ce0c31e33 100644 --- a/client/coral-admin/src/routes/Moderation/components/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js @@ -7,6 +7,7 @@ import ModerationMenu from './ModerationMenu'; import ModerationHeader from './ModerationHeader'; import ModerationKeysModal from '../../../components/ModerationKeysModal'; import StorySearch from '../containers/StorySearch'; +import Slot from 'coral-framework/components/Slot'; export default class Moderation extends Component { constructor() { @@ -100,7 +101,7 @@ export default class Moderation extends Component { } render () { - const {root, moderation, settings, viewUserDetail, hideUserDetail, activeTab, getModPath, premodEnabled, ...props} = this.props; + const {root, data, moderation, settings, viewUserDetail, hideUserDetail, activeTab, getModPath, premodEnabled, ...props} = this.props; const assetId = this.props.params.id; const {asset} = root; @@ -184,6 +185,14 @@ export default class Moderation extends Component { closeSearch={this.closeSearch} storySearchChange={this.props.storySearchChange} /> + + ); } diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js index 4488b0e7f..c9108c0fe 100644 --- a/client/coral-framework/hocs/withMutation.js +++ b/client/coral-framework/hocs/withMutation.js @@ -129,7 +129,7 @@ export default (document, config = {}) => (WrappedComponent) => { }) .catch((error) => { this.context.eventEmitter.emit(`mutation.${name}.error`, {variables, error}); - throw new error; + throw error; }); }; return config.props({...data, mutate}); diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 539bd674e..533032412 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -350,16 +350,6 @@ const setStatus = async ({user, loaders: {Comments}, pubsub}, {id, status}) => { // adjust the affected user's karma in the next tick. process.nextTick(adjustKarma(Comments, id, status)); - if (status === 'ACCEPTED') { - - // Publish the comment status change via the subscription. - pubsub.publish('commentAccepted', comment); - } else if (status === 'REJECTED') { - - // Publish the comment status change via the subscription. - pubsub.publish('commentRejected', comment); - } - return comment; }; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index dec0553a4..685c26a17 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -31,8 +31,18 @@ const RootMutation = { stopIgnoringUser(_, {id}, {mutators: {User}}) { return wrapResponse(null)(User.stopIgnoringUser({id})); }, - setCommentStatus(_, {id, status}, {mutators: {Comment}}) { - return wrapResponse(null)(Comment.setStatus({id, status})); + async setCommentStatus(_, {id, status}, {mutators: {Comment}, pubsub}) { + const comment = await Comment.setStatus({id, status}); + if (status === 'ACCEPTED') { + + // Publish the comment status change via the subscription. + pubsub.publish('commentAccepted', comment); + } else if (status === 'REJECTED') { + + // Publish the comment status change via the subscription. + pubsub.publish('commentRejected', comment); + } + return wrapResponse(null)(comment); }, addTag(_, {tag}, {mutators: {Tag}}) { return wrapResponse(null)(Tag.add(tag)); diff --git a/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js b/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js new file mode 100644 index 000000000..c65415c91 --- /dev/null +++ b/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js @@ -0,0 +1,70 @@ +import React from 'react'; +import {gql} from 'react-apollo'; +import {connect} from 'react-redux'; +import Comment from 'coral-admin/src/routes/Moderation/containers/Comment'; +import {handleCommentChange} from 'coral-admin/src/graphql/utils'; +import {getDefinitionName} from 'coral-framework/utils'; +import truncate from 'lodash/truncate'; +import t from 'coral-framework/services/i18n'; + +function prepareNotificationText(text) { + return truncate(text, {length: 50}).replace('\n', ' '); +} + +class ModSubscription extends React.Component { + unsubscribe = null; + + componentWillMount() { + this.unsubscribe = this.props.data.subscribeToMore({ + document: COMMENT_FEATURED_SUBSCRIPTION, + variables: { + assetId: this.props.data.variables.asset_id, + }, + updateQuery: (prev, {subscriptionData: {data: {commentFeatured: comment}}}) => { + const sort = this.props.data.variables.sort; + const user = comment.status_history[comment.status_history.length - 1].assigned_by; + const notify = { + activeQueue: this.props.activeTab, + text: t( + 'talk-plugin-featured-comments.notify_featured', + user.username, + prepareNotificationText(comment.body) + ), + anyQueue: true, + }; + return handleCommentChange(prev, comment, sort, notify); + }, + }); + } + + componentWillUnmount() { + this.unsubscribe(); + } + + render() { + return null; + } +} + +const COMMENT_FEATURED_SUBSCRIPTION = gql` + subscription CommentFeatured($assetId: ID){ + commentFeatured(asset_id: $assetId){ + ...${getDefinitionName(Comment.fragments.comment)} + status_history { + type + created_at + assigned_by { + id + username + } + } + } + } + ${Comment.fragments.comment} +`; + +const mapStateToProps = (state) => ({ + sortOrder: state.moderation.toJS().sortOrder, +}); + +export default connect(mapStateToProps, null)(ModSubscription); diff --git a/plugins/talk-plugin-featured-comments/client/index.js b/plugins/talk-plugin-featured-comments/client/index.js index a1a6c35a7..82cba8b91 100644 --- a/plugins/talk-plugin-featured-comments/client/index.js +++ b/plugins/talk-plugin-featured-comments/client/index.js @@ -6,6 +6,7 @@ import translations from './translations.yml'; import update from 'immutability-helper'; import reducer from './reducer'; import ModTag from './containers/ModTag'; +import ModSubscription from './containers/ModSubscription'; import {findCommentInEmbedQuery} from 'coral-embed-stream/src/graphql/utils'; import {insertCommentsSorted} from 'plugin-api/beta/client/utils'; @@ -18,7 +19,8 @@ export default { streamTabPanes: [TabPane], commentInfoBar: [Tag], commentReactions: [Button], - adminCommentInfoBar: [ModTag] + adminModeration: [ModSubscription], + adminCommentInfoBar: [ModTag], }, mutations: { IgnoreUser: ({variables}) => ({ diff --git a/plugins/talk-plugin-featured-comments/client/translations.yml b/plugins/talk-plugin-featured-comments/client/translations.yml index 4505782f8..953518452 100644 --- a/plugins/talk-plugin-featured-comments/client/translations.yml +++ b/plugins/talk-plugin-featured-comments/client/translations.yml @@ -6,6 +6,7 @@ en: featured_comments: Featured Comments go_to_conversation: Go to conversation tooltip_description: Comments selected by our team as worth reading + notify_featured: '{0} featured and approved comment "{1}"' es: talk-plugin-featured-comments: un_feature: Desmarcar @@ -13,4 +14,4 @@ es: featured: Remarcado featured_comments: Comentarios Remarcados go_to_conversation: Ir al comentario - tooltip_description: Comentarios seleccionados por nuestro equipo que valen la pena ser leidos \ No newline at end of file + tooltip_description: Comentarios seleccionados por nuestro equipo que valen la pena ser leidos diff --git a/plugins/talk-plugin-featured-comments/index.js b/plugins/talk-plugin-featured-comments/index.js index 88f8344ae..6018cbb0d 100644 --- a/plugins/talk-plugin-featured-comments/index.js +++ b/plugins/talk-plugin-featured-comments/index.js @@ -1,4 +1,48 @@ +const {check} = require('perms/utils'); + module.exports = { + typeDefs: ` + type Subscription { + + # Subscribe to featured comments. + commentFeatured(asset_id: ID): Comment + } + `, + resolvers: { + Subscription: { + commentFeatured: ({comment}) => { + return comment; + }, + }, + }, + setupFunctions: { + commentFeatured: (options, args) => ({ + commentFeatured: { + filter: ({comment}, {user}) => { + if (args.asset_id === null) { + return check(user, ['ADMIN', 'MODERATOR']); + } + return comment.asset_id === args.asset_id; + }, + }, + }), + }, + hooks: { + RootMutation: { + addTag: { + async post(obj, {tag: {name, id, item_type}}, {mutators: {Comment}, pubsub}, info, result) { + if (name === 'FEATURED' && item_type === 'COMMENTS') { + const comment = await Comment.setStatus({id: id, status: 'ACCEPTED'}); + if (comment) { + pubsub.publish('commentFeatured', {comment}); + } + return result; + } + return result; + }, + }, + }, + }, tags: [ { name: 'FEATURED',