diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 31ad999dc..e45e3f095 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -3,7 +3,7 @@ import cn from 'classnames'; import PropTypes from 'prop-types'; import capitalize from 'lodash/capitalize'; import styles from './UserDetail.css'; -import AccountHistory from './AccountHistory'; +import UserHistory from './UserHistory'; import { Slot } from 'coral-framework/components'; import UserDetailCommentList from '../components/UserDetailCommentList'; import { @@ -28,26 +28,6 @@ import UserInfoTooltip from './UserInfoTooltip'; import t from 'coral-framework/services/i18n'; class UserDetail extends React.Component { - rejectThenReload = async info => { - await this.props.rejectComment(info); - this.props.data.refetch(); - }; - - acceptThenReload = async info => { - await this.props.acceptComment(info); - this.props.data.refetch(); - }; - - bulkAcceptThenReload = async () => { - await this.props.bulkAccept(); - this.props.data.refetch(); - }; - - bulkRejectThenReload = async () => { - await this.props.bulkReject(); - this.props.data.refetch(); - }; - changeTab = tab => { this.props.changeTab(tab); }; @@ -110,8 +90,14 @@ class UserDetail extends React.Component { unbanUser, unsuspendUser, modal, + acceptComment, + rejectComment, + bulkAccept, + bulkReject, } = this.props; + console.log(rejectedComments, totalComments); + // if totalComments is 0, you're dividing by zero let rejectedPercent = rejectedComments / totalComments * 100; @@ -286,7 +272,7 @@ class UserDetail extends React.Component { 'talk-admin-user-detail-history-tab' )} > - {t('user_detail.account_history')} + {t('user_detail.user_history')} @@ -304,12 +290,12 @@ class UserDetail extends React.Component { loadMore={loadMore} toggleSelect={toggleSelect} viewUserDetail={viewUserDetail} - acceptComment={this.acceptThenReload} - rejectComment={this.rejectThenReload} + acceptComment={acceptComment} + rejectComment={rejectComment} selectedCommentIds={selectedCommentIds} toggleSelectAll={toggleSelectAll} - bulkAcceptThenReload={this.bulkAcceptThenReload} - bulkRejectThenReload={this.bulkRejectThenReload} + bulkAcceptThenReload={bulkAccept} + bulkRejectThenReload={bulkReject} /> - + diff --git a/client/coral-admin/src/components/AccountHistory.css b/client/coral-admin/src/components/UserHistory.css similarity index 100% rename from client/coral-admin/src/components/AccountHistory.css rename to client/coral-admin/src/components/UserHistory.css diff --git a/client/coral-admin/src/components/AccountHistory.js b/client/coral-admin/src/components/UserHistory.js similarity index 73% rename from client/coral-admin/src/components/AccountHistory.js rename to client/coral-admin/src/components/UserHistory.js index 0d85b62af..3a3472c86 100644 --- a/client/coral-admin/src/components/AccountHistory.js +++ b/client/coral-admin/src/components/UserHistory.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { murmur3 } from 'murmurhash-js'; -import styles from './AccountHistory.css'; +import styles from './UserHistory.css'; import cn from 'classnames'; import flatten from 'lodash/flatten'; import orderBy from 'lodash/orderBy'; @@ -43,15 +43,15 @@ const readableDuration = (startDate, endDate) => { const buildActionResponse = (typename, created_at, until, status) => { switch (typename) { case 'UsernameStatusHistory': - return t('account_history.username_status', status); + return t('user_history.username_status', status); case 'BannedStatusHistory': return status - ? t('account_history.user_banned') - : t('account_history.ban_removed'); + ? t('user_history.user_banned') + : t('user_history.ban_removed'); case 'SuspensionStatusHistory': return until - ? t('account_history.suspended', readableDuration(created_at, until)) - : t('account_history.suspension_removed'); + ? t('user_history.suspended', readableDuration(created_at, until)) + : t('user_history.suspension_removed'); default: return '-'; } @@ -62,43 +62,41 @@ const getModerationValue = assignedBy => assignedBy.username ) : ( - {t('account_history.system')} + {t('user_history.system')} ); -class AccountHistory extends React.Component { +class UserHistory extends React.Component { render() { const { user } = this.props; const userHistory = buildUserHistory(user.state); return (
-
+
+
{t('user_history.date')}
- {t('account_history.date')} + {t('user_history.action')}
- {t('account_history.action')} -
-
- {t('account_history.taken_by')} + {t('user_history.taken_by')}
{userHistory.map( ({ __typename, created_at, assigned_by, until, status }) => (
{moment(new Date(created_at)).format('MMM DD, YYYY')} @@ -107,7 +105,7 @@ class AccountHistory extends React.Component { className={cn( styles.item, styles.action, - 'talk-admin-account-history-row-status' + 'talk-admin-user-history-row-status' )} > {buildActionResponse(__typename, created_at, until, status)} @@ -116,7 +114,7 @@ class AccountHistory extends React.Component { className={cn( styles.item, styles.username, - 'talk-admin-account-history-row-assigned-by' + 'talk-admin-user-history-row-assigned-by' )} > {getModerationValue(assigned_by)} @@ -130,8 +128,8 @@ class AccountHistory extends React.Component { } } -AccountHistory.propTypes = { +UserHistory.propTypes = { user: PropTypes.object.isRequired, }; -export default AccountHistory; +export default UserHistory; diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index fb1508a2f..360819dd6 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -148,6 +148,7 @@ UserDetailContainer.propTypes = { selectedCommentIds: PropTypes.array, unbanUser: PropTypes.func.isRequired, unsuspendUser: PropTypes.func.isRequired, + userId: PropTypes.string, }; const LOAD_MORE_QUERY = gql` @@ -245,7 +246,6 @@ export const withUserDetailQuery = withQuery( options: ({ userId, statuses }) => { return { variables: { author_id: userId, statuses }, - fetchPolicy: 'network-only', }; }, skip: ownProps => !ownProps.userId, diff --git a/client/coral-embed-stream/src/tabs/stream/components/InactiveCommentLabel.js b/client/coral-embed-stream/src/tabs/stream/components/InactiveCommentLabel.js index 3f0360099..48ea7767c 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/InactiveCommentLabel.js +++ b/client/coral-embed-stream/src/tabs/stream/components/InactiveCommentLabel.js @@ -18,6 +18,10 @@ const InactiveCommentLabel = ({ status, className, ...rest }) => { label = t('modqueue.rejected'); icon = 'close'; break; + case 'SYSTEM_WITHHELD': + label = t('modqueue.system_withheld'); + icon = 'flag'; + break; default: throw new Error(`Unknown inactive status ${status}`); } diff --git a/client/coral-framework/graphql/mutations.js b/client/coral-framework/graphql/mutations.js index 1b3b4c6fc..bd31be630 100644 --- a/client/coral-framework/graphql/mutations.js +++ b/client/coral-framework/graphql/mutations.js @@ -1,5 +1,6 @@ import { gql } from 'react-apollo'; import withMutation from '../hocs/withMutation'; +import update from 'immutability-helper'; function convertItemType(item_type) { switch (item_type) { @@ -167,9 +168,39 @@ export const withSetCommentStatus = withMutation( errors: null, }, }, + updateQueries: { + CoralAdmin_UserDetail: prev => { + const increment = { + rejectedComments: { + $apply: count => + count < prev.totalComments ? count + 1 : count, + }, + }; + + const decrement = { + rejectedComments: { + $apply: count => (count > 0 ? count - 1 : 0), + }, + }; + + // If rejected then increment rejectedComments by one + if (status === 'REJECTED') { + const updated = update(prev, increment); + return updated; + } + + // If approved then decrement rejectedComments by one + if (status === 'ACCEPTED') { + const updated = update(prev, decrement); + return updated; + } + + return prev; + }, + }, update: proxy => { const fragment = gql` - fragment Talk_SetCommentStatus on Comment { + fragment Talk_SetCommentStatus_Comment on Comment { status status_history { type @@ -182,9 +213,11 @@ export const withSetCommentStatus = withMutation( const data = proxy.readFragment({ fragment, id: fragmentId }); data.status = status; + data.status_history = data.status_history ? data.status_history : []; + data.status_history.push({ __typename: 'CommentStatusHistory', type: status, diff --git a/locales/ar.yml b/locales/ar.yml index 3c94c06f0..98f6cc54e 100644 --- a/locales/ar.yml +++ b/locales/ar.yml @@ -438,8 +438,8 @@ ar: reports: "Reports" all: "All" rejected: "Rejected" - account_history: "Account History" - account_history: + user_history: "User History" + user_history: user_banned: "User banned" ban_removed: "Ban removed" username_status: "Username {0}" diff --git a/locales/da.yml b/locales/da.yml index 9618b2f58..0afe4c5ce 100644 --- a/locales/da.yml +++ b/locales/da.yml @@ -431,8 +431,8 @@ da: reports: "Rapporter" all: "Alle" rejected: "Afvist" - account_history: "Konto historik" - account_history: + user_history: "Konto historik" + user_history: user_banned: "Bruger bannet" ban_removed: "Ban fjernet" username_status: "Brugernavn {0}" diff --git a/locales/de.yml b/locales/de.yml index 972c2735b..6f7a1a66e 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -430,8 +430,8 @@ de: reports: "Meldungen" all: "Alle" rejected: "Abgelehnte" - account_history: "Konto-Verlauf" - account_history: + user_history: "Konto-Verlauf" + user_history: user_banned: "User banned" ban_removed: "Ban removed" username_status: "Username {0}" diff --git a/locales/en.yml b/locales/en.yml index f4c477710..cb8051938 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -347,6 +347,7 @@ en: sort: "Sort" show_shortcuts: "Show Shortcuts" singleview: "Zen mode" + system_withheld: "System Withheld" thismenu: "Open this menu" jump_to_queue: "Jump to specific queue" thousand: k @@ -449,8 +450,8 @@ en: reports: "Reports" all: "All" rejected: "Rejected" - account_history: "Account History" - account_history: + user_history: "User History" + user_history: user_banned: "User banned" ban_removed: "Ban removed" username_status: "Username {0}" diff --git a/locales/es.yml b/locales/es.yml index 4e7a2c51b..dd85b588e 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -439,8 +439,8 @@ es: reports: "Reports" all: "All" rejected: "Rejected" - account_history: "Account History" - account_history: + user_history: "User History" + user_history: user_banned: "User banned" ban_removed: "Ban removed" username_status: "Username {0}" diff --git a/locales/nl_NL.yml b/locales/nl_NL.yml index aa80f58be..9d4057065 100644 --- a/locales/nl_NL.yml +++ b/locales/nl_NL.yml @@ -431,8 +431,8 @@ nl_NL: reports: "Rapportages" all: "Alle" rejected: "Afgewezen" - account_history: "Accountgeschiedenis" - account_history: + user_history: "Accountgeschiedenis" + user_history: user_banned: "User banned" ban_removed: "Ban removed" username_status: "Username {0}" diff --git a/locales/pt_BR.yml b/locales/pt_BR.yml index 86267d51d..d9ef119ed 100644 --- a/locales/pt_BR.yml +++ b/locales/pt_BR.yml @@ -430,8 +430,8 @@ pt_BR: reports: "Reports" all: "All" rejected: "Rejected" - account_history: "Account History" - account_history: + user_history: "User History" + user_history: user_banned: "User banned" ban_removed: "Ban removed" username_status: "Username {0}" diff --git a/locales/zh_CN.yml b/locales/zh_CN.yml index 59e028eb7..b3307a886 100644 --- a/locales/zh_CN.yml +++ b/locales/zh_CN.yml @@ -432,8 +432,8 @@ zh_CN: reports: "Reports" all: "All" rejected: "Rejected" - account_history: "Account History" - account_history: + user_history: "User History" + user_history: user_banned: "User banned" ban_removed: "Ban removed" username_status: "Username {0}" diff --git a/locales/zh_TW.yml b/locales/zh_TW.yml index 1b131f636..c186a667f 100644 --- a/locales/zh_TW.yml +++ b/locales/zh_TW.yml @@ -432,8 +432,8 @@ zh_TW: reports: "Reports" all: "All" rejected: "Rejected" - account_history: "Account History" - account_history: + user_history: "User History" + user_history: user_banned: "User banned" ban_removed: "Ban removed" username_status: "Username {0}" diff --git a/models/schema/comment.js b/models/schema/comment.js index 985f5c399..a211884ff 100644 --- a/models/schema/comment.js +++ b/models/schema/comment.js @@ -191,6 +191,17 @@ Comment.index( } ); +// Add an index that is optimized for finding a user's comments. +Comment.index( + { + author_id: 1, + created_at: -1, + }, + { + background: true, + } +); + // Optimize for tag searches/counts. Comment.index( { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..d6105c594 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "talk", + "version": "4.3.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" + }, + "react-side-effect": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-1.1.5.tgz", + "integrity": "sha512-Z2ZJE4p/jIfvUpiUMRydEVpQRf2f8GMHczT6qLcARmX7QRb28JDBTpnM2g/i5y/p7ZDEXYGHWg0RbhikE+hJRw==", + "requires": { + "exenv": "1.2.2", + "shallowequal": "1.0.2" + } + }, + "shallowequal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.0.2.tgz", + "integrity": "sha512-zlVXeVUKvo+HEv1e2KQF/csyeMKx2oHvatQ9l6XjCUj3agvC8XGf6R9HvIPDSmp8FNPvx7b5kaEJTRi7CqxtEw==" + } + } +} diff --git a/test/e2e/page_objects/admin.js b/test/e2e/page_objects/admin.js index 218afe425..8e319e499 100644 --- a/test/e2e/page_objects/admin.js +++ b/test/e2e/page_objects/admin.js @@ -128,8 +128,8 @@ module.exports = { rejectedTab: '.talk-admin-user-detail-rejected-tab', historyTab: '.talk-admin-user-detail-history-tab', historyPane: '.talk-admin-user-detail-history-tab-pane', - accountHistory: '.talk-admin-account-history', - accountHistoryRowStatus: '.talk-admin-account-history-row-status', + UserHistory: '.talk-admin-user-history', + UserHistoryRowStatus: '.talk-admin-user-history-row-status', actionsMenu: '.talk-admin-user-detail-actions-button', actionItemSuspendUser: '.action-menu-item#suspendUser', actionMenuButton: diff --git a/test/e2e/specs/06_suspendUser.js b/test/e2e/specs/06_suspendUser.js index 0b89c5c01..81fde3781 100644 --- a/test/e2e/specs/06_suspendUser.js +++ b/test/e2e/specs/06_suspendUser.js @@ -125,7 +125,7 @@ module.exports = { .waitForElementVisible('@historyTab') .click('@historyTab') .waitForElementVisible('@historyPane') - .waitForElementVisible('@accountHistory') + .waitForElementVisible('@UserHistory') .click('@closeButton'); }, 'admin logs out': client => {