diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index cd09b04b2..7899b7685 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -31,10 +31,16 @@ export const storySearchChange = value => ({ }); export const clearState = () => ({ - type: actions.MODERATION_CLEAR_STATE, + type: actions.CLEAR_STATE, }); export const selectCommentId = id => ({ - type: actions.MODERATION_SELECT_COMMENT, + type: actions.SELECT_COMMENT, id, }); + +// Enable or disable the activity indicator subscriptions. +export const setIndicatorTrack = track => ({ + type: actions.SET_INDICATOR_TRACK, + track, +}); diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js index 8eb939d76..c233692dc 100644 --- a/client/coral-admin/src/constants/moderation.js +++ b/client/coral-admin/src/constants/moderation.js @@ -1,9 +1,12 @@ -export const TOGGLE_MODAL = 'TOGGLE_MODAL'; -export const SINGLE_VIEW = 'SINGLE_VIEW'; -export const HIDE_SHORTCUTS_NOTE = 'HIDE_SHORTCUTS_NOTE'; -export const SET_SORT_ORDER = 'MODERATION_SET_SORT_ORDER'; -export const SHOW_STORY_SEARCH = 'SHOW_STORY_SEARCH'; -export const HIDE_STORY_SEARCH = 'HIDE_STORY_SEARCH'; -export const STORY_SEARCH_CHANGE_VALUE = 'STORY_SEARCH_CHANGE_VALUE'; -export const MODERATION_CLEAR_STATE = 'MODERATION_CLEAR_STATE'; -export const MODERATION_SELECT_COMMENT = 'MODERATION_SELECT_COMMENT'; +const prefix = `MODERATION`; + +export const TOGGLE_MODAL = `${prefix}_TOGGLE_MODAL`; +export const SINGLE_VIEW = `${prefix}_SINGLE_VIEW`; +export const HIDE_SHORTCUTS_NOTE = `${prefix}_HIDE_SHORTCUTS_NOTE`; +export const SET_SORT_ORDER = `${prefix}_SET_SORT_ORDER`; +export const SHOW_STORY_SEARCH = `${prefix}_SHOW_STORY_SEARCH`; +export const HIDE_STORY_SEARCH = `${prefix}_HIDE_STORY_SEARCH`; +export const STORY_SEARCH_CHANGE_VALUE = `${prefix}_STORY_SEARCH_CHANGE_VALUE`; +export const CLEAR_STATE = `${prefix}_CLEAR_STATE`; +export const SELECT_COMMENT = `${prefix}_SELECT_COMMENT`; +export const SET_INDICATOR_TRACK = `${prefix}_SET_INDICATOR_TRACK`; diff --git a/client/coral-admin/src/reducers/moderation.js b/client/coral-admin/src/reducers/moderation.js index e2c6a32ff..3dee12785 100644 --- a/client/coral-admin/src/reducers/moderation.js +++ b/client/coral-admin/src/reducers/moderation.js @@ -8,14 +8,19 @@ const initialState = { shortcutsNoteVisible: 'show', sortOrder: 'DESC', selectedCommentId: '', + // If true the activity indicator will turn on subscriptions + // in order to determine queue counts. Set this to false + // if the queue count is determined by other means. + indicatorTrack: true, }; export default function moderation(state = initialState, action) { switch (action.type) { - case actions.MODERATION_CLEAR_STATE: + case actions.CLEAR_STATE: return { ...initialState, shortcutsNoteVisible: state.shortcutsNoteVisible, + indicatorTrack: state.indicatorTrack, }; case actions.TOGGLE_MODAL: return { @@ -52,11 +57,16 @@ export default function moderation(state = initialState, action) { ...state, sortOrder: action.order, }; - case actions.MODERATION_SELECT_COMMENT: + case actions.SELECT_COMMENT: return { ...state, selectedCommentId: action.id, }; + case actions.SET_INDICATOR_TRACK: + return { + ...state, + indicatorTrack: action.track, + }; default: return state; } diff --git a/client/coral-admin/src/routes/Community/containers/FlaggedAccounts.js b/client/coral-admin/src/routes/Community/containers/FlaggedAccounts.js index f5fd36263..ddd684f87 100644 --- a/client/coral-admin/src/routes/Community/containers/FlaggedAccounts.js +++ b/client/coral-admin/src/routes/Community/containers/FlaggedAccounts.js @@ -35,10 +35,6 @@ function whoFlagged(user) { class FlaggedAccountsContainer extends Component { subscriptions = []; - constructor(props) { - super(props); - } - getCountWithoutDangling() { return this.props.root.flaggedUsers.nodes.filter( node => !isFlaggedUserDangling(node) diff --git a/client/coral-admin/src/routes/Community/containers/Indicator.js b/client/coral-admin/src/routes/Community/containers/Indicator.js index eeb5e20f7..9e5be831a 100644 --- a/client/coral-admin/src/routes/Community/containers/Indicator.js +++ b/client/coral-admin/src/routes/Community/containers/Indicator.js @@ -153,9 +153,6 @@ const enhance = compose( state: { status: { username: [SET, CHANGED] } } } ) - me { - id - } } `, }) diff --git a/client/coral-admin/src/routes/Moderation/containers/Indicator.js b/client/coral-admin/src/routes/Moderation/containers/Indicator.js index bd33a58ef..d4fe22851 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Indicator.js +++ b/client/coral-admin/src/routes/Moderation/containers/Indicator.js @@ -1,11 +1,179 @@ +import React, { Component } from 'react'; import { compose, gql } from 'react-apollo'; import Indicator from '../../../components/Indicator'; import { withFragments } from 'plugin-api/beta/client/hocs'; -import { branch, renderNothing } from 'recompose'; +import { handleIndicatorChange } from '../graphql'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import withQueueConfig from '../hoc/withQueueConfig'; +import baseQueueConfig from '../queueConfig'; -const hideIfNoData = hasNoData => branch(hasNoData, renderNothing); +class IndicatorContainer extends Component { + subscriptions = []; + + subscribeToUpdates() { + const parameters = [ + { + document: COMMENT_ADDED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { commentAdded: comment } } } + ) => { + return handleIndicatorChange(prev, comment, this.props.queueConfig); + }, + }, + { + document: COMMENT_FLAGGED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { commentFlagged: comment } } } + ) => { + return handleIndicatorChange(prev, comment, this.props.queueConfig); + }, + }, + { + document: COMMENT_ACCEPTED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { commentAccepted: comment } } } + ) => { + return handleIndicatorChange(prev, comment, this.props.queueConfig); + }, + }, + { + document: COMMENT_REJECTED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { commentRejected: { comment } } } } + ) => { + return handleIndicatorChange(prev, comment, this.props.queueConfig); + }, + }, + { + document: COMMENT_RESET_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { commentReset: { comment } } } } + ) => { + return handleIndicatorChange(prev, comment, this.props.queueConfig); + }, + }, + ]; + + this.subscriptions = parameters.map(param => + this.props.data.subscribeToMore(param) + ); + } + + unsubscribe() { + this.subscriptions.forEach(unsubscribe => unsubscribe()); + this.subscriptions = []; + } + + componentWillMount() { + if (this.props.track) { + this.subscribeToUpdates(); + } + } + + componentWillUnmount() { + this.unsubscribe(); + } + + componentWillReceiveProps(nextProps) { + if (!this.props.track && nextProps.track) { + this.subscribeToUpdates(); + } + if (this.props.track && !nextProps.track) { + this.unsubscribe(); + } + } + + render() { + if ( + !this.props.root || + (!this.props.root.premodCount && !this.props.root.reportedCount) + ) { + return null; + } + + return ; + } +} + +IndicatorContainer.propTypes = { + data: PropTypes.object, + root: PropTypes.object, + track: PropTypes.bool, + queueConfig: PropTypes.object, +}; + +const fields = ` + status + actions { + __typename + ... on FlagAction { + reason + } + user { + id + role + } + } + status_history { + type + assigned_by { + id + } + } +`; + +const COMMENT_ADDED_SUBSCRIPTION = gql` + subscription TalkAdmin_ModerationIndicator_CommentAdded { + commentAdded { + ${fields} + } + } +`; + +const COMMENT_FLAGGED_SUBSCRIPTION = gql` + subscription TalkAdmin_ModerationIndicator_CommentFlagged { + commentFlagged { + ${fields} + } + } +`; + +const COMMENT_ACCEPTED_SUBSCRIPTION = gql` + subscription TalkAdmin_ModerationIndicator_CommentAccepted { + commentAccepted{ + ${fields} + } + } +`; + +const COMMENT_REJECTED_SUBSCRIPTION = gql` + subscription TalkAdmin_ModerationIndicator_CommentRejected { + commentRejected{ + ${fields} + } + } +`; + +const COMMENT_RESET_SUBSCRIPTION = gql` + subscription TalkAdmin_ModerationIndicator_CommentReset { + commentReset { + ${fields} + } + } +`; + +const mapStateToProps = state => ({ + track: state.moderation.indicatorTrack, +}); const enhance = compose( + connect(mapStateToProps), withFragments({ root: gql` fragment TalkAdmin_Moderation_Indicator_root on RootQuery { @@ -19,7 +187,7 @@ const enhance = compose( } `, }), - hideIfNoData(props => !props.root.premodCount && !props.root.reportedCount) + withQueueConfig(baseQueueConfig) ); -export default enhance(Indicator); +export default enhance(IndicatorContainer); diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index fd96c598b..9790291a8 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -27,6 +27,7 @@ import { storySearchChange, clearState, selectCommentId, + setIndicatorTrack, } from 'actions/moderation'; import withQueueConfig from '../hoc/withQueueConfig'; import { notify } from 'coral-framework/actions/notification'; @@ -198,11 +199,15 @@ class ModerationContainer extends Component { } componentWillMount() { + // Stop activity indicator tracking, as we'll handle it here. + this.props.setIndicatorTrack(false); this.props.clearState(); this.subscribeToUpdates(); } componentWillUnmount() { + // Restart activity indicator tracking. + this.props.setIndicatorTrack(true); this.unsubscribe(); } @@ -525,6 +530,7 @@ const mapDispatchToProps = dispatch => ({ clearState, notify, selectCommentId, + setIndicatorTrack, }, dispatch ), diff --git a/client/coral-admin/src/routes/Moderation/graphql.js b/client/coral-admin/src/routes/Moderation/graphql.js index 481c42ab9..ae4bb5e6e 100644 --- a/client/coral-admin/src/routes/Moderation/graphql.js +++ b/client/coral-admin/src/routes/Moderation/graphql.js @@ -91,6 +91,22 @@ function getCommentQueues(comment, queueConfig) { return queues; } +/** + * getPreviousCommentQueues determines queues that this comment previously belonged to. + */ +function getPreviousCommentQueues(comment, queueConfig) { + return comment.status_history.length <= 1 + ? [] + : getCommentQueues( + { + ...comment, + status: + comment.status_history[comment.status_history.length - 2].type, + }, + queueConfig + ); +} + /** * Return whether or not the comment belongs to the queue. */ @@ -203,17 +219,7 @@ export function handleCommentChange( let next = root; // Queues that this comment previously belonged to. - const prevQueues = - comment.status_history.length <= 1 - ? [] - : getCommentQueues( - { - ...comment, - status: - comment.status_history[comment.status_history.length - 2].type, - }, - queueConfig - ); + const prevQueues = getPreviousCommentQueues(comment, queueConfig); // Queues that this comment needs to be placed. const nextQueues = getCommentQueues(comment, queueConfig); @@ -291,3 +297,34 @@ export function handleCommentChange( }); return next; } + +const indicatorQueues = ['premod', 'reported']; + +/** + * Track indicator status + * @param {Object} root current state of the store + * @param {Object} comment comment that was changed + * @return {Object} next state of the store + */ +export function handleIndicatorChange(root, comment, queueConfig) { + let next = root; + + // Queues that this comment previously belonged to. + const prevQueues = getPreviousCommentQueues(comment, queueConfig); + + // Queues that this comment needs to be placed. + const nextQueues = getCommentQueues(comment, queueConfig); + + for (const queue of indicatorQueues) { + if (prevQueues.indexOf(queue) === -1 && nextQueues.indexOf(queue) >= 0) { + next = increaseCommentCount(next, queue); + } else if ( + prevQueues.indexOf(queue) >= 0 && + nextQueues.indexOf(queue) === -1 + ) { + next = decreaseCommentCount(next, queue); + } + } + + return next; +}