From 1fc07e8649cf9b3e72dc284000fcaffa365733a3 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 24 Jan 2018 23:31:31 +0100 Subject: [PATCH 1/9] Implement activity tracking for flagged accounts --- client/coral-admin/src/actions/community.js | 6 + client/coral-admin/src/components/Header.js | 6 +- client/coral-admin/src/constants/community.js | 2 + client/coral-admin/src/containers/Header.js | 7 +- client/coral-admin/src/graphql/index.js | 16 +- client/coral-admin/src/reducers/community.js | 10 ++ .../Community/containers/FlaggedAccounts.js | 21 ++- .../routes/Community/containers/Indicator.js | 152 +++++++++++++++++- .../src/routes/Community/graphql.js | 24 ++- 9 files changed, 217 insertions(+), 27 deletions(-) diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index d2b8f1a3f..82a74daf3 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -11,6 +11,7 @@ import { HIDE_BANUSER_DIALOG, SHOW_REJECT_USERNAME_DIALOG, HIDE_REJECT_USERNAME_DIALOG, + SET_INDICATOR_TRACK, } from '../constants/community'; import t from 'coral-framework/services/i18n'; @@ -68,3 +69,8 @@ export const showRejectUsernameDialog = user => ({ export const hideRejectUsernameDialog = () => ({ type: HIDE_REJECT_USERNAME_DIALOG, }); + +export const setIndicatorTrack = track => ({ + type: SET_INDICATOR_TRACK, + track, +}); diff --git a/client/coral-admin/src/components/Header.js b/client/coral-admin/src/components/Header.js index e6bd73f52..6b2274d27 100644 --- a/client/coral-admin/src/components/Header.js +++ b/client/coral-admin/src/components/Header.js @@ -15,6 +15,7 @@ const CoralHeader = ({ showShortcuts = () => {}, auth, root, + data, }) => { return (
@@ -31,7 +32,7 @@ const CoralHeader = ({ activeClassName={styles.active} > {t('configure.moderate')} - + )} {t('configure.community')} - + {can(auth.user, 'UPDATE_CONFIG') && ( @@ -119,6 +120,7 @@ CoralHeader.propTypes = { showShortcuts: PropTypes.func, handleLogout: PropTypes.func.isRequired, root: PropTypes.object.isRequired, + data: PropTypes.object.isRequired, }; export default CoralHeader; diff --git a/client/coral-admin/src/constants/community.js b/client/coral-admin/src/constants/community.js index a9cc2f1e3..1ffe3f8d6 100644 --- a/client/coral-admin/src/constants/community.js +++ b/client/coral-admin/src/constants/community.js @@ -18,3 +18,5 @@ export const SHOW_REJECT_USERNAME_DIALOG = `${prefix}_SHOW_REJECT_USERNAME_DIALO export const HIDE_REJECT_USERNAME_DIALOG = `${prefix}_HIDE_REJECT_USERNAME_DIALOG`; export const SET_SEARCH_VALUE = `${prefix}_SET_SEARCH_VALUE`; + +export const SET_INDICATOR_TRACK = `${prefix}_SET_INDICATOR_TRACK`; diff --git a/client/coral-admin/src/containers/Header.js b/client/coral-admin/src/containers/Header.js index dae096517..dd16c17c6 100644 --- a/client/coral-admin/src/containers/Header.js +++ b/client/coral-admin/src/containers/Header.js @@ -13,10 +13,5 @@ export default withQuery( } ${ModerationIndicator.fragments.root} ${CommunityIndicator.fragments.root} - `, - { - options: { - pollInterval: 10000, - }, - } + ` )(Header); diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index 3a24702cd..95595c265 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -173,13 +173,17 @@ export default { }, updateQueries: { TalkAdmin_Community_FlaggedAccounts: (prev, { mutationResult }) => { + const decrement = { + flaggedUsernamesCount: { $apply: count => count - 1 }, + }; + // Remove from list after the mutation was "really" completed. if (get(mutationResult, 'data.approveUsername.isOptimistic')) { - return prev; + return update(prev, decrement); } const updated = update(prev, { - flaggedUsernamesCount: { $apply: count => count - 1 }, + ...decrement, flaggedUsers: { nodes: { $apply: nodes => nodes.filter(node => node.id !== id) }, }, @@ -227,13 +231,17 @@ export default { }, updateQueries: { TalkAdmin_Community_FlaggedAccounts: (prev, { mutationResult }) => { + const decrement = { + flaggedUsernamesCount: { $apply: count => count - 1 }, + }; + // Remove from list after the mutation was "really" completed. if (get(mutationResult, 'data.rejectUsername.isOptimistic')) { - return prev; + return update(prev, decrement); } const updated = update(prev, { - flaggedUsernamesCount: { $apply: count => count - 1 }, + ...decrement, flaggedUsers: { nodes: { $apply: nodes => nodes.filter(node => node.id !== id), diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index 9fe17ad8f..68e471935 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -9,6 +9,7 @@ import { HIDE_BANUSER_DIALOG, SHOW_REJECT_USERNAME_DIALOG, HIDE_REJECT_USERNAME_DIALOG, + SET_INDICATOR_TRACK, } from '../constants/community'; const initialState = { @@ -24,6 +25,10 @@ const initialState = { user: {}, banDialog: false, rejectUsernameDialog: false, + // If true the activity indicator will track flagged account changes + // in order to determine the current queue count. Set this to false + // if the queue count is determined by other means. + indicatorTrack: true, }; export default function community(state = initialState, action) { @@ -91,6 +96,11 @@ export default function community(state = initialState, action) { ...state, searchValue: action.value, }; + case 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 09240b8f3..f5fd36263 100644 --- a/client/coral-admin/src/routes/Community/containers/FlaggedAccounts.js +++ b/client/coral-admin/src/routes/Community/containers/FlaggedAccounts.js @@ -6,12 +6,15 @@ import withQuery from 'coral-framework/hocs/withQuery'; import { Spinner } from 'coral-ui'; import PropTypes from 'prop-types'; import { withApproveUsername } from 'coral-framework/graphql/mutations'; -import { showRejectUsernameDialog } from '../../../actions/community'; +import { + showRejectUsernameDialog, + setIndicatorTrack, +} from '../../../actions/community'; import { viewUserDetail } from '../../../actions/userDetail'; import { getDefinitionName } from 'coral-framework/utils'; import { appendNewNodes } from 'plugin-api/beta/client/utils'; import update from 'immutability-helper'; -import { handleFlaggedUsernameChange } from '../graphql'; +import { handleFlaggedAccountsChange } from '../graphql'; import { notify } from 'coral-framework/actions/notification'; import { isFlaggedUserDangling } from '../utils'; import t from 'coral-framework/services/i18n'; @@ -50,7 +53,7 @@ class FlaggedAccountsContainer extends Component { prev, { subscriptionData: { data: { usernameFlagged: user } } } ) => { - return handleFlaggedUsernameChange(prev, user, () => { + return handleFlaggedAccountsChange(prev, user, () => { const msg = t( 'flagged_usernames.notify_flagged', whoFlagged(user), @@ -66,7 +69,7 @@ class FlaggedAccountsContainer extends Component { prev, { subscriptionData: { data: { usernameApproved: user } } } ) => { - return handleFlaggedUsernameChange(prev, user, () => { + return handleFlaggedAccountsChange(prev, user, () => { const msg = t( 'flagged_usernames.notify_approved', whoChangedTheStatus(user.state.status.username), @@ -82,7 +85,7 @@ class FlaggedAccountsContainer extends Component { prev, { subscriptionData: { data: { usernameRejected: user } } } ) => { - return handleFlaggedUsernameChange(prev, user, () => { + return handleFlaggedAccountsChange(prev, user, () => { const msg = t( 'flagged_usernames.notify_rejected', whoChangedTheStatus(user.state.status.username), @@ -102,7 +105,7 @@ class FlaggedAccountsContainer extends Component { }, } ) => { - return handleFlaggedUsernameChange(prev, user, () => { + return handleFlaggedAccountsChange(prev, user, () => { const msg = t( 'flagged_usernames.notify_changed', previousUsername, @@ -125,10 +128,14 @@ class FlaggedAccountsContainer extends Component { } componentWillMount() { + // Stop activity indicator tracking, as we'll handle it here. + this.props.setIndicatorTrack(false); this.subscribeToUpdates(); } componentWillUnmount() { + // Restart activity indicator tracking. + this.props.setIndicatorTrack(true); this.unsubscribe(); } @@ -197,6 +204,7 @@ FlaggedAccountsContainer.propTypes = { approveUsername: PropTypes.func, data: PropTypes.object, root: PropTypes.object, + setIndicatorTrack: PropTypes.func, }; const LOAD_MORE_QUERY = gql` @@ -288,6 +296,7 @@ const mapDispatchToProps = dispatch => showRejectUsernameDialog, viewUserDetail, notify, + setIndicatorTrack, }, dispatch ); diff --git a/client/coral-admin/src/routes/Community/containers/Indicator.js b/client/coral-admin/src/routes/Community/containers/Indicator.js index 04a351fb6..eeb5e20f7 100644 --- a/client/coral-admin/src/routes/Community/containers/Indicator.js +++ b/client/coral-admin/src/routes/Community/containers/Indicator.js @@ -1,24 +1,164 @@ +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'; -const hideIfNoData = hasNoData => branch(hasNoData, renderNothing); +class IndicatorContainer extends Component { + subscriptions = []; + + subscribeToUpdates() { + const parameters = [ + { + document: USERNAME_FLAGGED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { usernameFlagged: user } } } + ) => { + return handleIndicatorChange(prev, user); + }, + }, + { + document: USERNAME_APPROVED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { usernameApproved: user } } } + ) => { + return handleIndicatorChange(prev, user); + }, + }, + { + document: USERNAME_REJECTED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { usernameRejected: user } } } + ) => { + return handleIndicatorChange(prev, user); + }, + }, + { + document: USERNAME_CHANGED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { usernameChanged: { user } } } } + ) => { + return handleIndicatorChange(prev, user); + }, + }, + ]; + + 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.flaggedUsernamesCount) { + return null; + } + + return ; + } +} + +IndicatorContainer.propTypes = { + data: PropTypes.object, + root: PropTypes.object, + track: PropTypes.bool, +}; + +const fields = ` + state { + status { + username { + status + } + } + } +`; + +const USERNAME_FLAGGED_SUBSCRIPTION = gql` + subscription TalkAdmin_CommunityIndicator_UsernameFlagged { + usernameFlagged { + ${fields} + } + } +`; + +const USERNAME_APPROVED_SUBSCRIPTION = gql` + subscription TalkAdmin_ComunityIndicator_UsernameApproved { + usernameApproved { + ${fields} + } + } +`; + +const USERNAME_REJECTED_SUBSCRIPTION = gql` + subscription TalkAdmin_CommunityIndicator_UsernameRejected { + usernameRejected { + ${fields} + } + } +`; + +const USERNAME_CHANGED_SUBSCRIPTION = gql` + subscription TalkAdmin_ComunityIndicator_UsernameChanged { + usernameChanged { + previousUsername + user { + ${fields} + } + } + } +`; + +const mapStateToProps = state => ({ + track: state.community.indicatorTrack, +}); const enhance = compose( + connect(mapStateToProps), withFragments({ root: gql` - fragment TalkAdmin_Community_Indicator_root on RootQuery { + fragment TalkAdmin_CommunityIndicator_root on RootQuery { flaggedUsernamesCount: userCount( query: { action_type: FLAG state: { status: { username: [SET, CHANGED] } } } ) + me { + id + } } `, - }), - hideIfNoData(props => !props.root.flaggedUsernamesCount) + }) ); -export default enhance(Indicator); +export default enhance(IndicatorContainer); diff --git a/client/coral-admin/src/routes/Community/graphql.js b/client/coral-admin/src/routes/Community/graphql.js index af10c8e86..8d6941c41 100644 --- a/client/coral-admin/src/routes/Community/graphql.js +++ b/client/coral-admin/src/routes/Community/graphql.js @@ -49,13 +49,13 @@ function decrementFlaggedUserCount(root) { } /** - * Assimilate flagged user changes into current store. + * Assimilate flagged acount changes into current store. * @param {Object} root current state of the store * @param {Object} user user that was changed * @param {function} notify callback to show notification * @return {Object} next state of the store */ -export function handleFlaggedUsernameChange(root, user, notify) { +export function handleFlaggedAccountsChange(root, user, notify) { if (user.state.status.username.status !== 'SET') { // Check if change came from current user, if so ignore it. const lastChange = @@ -87,7 +87,7 @@ export function handleFlaggedUsernameChange(root, user, notify) { break; case 'APPROVED': case 'REJECTED': - return root; + return decrementFlaggedUserCount(root); default: } } @@ -105,3 +105,21 @@ export function handleFlaggedUsernameChange(root, user, notify) { } } } + +/** + * Track indicator status + * @param {Object} root current state of the store + * @param {Object} user user that was changed + * @return {Object} next state of the store + */ +export function handleIndicatorChange(root, user) { + switch (user.state.status.username.status) { + case 'SET': + case 'CHANGED': + return incrementFlaggedUserCount(root); + case 'APPROVED': + case 'REJECTED': + return decrementFlaggedUserCount(root); + default: + } +} From 2246b16cf41ea2b88ed534dc3abba6c6457512fa Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 24 Jan 2018 23:34:53 +0100 Subject: [PATCH 2/9] Add some comments --- client/coral-admin/src/actions/community.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index 82a74daf3..9a0452ea1 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -70,6 +70,7 @@ export const hideRejectUsernameDialog = () => ({ type: HIDE_REJECT_USERNAME_DIALOG, }); +// Enable or disable the activity indicator subscriptions. export const setIndicatorTrack = track => ({ type: SET_INDICATOR_TRACK, track, From 66934cbb82485a53a7e7ab6b1db4e5c28d026198 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 25 Jan 2018 16:45:19 +0100 Subject: [PATCH 3/9] Implement indicator subscriptions for moderation --- client/coral-admin/src/actions/moderation.js | 10 +- .../coral-admin/src/constants/moderation.js | 21 ++- client/coral-admin/src/reducers/moderation.js | 14 +- .../Community/containers/FlaggedAccounts.js | 4 - .../routes/Community/containers/Indicator.js | 3 - .../routes/Moderation/containers/Indicator.js | 176 +++++++++++++++++- .../Moderation/containers/Moderation.js | 6 + .../src/routes/Moderation/graphql.js | 59 ++++-- 8 files changed, 258 insertions(+), 35 deletions(-) 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; +} From 26ca3622c2c633090f81b92a9368bf9651309333 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 25 Jan 2018 20:01:25 +0100 Subject: [PATCH 4/9] Integrate featured into activity indicator subscription --- .../routes/Moderation/containers/Indicator.js | 34 +++++-- .../containers/ModIndicatorSubscription.js | 94 +++++++++++++++++++ .../client/index.js | 2 + .../talk-plugin-featured-comments/index.js | 4 +- 4 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js diff --git a/client/coral-admin/src/routes/Moderation/containers/Indicator.js b/client/coral-admin/src/routes/Moderation/containers/Indicator.js index d4fe22851..7df710f2f 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Indicator.js +++ b/client/coral-admin/src/routes/Moderation/containers/Indicator.js @@ -7,10 +7,15 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import withQueueConfig from '../hoc/withQueueConfig'; import baseQueueConfig from '../queueConfig'; +import Slot from 'coral-framework/components/Slot'; class IndicatorContainer extends Component { subscriptions = []; + handleCommentChange = (root, comment) => { + return handleIndicatorChange(root, comment, this.props.queueConfig); + }; + subscribeToUpdates() { const parameters = [ { @@ -19,7 +24,7 @@ class IndicatorContainer extends Component { prev, { subscriptionData: { data: { commentAdded: comment } } } ) => { - return handleIndicatorChange(prev, comment, this.props.queueConfig); + return this.handleCommentChange(prev, comment); }, }, { @@ -28,7 +33,7 @@ class IndicatorContainer extends Component { prev, { subscriptionData: { data: { commentFlagged: comment } } } ) => { - return handleIndicatorChange(prev, comment, this.props.queueConfig); + return this.handleCommentChange(prev, comment); }, }, { @@ -37,25 +42,25 @@ class IndicatorContainer extends Component { prev, { subscriptionData: { data: { commentAccepted: comment } } } ) => { - return handleIndicatorChange(prev, comment, this.props.queueConfig); + return this.handleCommentChange(prev, comment); }, }, { document: COMMENT_REJECTED_SUBSCRIPTION, updateQuery: ( prev, - { subscriptionData: { data: { commentRejected: { comment } } } } + { subscriptionData: { data: { commentRejected: comment } } } ) => { - return handleIndicatorChange(prev, comment, this.props.queueConfig); + return this.handleCommentChange(prev, comment); }, }, { document: COMMENT_RESET_SUBSCRIPTION, updateQuery: ( prev, - { subscriptionData: { data: { commentReset: { comment } } } } + { subscriptionData: { data: { commentReset: comment } } } ) => { - return handleIndicatorChange(prev, comment, this.props.queueConfig); + return this.handleCommentChange(prev, comment); }, }, ]; @@ -97,7 +102,16 @@ class IndicatorContainer extends Component { return null; } - return ; + return ( + + + + + ); } } @@ -146,7 +160,7 @@ const COMMENT_FLAGGED_SUBSCRIPTION = gql` const COMMENT_ACCEPTED_SUBSCRIPTION = gql` subscription TalkAdmin_ModerationIndicator_CommentAccepted { - commentAccepted{ + commentAccepted { ${fields} } } @@ -154,7 +168,7 @@ const COMMENT_ACCEPTED_SUBSCRIPTION = gql` const COMMENT_REJECTED_SUBSCRIPTION = gql` subscription TalkAdmin_ModerationIndicator_CommentRejected { - commentRejected{ + commentRejected { ${fields} } } diff --git a/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js b/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js new file mode 100644 index 000000000..53af12eb4 --- /dev/null +++ b/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js @@ -0,0 +1,94 @@ +import React from 'react'; +import { gql } from 'react-apollo'; + +class ModIndicatorSubscription extends React.Component { + subscriptions = null; + + componentWillMount() { + const configs = [ + { + document: COMMENT_FEATURED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { commentFeatured: { comment } } } } + ) => { + return this.props.handleCommentChange(prev, comment); + }, + }, + { + document: COMMENT_UNFEATURED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { commentUnfeatured: { comment } } } } + ) => { + return this.props.handleCommentChange(prev, comment); + }, + }, + ]; + this.subscriptions = configs.map(config => + this.props.data.subscribeToMore(config) + ); + } + + componentWillUnmount() { + this.subscriptions.forEach(unsubscribe => unsubscribe()); + } + + render() { + return null; + } +} + +const COMMENT_FEATURED_SUBSCRIPTION = gql` + subscription TalkFeaturedComments_Indicator_CommentFeatured { + commentFeatured { + comment { + status + actions { + __typename + ... on FlagAction { + reason + } + user { + id + role + } + } + status_history { + type + assigned_by { + id + } + } + } + } + } +`; + +const COMMENT_UNFEATURED_SUBSCRIPTION = gql` + subscription TalkFeaturedComments_Indicator_CommentUnfeatured { + commentUnfeatured { + comment { + status + actions { + __typename + ... on FlagAction { + reason + } + user { + id + role + } + } + status_history { + type + assigned_by { + id + } + } + } + } + } +`; + +export default ModIndicatorSubscription; diff --git a/plugins/talk-plugin-featured-comments/client/index.js b/plugins/talk-plugin-featured-comments/client/index.js index 1f0bac704..2572a54f4 100644 --- a/plugins/talk-plugin-featured-comments/client/index.js +++ b/plugins/talk-plugin-featured-comments/client/index.js @@ -6,6 +6,7 @@ import update from 'immutability-helper'; import ModTag from './containers/ModTag'; import ModActionButton from './containers/ModActionButton'; import ModSubscription from './containers/ModSubscription'; +import ModIndicatorSubscription from './containers/ModIndicatorSubscription'; import FeaturedDialog from './containers/FeaturedDialog'; import { gql } from 'react-apollo'; import reducer from './reducer'; @@ -23,6 +24,7 @@ export default { moderationActions: [ModActionButton], adminModeration: [ModSubscription, FeaturedDialog], adminCommentInfoBar: [ModTag], + adminModerationIndicator: [ModIndicatorSubscription], }, mutations: { IgnoreUser: ({ variables }) => ({ diff --git a/plugins/talk-plugin-featured-comments/index.js b/plugins/talk-plugin-featured-comments/index.js index f5a9c8ae5..6dd3114e1 100644 --- a/plugins/talk-plugin-featured-comments/index.js +++ b/plugins/talk-plugin-featured-comments/index.js @@ -36,7 +36,7 @@ module.exports = { commentFeatured: (options, args) => ({ commentFeatured: { filter: ({ comment }, { user }) => { - if (args.asset_id === null) { + if (!args.asset_id) { return check(user, ['ADMIN', 'MODERATOR']); } return comment.asset_id === args.asset_id; @@ -46,7 +46,7 @@ module.exports = { commentUnfeatured: (options, args) => ({ commentUnfeatured: { filter: ({ comment }, { user }) => { - if (args.asset_id === null) { + if (!args.asset_id) { return check(user, ['ADMIN', 'MODERATOR']); } return comment.asset_id === args.asset_id; From 6fd4d1576e48f8dc1cd8e3eadc7a1a135c41eba5 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 25 Jan 2018 20:08:57 +0100 Subject: [PATCH 5/9] Correct logic for disabling activity subscriptions --- .../Moderation/containers/Moderation.js | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index 9790291a8..481a6d331 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -199,25 +199,42 @@ class ModerationContainer extends Component { } componentWillMount() { - // Stop activity indicator tracking, as we'll handle it here. - this.props.setIndicatorTrack(false); + if (!this.props.data.variables.asset_id) { + // 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); + if (!this.props.data.variables.asset_id) { + // Restart activity indicator tracking. + this.props.setIndicatorTrack(true); + } this.unsubscribe(); } componentWillReceiveProps(nextProps) { + const currentAssetId = this.props.data.variables.asset_id; + const nextAssetId = nextProps.data.variables.asset_id; + // Resubscribe when we change between assets. - if ( - this.props.data.variables.asset_id !== nextProps.data.variables.asset_id - ) { + if (currentAssetId !== nextAssetId) { this.resubscribe(nextProps.data.variables); } + + // We are only subscribing to a specific asset_id, so activity indicator + // needs to do its own tracking. + if (!currentAssetId && nextAssetId) { + this.props.setIndicatorTrack(true); + } + + // We are subscribing to all comment changes, and as such there is no + // need for the activity indicator to do the same. + if (currentAssetId && !nextAssetId) { + this.props.setIndicatorTrack(false); + } } cleanUpQueue = queue => { From 426025f8c23c0a7c9ac2619b52c7858dc00b6a93 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 25 Jan 2018 20:49:57 +0100 Subject: [PATCH 6/9] Subscribe to edited + bug fixes --- .../routes/Moderation/containers/Indicator.js | 19 ++++++++++++++++++- graph/resolvers/util.js | 8 ++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/containers/Indicator.js b/client/coral-admin/src/routes/Moderation/containers/Indicator.js index 7df710f2f..41c5be990 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Indicator.js +++ b/client/coral-admin/src/routes/Moderation/containers/Indicator.js @@ -36,6 +36,15 @@ class IndicatorContainer extends Component { return this.handleCommentChange(prev, comment); }, }, + { + document: COMMENT_EDITED_SUBSCRIPTION, + updateQuery: ( + prev, + { subscriptionData: { data: { commentEdited: comment } } } + ) => { + return this.handleCommentChange(prev, comment); + }, + }, { document: COMMENT_ACCEPTED_SUBSCRIPTION, updateQuery: ( @@ -144,7 +153,7 @@ const fields = ` const COMMENT_ADDED_SUBSCRIPTION = gql` subscription TalkAdmin_ModerationIndicator_CommentAdded { - commentAdded { + commentAdded(statuses: null) { ${fields} } } @@ -158,6 +167,14 @@ const COMMENT_FLAGGED_SUBSCRIPTION = gql` } `; +const COMMENT_EDITED_SUBSCRIPTION = gql` + subscription TalkAdmin_ModerationIndicator_CommentEdited { + commentEdited { + ${fields} + } + } +`; + const COMMENT_ACCEPTED_SUBSCRIPTION = gql` subscription TalkAdmin_ModerationIndicator_CommentAccepted { commentAccepted { diff --git a/graph/resolvers/util.js b/graph/resolvers/util.js index ff9ab9c95..992148b4f 100644 --- a/graph/resolvers/util.js +++ b/graph/resolvers/util.js @@ -50,8 +50,12 @@ const decorateWithPermissionCheck = (typeResolver, protect) => { */ const decorateUserField = (typeResolver, field) => { // The default resolver for the user decorator is loading the user by id. - let fieldResolver = (obj, args, ctx) => - ctx.loaders.Users.getByID.load(obj[field]); + let fieldResolver = (obj, args, ctx) => { + if (!obj[field]) { + return null; + } + return ctx.loaders.Users.getByID.load(obj[field]); + }; // The resolver can be overridden however. This decorator will simply wrap the // field with a permission check. From ce25cfb159ceaa163237e2db55f947b3b1a9e102 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 25 Jan 2018 22:03:37 +0100 Subject: [PATCH 7/9] Reconstruct previous comment state more accurately --- .../src/containers/CommentLabels.js | 1 + .../routes/Moderation/containers/Indicator.js | 11 +-- .../Moderation/containers/Moderation.js | 49 +++++----- .../src/routes/Moderation/graphql.js | 96 ++++++++++++++++--- graph/typeDefs.graphql | 3 + .../containers/ModIndicatorSubscription.js | 53 ++++------ .../client/containers/ModSubscription.js | 27 ++++-- 7 files changed, 153 insertions(+), 87 deletions(-) diff --git a/client/coral-admin/src/containers/CommentLabels.js b/client/coral-admin/src/containers/CommentLabels.js index 4cee8b1cc..ee65eb636 100644 --- a/client/coral-admin/src/containers/CommentLabels.js +++ b/client/coral-admin/src/containers/CommentLabels.js @@ -25,6 +25,7 @@ export default withFragments({ id role } + created_at } ${getSlotFragmentSpreads(slots, 'comment')} } diff --git a/client/coral-admin/src/routes/Moderation/containers/Indicator.js b/client/coral-admin/src/routes/Moderation/containers/Indicator.js index 41c5be990..b5620765e 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Indicator.js +++ b/client/coral-admin/src/routes/Moderation/containers/Indicator.js @@ -135,20 +135,17 @@ const fields = ` status actions { __typename - ... on FlagAction { - reason - } - user { - id - role - } + created_at } status_history { type assigned_by { id } + created_at } + updated_at + created_at `; const COMMENT_ADDED_SUBSCRIPTION = gql` diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index 481a6d331..c654ad6b8 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -338,10 +338,30 @@ class ModerationContainer extends Component { ); } } + +const subscriptionFields = ` + status + actions { + __typename + created_at + } + status_history { + type + created_at + assigned_by { + id + username + } + } + created_at + updated_at +`; + const COMMENT_ADDED_SUBSCRIPTION = gql` subscription CommentAdded($asset_id: ID){ commentAdded(asset_id: $asset_id, statuses: null){ ...${getDefinitionName(Comment.fragments.comment)} + ${subscriptionFields} } } ${Comment.fragments.comment} @@ -351,6 +371,7 @@ const COMMENT_EDITED_SUBSCRIPTION = gql` subscription CommentEdited($asset_id: ID){ commentEdited(asset_id: $asset_id){ ...${getDefinitionName(Comment.fragments.comment)} + ${subscriptionFields} } } ${Comment.fragments.comment} @@ -360,6 +381,7 @@ const COMMENT_FLAGGED_SUBSCRIPTION = gql` subscription CommentFlagged($asset_id: ID){ commentFlagged(asset_id: $asset_id){ ...${getDefinitionName(Comment.fragments.comment)} + ${subscriptionFields} } } ${Comment.fragments.comment} @@ -369,14 +391,7 @@ const COMMENT_ACCEPTED_SUBSCRIPTION = gql` subscription CommentAccepted($asset_id: ID){ commentAccepted(asset_id: $asset_id){ ...${getDefinitionName(Comment.fragments.comment)} - status_history { - type - created_at - assigned_by { - id - username - } - } + ${subscriptionFields} } } ${Comment.fragments.comment} @@ -386,14 +401,7 @@ const COMMENT_REJECTED_SUBSCRIPTION = gql` subscription CommentRejected($asset_id: ID){ commentRejected(asset_id: $asset_id){ ...${getDefinitionName(Comment.fragments.comment)} - status_history { - type - created_at - assigned_by { - id - username - } - } + ${subscriptionFields} } } ${Comment.fragments.comment} @@ -403,14 +411,7 @@ const COMMENT_RESET_SUBSCRIPTION = gql` subscription CommentReset($asset_id: ID){ commentReset(asset_id: $asset_id){ ...${getDefinitionName(Comment.fragments.comment)} - status_history { - type - created_at - assigned_by { - id - username - } - } + ${subscriptionFields} } } ${Comment.fragments.comment} diff --git a/client/coral-admin/src/routes/Moderation/graphql.js b/client/coral-admin/src/routes/Moderation/graphql.js index ae4bb5e6e..e113e02e1 100644 --- a/client/coral-admin/src/routes/Moderation/graphql.js +++ b/client/coral-admin/src/routes/Moderation/graphql.js @@ -91,20 +91,87 @@ function getCommentQueues(comment, queueConfig) { return queues; } +function getOlderDate(a, b) { + if (a) { + a = new Date(a); + } + if (b) { + b = new Date(b); + } + + if (!b) { + return a; + } + + if (!a) { + return b; + } + return a < b ? b : a; +} + +function determineLatestChange(comment) { + let lc = null; + + // Skip it when comment itself was not updated. + // We'll get use the one from the status_history instead + // as they might diverge a little bit (status_history item is created + // before the comment itself) + if (comment.createdAt !== comment.updatedAt) { + lc = getOlderDate(lc, comment.createdAt); + lc = getOlderDate(lc, comment.updatedAt); + } + + comment.status_history.forEach(item => { + lc = getOlderDate(lc, item.created_at); + lc = getOlderDate(lc, item.updated_at); + }); + + comment.actions.forEach(item => { + lc = getOlderDate(lc, item.created_at); + lc = getOlderDate(lc, item.updated_at); + }); + + return lc; +} + +function reconstructPreviousCommentState(comment) { + const history = comment.status_history; + const actions = comment.actions; + const lastChangeDate = determineLatestChange(comment); + console.log(lastChangeDate); + const previousComment = { + ...comment, + status_history: history.filter( + item => new Date(item.created_at) < lastChangeDate + ), + actions: actions.filter(item => new Date(item.created_at) < lastChangeDate), + }; + + // Comment did not exist previously. + if (!previousComment.status_history.length) { + return null; + } + + previousComment.status = + previousComment.status_history[ + previousComment.status_history.length - 1 + ].type; + + return previousComment; +} + /** * 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 - ); + const previousCommentState = reconstructPreviousCommentState(comment); + + console.log(previousCommentState, comment); + if (!previousCommentState) { + return []; + } + + return getCommentQueues(previousCommentState, queueConfig); } /** @@ -224,6 +291,8 @@ export function handleCommentChange( // Queues that this comment needs to be placed. const nextQueues = getCommentQueues(comment, queueConfig); + console.log(prevQueues, nextQueues); + let notificationShown = false; const showNotificationOnce = () => { if (notificationShown) { @@ -318,13 +387,12 @@ export function handleIndicatorChange(root, 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 - ) { + } + if (prevQueues.indexOf(queue) >= 0 && nextQueues.indexOf(queue) === -1) { next = decreaseCommentCount(next, queue); } } + console.log(prevQueues, nextQueues, next); return next; } diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 79d1c5b0f..550856082 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -502,6 +502,9 @@ type Comment { # The time when the comment was created created_at: Date! + # The time when the comment was updated. + updated_at: Date + # describes how the comment can be edited editing: EditInfo diff --git a/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js b/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js index 53af12eb4..872fe641c 100644 --- a/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js +++ b/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js @@ -39,27 +39,28 @@ class ModIndicatorSubscription extends React.Component { } } +const fields = ` + status + actions { + __typename + created_at + } + status_history { + type + assigned_by { + id + } + created_at + } + updated_at + created_at +`; + const COMMENT_FEATURED_SUBSCRIPTION = gql` subscription TalkFeaturedComments_Indicator_CommentFeatured { commentFeatured { comment { - status - actions { - __typename - ... on FlagAction { - reason - } - user { - id - role - } - } - status_history { - type - assigned_by { - id - } - } + ${fields} } } } @@ -69,23 +70,7 @@ const COMMENT_UNFEATURED_SUBSCRIPTION = gql` subscription TalkFeaturedComments_Indicator_CommentUnfeatured { commentUnfeatured { comment { - status - actions { - __typename - ... on FlagAction { - reason - } - user { - id - role - } - } - status_history { - type - assigned_by { - id - } - } + ${fields} } } } diff --git a/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js b/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js index 3e7eb3aaf..da00e8ba7 100644 --- a/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js +++ b/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js @@ -74,19 +74,29 @@ class ModSubscription extends React.Component { } } +const fields = ` + status + actions { + __typename + created_at + } + status_history { + type + assigned_by { + id + } + created_at + } + updated_at + created_at +`; + const COMMENT_FEATURED_SUBSCRIPTION = gql` subscription CommentFeatured($assetId: ID){ commentFeatured(asset_id: $assetId) { comment { ...${getDefinitionName(Comment.fragments.comment)} - status_history { - type - created_at - assigned_by { - id - username - } - } + ${fields} } user { id @@ -102,6 +112,7 @@ const COMMENT_UNFEATURED_SUBSCRIPTION = gql` commentUnfeatured(asset_id: $assetId){ comment { ...${getDefinitionName(Comment.fragments.comment)} + ${fields} } user { id From b638b783febc99eb22920628a64749e5eb6e9082 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 26 Jan 2018 01:42:46 +0100 Subject: [PATCH 8/9] Refactor --- .../routes/Moderation/containers/Indicator.js | 31 +++++-------------- .../Moderation/containers/Moderation.js | 19 +----------- .../src/routes/Moderation/graphql.js | 24 ++++++++++---- .../containers/ModIndicatorSubscription.js | 22 ++----------- .../client/containers/ModSubscription.js | 22 ++----------- 5 files changed, 32 insertions(+), 86 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/containers/Indicator.js b/client/coral-admin/src/routes/Moderation/containers/Indicator.js index b5620765e..359a2cd5d 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Indicator.js +++ b/client/coral-admin/src/routes/Moderation/containers/Indicator.js @@ -2,7 +2,7 @@ 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 { handleIndicatorChange } from '../graphql'; +import { handleIndicatorChange, subscriptionFields } from '../graphql'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import withQueueConfig from '../hoc/withQueueConfig'; @@ -131,27 +131,10 @@ IndicatorContainer.propTypes = { queueConfig: PropTypes.object, }; -const fields = ` - status - actions { - __typename - created_at - } - status_history { - type - assigned_by { - id - } - created_at - } - updated_at - created_at -`; - const COMMENT_ADDED_SUBSCRIPTION = gql` subscription TalkAdmin_ModerationIndicator_CommentAdded { commentAdded(statuses: null) { - ${fields} + ${subscriptionFields} } } `; @@ -159,7 +142,7 @@ const COMMENT_ADDED_SUBSCRIPTION = gql` const COMMENT_FLAGGED_SUBSCRIPTION = gql` subscription TalkAdmin_ModerationIndicator_CommentFlagged { commentFlagged { - ${fields} + ${subscriptionFields} } } `; @@ -167,7 +150,7 @@ const COMMENT_FLAGGED_SUBSCRIPTION = gql` const COMMENT_EDITED_SUBSCRIPTION = gql` subscription TalkAdmin_ModerationIndicator_CommentEdited { commentEdited { - ${fields} + ${subscriptionFields} } } `; @@ -175,7 +158,7 @@ const COMMENT_EDITED_SUBSCRIPTION = gql` const COMMENT_ACCEPTED_SUBSCRIPTION = gql` subscription TalkAdmin_ModerationIndicator_CommentAccepted { commentAccepted { - ${fields} + ${subscriptionFields} } } `; @@ -183,7 +166,7 @@ const COMMENT_ACCEPTED_SUBSCRIPTION = gql` const COMMENT_REJECTED_SUBSCRIPTION = gql` subscription TalkAdmin_ModerationIndicator_CommentRejected { commentRejected { - ${fields} + ${subscriptionFields} } } `; @@ -191,7 +174,7 @@ const COMMENT_REJECTED_SUBSCRIPTION = gql` const COMMENT_RESET_SUBSCRIPTION = gql` subscription TalkAdmin_ModerationIndicator_CommentReset { commentReset { - ${fields} + ${subscriptionFields} } } `; diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index c654ad6b8..b5820287b 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -15,6 +15,7 @@ import { handleCommentChange, commentBelongToQueue, cleanUpQueue, + subscriptionFields, } from '../graphql'; import { viewUserDetail } from '../../../actions/userDetail'; @@ -339,24 +340,6 @@ class ModerationContainer extends Component { } } -const subscriptionFields = ` - status - actions { - __typename - created_at - } - status_history { - type - created_at - assigned_by { - id - username - } - } - created_at - updated_at -`; - const COMMENT_ADDED_SUBSCRIPTION = gql` subscription CommentAdded($asset_id: ID){ commentAdded(asset_id: $asset_id, statuses: null){ diff --git a/client/coral-admin/src/routes/Moderation/graphql.js b/client/coral-admin/src/routes/Moderation/graphql.js index e113e02e1..44b4f6108 100644 --- a/client/coral-admin/src/routes/Moderation/graphql.js +++ b/client/coral-admin/src/routes/Moderation/graphql.js @@ -113,7 +113,7 @@ function determineLatestChange(comment) { let lc = null; // Skip it when comment itself was not updated. - // We'll get use the one from the status_history instead + // We'll use the one from the status_history instead // as they might diverge a little bit (status_history item is created // before the comment itself) if (comment.createdAt !== comment.updatedAt) { @@ -138,7 +138,6 @@ function reconstructPreviousCommentState(comment) { const history = comment.status_history; const actions = comment.actions; const lastChangeDate = determineLatestChange(comment); - console.log(lastChangeDate); const previousComment = { ...comment, status_history: history.filter( @@ -166,7 +165,6 @@ function reconstructPreviousCommentState(comment) { function getPreviousCommentQueues(comment, queueConfig) { const previousCommentState = reconstructPreviousCommentState(comment); - console.log(previousCommentState, comment); if (!previousCommentState) { return []; } @@ -291,8 +289,6 @@ export function handleCommentChange( // Queues that this comment needs to be placed. const nextQueues = getCommentQueues(comment, queueConfig); - console.log(prevQueues, nextQueues); - let notificationShown = false; const showNotificationOnce = () => { if (notificationShown) { @@ -393,6 +389,22 @@ export function handleIndicatorChange(root, comment, queueConfig) { } } - console.log(prevQueues, nextQueues, next); return next; } + +export const subscriptionFields = ` + status + actions { + __typename + created_at + } + status_history { + type + assigned_by { + id + } + created_at + } + updated_at + created_at +`; diff --git a/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js b/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js index 872fe641c..86ae6bb2c 100644 --- a/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js +++ b/plugins/talk-plugin-featured-comments/client/containers/ModIndicatorSubscription.js @@ -1,5 +1,6 @@ import React from 'react'; import { gql } from 'react-apollo'; +import { subscriptionFields } from 'coral-admin/src/routes/Moderation/graphql'; class ModIndicatorSubscription extends React.Component { subscriptions = null; @@ -39,28 +40,11 @@ class ModIndicatorSubscription extends React.Component { } } -const fields = ` - status - actions { - __typename - created_at - } - status_history { - type - assigned_by { - id - } - created_at - } - updated_at - created_at -`; - const COMMENT_FEATURED_SUBSCRIPTION = gql` subscription TalkFeaturedComments_Indicator_CommentFeatured { commentFeatured { comment { - ${fields} + ${subscriptionFields} } } } @@ -70,7 +54,7 @@ const COMMENT_UNFEATURED_SUBSCRIPTION = gql` subscription TalkFeaturedComments_Indicator_CommentUnfeatured { commentUnfeatured { comment { - ${fields} + ${subscriptionFields} } } } diff --git a/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js b/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js index da00e8ba7..6d0fdd811 100644 --- a/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js +++ b/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js @@ -5,6 +5,7 @@ import Comment from 'coral-admin/src/routes/Moderation/containers/Comment'; import { getDefinitionName } from 'coral-framework/utils'; import truncate from 'lodash/truncate'; import t from 'coral-framework/services/i18n'; +import { subscriptionFields } from 'coral-admin/src/routes/Moderation/graphql'; function prepareNotificationText(text) { return truncate(text, { length: 50 }).replace('\n', ' '); @@ -74,29 +75,12 @@ class ModSubscription extends React.Component { } } -const fields = ` - status - actions { - __typename - created_at - } - status_history { - type - assigned_by { - id - } - created_at - } - updated_at - created_at -`; - const COMMENT_FEATURED_SUBSCRIPTION = gql` subscription CommentFeatured($assetId: ID){ commentFeatured(asset_id: $assetId) { comment { ...${getDefinitionName(Comment.fragments.comment)} - ${fields} + ${subscriptionFields} } user { id @@ -112,7 +96,7 @@ const COMMENT_UNFEATURED_SUBSCRIPTION = gql` commentUnfeatured(asset_id: $assetId){ comment { ...${getDefinitionName(Comment.fragments.comment)} - ${fields} + ${subscriptionFields} } user { id From 3fa2af975c327ec21832d80489a91356911b3f6b Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 26 Jan 2018 17:28:21 +0100 Subject: [PATCH 9/9] Bug fixes --- client/coral-admin/src/containers/Header.js | 9 +++++++-- .../src/routes/Moderation/containers/Indicator.js | 5 ++++- client/coral-admin/src/routes/Moderation/graphql.js | 11 ----------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/client/coral-admin/src/containers/Header.js b/client/coral-admin/src/containers/Header.js index dd16c17c6..d3be66764 100644 --- a/client/coral-admin/src/containers/Header.js +++ b/client/coral-admin/src/containers/Header.js @@ -7,11 +7,16 @@ import { getDefinitionName } from 'coral-framework/utils'; export default withQuery( gql` - query TalkAdmin_Header { + query TalkAdmin_Header($nullID: ID) { ...${getDefinitionName(ModerationIndicator.fragments.root)} ...${getDefinitionName(CommunityIndicator.fragments.root)} } ${ModerationIndicator.fragments.root} ${CommunityIndicator.fragments.root} - ` + `, + { + options: { + variables: { nullID: null }, + }, + } )(Header); diff --git a/client/coral-admin/src/routes/Moderation/containers/Indicator.js b/client/coral-admin/src/routes/Moderation/containers/Indicator.js index 359a2cd5d..1475b6e2d 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Indicator.js +++ b/client/coral-admin/src/routes/Moderation/containers/Indicator.js @@ -188,11 +188,14 @@ const enhance = compose( withFragments({ root: gql` fragment TalkAdmin_Moderation_Indicator_root on RootQuery { - premodCount: commentCount(query: { statuses: [PREMOD] }) + premodCount: commentCount( + query: { statuses: [PREMOD], asset_id: $nullID } + ) reportedCount: commentCount( query: { statuses: [NONE, PREMOD, SYSTEM_WITHHELD] action_type: FLAG + asset_id: $nullID } ) } diff --git a/client/coral-admin/src/routes/Moderation/graphql.js b/client/coral-admin/src/routes/Moderation/graphql.js index 44b4f6108..c542b8f84 100644 --- a/client/coral-admin/src/routes/Moderation/graphql.js +++ b/client/coral-admin/src/routes/Moderation/graphql.js @@ -112,23 +112,12 @@ function getOlderDate(a, b) { function determineLatestChange(comment) { let lc = null; - // Skip it when comment itself was not updated. - // We'll use the one from the status_history instead - // as they might diverge a little bit (status_history item is created - // before the comment itself) - if (comment.createdAt !== comment.updatedAt) { - lc = getOlderDate(lc, comment.createdAt); - lc = getOlderDate(lc, comment.updatedAt); - } - comment.status_history.forEach(item => { lc = getOlderDate(lc, item.created_at); - lc = getOlderDate(lc, item.updated_at); }); comment.actions.forEach(item => { lc = getOlderDate(lc, item.created_at); - lc = getOlderDate(lc, item.updated_at); }); return lc;