Implement live update for reported users

This commit is contained in:
Chi Vinh Le
2018-01-17 15:20:38 +01:00
parent 519480c0e4
commit ee78f08598
12 changed files with 247 additions and 4 deletions
@@ -17,6 +17,7 @@ class FlaggedAccounts extends React.Component {
approveUser,
me,
viewUserDetail,
hasMore,
} = this.props;
const hasResults = users.nodes && !!users.nodes.length;
@@ -60,7 +61,7 @@ class FlaggedAccounts extends React.Component {
) : (
<EmptyCard>{t('community.no_flagged_accounts')}</EmptyCard>
)}
<LoadMore loadMore={loadMore} showLoadMore={users.hasNextPage} />
<LoadMore loadMore={loadMore} showLoadMore={hasMore} />
</div>
</div>
);
@@ -70,6 +71,7 @@ class FlaggedAccounts extends React.Component {
FlaggedAccounts.propTypes = {
users: PropTypes.object,
loadMore: PropTypes.func,
hasMore: PropTypes.bool,
showRejectUsernameDialog: PropTypes.func,
approveUser: PropTypes.func,
me: PropTypes.object,
@@ -110,10 +110,12 @@ class User extends React.Component {
<div className={styles.actions}>
<ApproveButton
className="talk-admin-flagged-user-approve-button"
active={user.state.status.username.status === 'APPROVED'}
onClick={this.approveUser}
/>
<RejectButton
className="talk-admin-flagged-user-reject-button"
active={user.state.status.username.status === 'REJECTED'}
onClick={this.showRejectUsernameDialog}
/>
</div>
@@ -11,15 +11,88 @@ 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 { handleFlaggedUserChange } from '../graphql';
import { notify } from 'coral-framework/actions/notification';
import FlaggedAccounts from '../components/FlaggedAccounts';
import FlaggedUser from '../containers/FlaggedUser';
class FlaggedAccountsContainer extends Component {
subscriptions = [];
constructor(props) {
super(props);
}
subscribeToUpdates() {
const parameters = [
{
document: USERNAME_FLAGGED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameFlagged: user } } }
) => {
return handleFlaggedUserChange(prev, user, () => {
this.props.notify('info', `user ${user.username} flagged`);
});
},
},
{
document: USERNAME_APPROVED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameApproved: user } } }
) => {
console.log(user);
return handleFlaggedUserChange(prev, user, () => {
this.props.notify('info', `user ${user.username} approved`);
});
},
},
{
document: USERNAME_REJECTED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameRejected: user } } }
) => {
console.log(user);
return handleFlaggedUserChange(prev, user, () => {
this.props.notify('info', `user ${user.username} rejected`);
});
},
},
{
document: USERNAME_CHANGED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameChanged: user } } }
) => {
console.log(user);
return handleFlaggedUserChange(prev, user, () => {
this.props.notify('info', `user ${user.username} changed`);
});
},
},
];
this.subscriptions = parameters.map(param =>
this.props.data.subscribeToMore(param)
);
}
unsubscribe() {
this.subscriptions.forEach(unsubscribe => unsubscribe());
this.subscriptions = [];
}
componentWillMount() {
this.subscribeToUpdates();
}
componentWillUnmount() {
this.unsubscribe();
}
approveUser = ({ userId: id }) => {
return this.props.approveUsername(id);
};
@@ -68,6 +141,10 @@ class FlaggedAccountsContainer extends Component {
data={this.props.data}
root={this.props.root}
users={this.props.root.flaggedUsers}
hasMore={
this.props.root.flaggedUsers.nodes.length <
this.props.root.flaggedUsernamesCount
}
me={this.props.root.me}
/>
);
@@ -77,6 +154,8 @@ class FlaggedAccountsContainer extends Component {
FlaggedAccountsContainer.propTypes = {
showRejectUsernameDialog: PropTypes.func,
viewUserDetail: PropTypes.func,
notify: PropTypes.func,
flaggedUsernamesCount: PropTypes.number,
approveUsername: PropTypes.func,
data: PropTypes.object,
root: PropTypes.object,
@@ -105,11 +184,48 @@ const LOAD_MORE_QUERY = gql`
${FlaggedUser.fragments.user}
`;
const USERNAME_FLAGGED_SUBSCRIPTION = gql`
subscription TalkAdmin_UsernameFlagged {
usernameFlagged {
...${getDefinitionName(FlaggedUser.fragments.user)}
}
}
${FlaggedUser.fragments.user}
`;
const USERNAME_APPROVED_SUBSCRIPTION = gql`
subscription TalkAdmin_UsernameApproved {
usernameApproved {
...${getDefinitionName(FlaggedUser.fragments.user)}
}
}
${FlaggedUser.fragments.user}
`;
const USERNAME_REJECTED_SUBSCRIPTION = gql`
subscription TalkAdmin_UsernameRejected {
usernameRejected {
...${getDefinitionName(FlaggedUser.fragments.user)}
}
}
${FlaggedUser.fragments.user}
`;
const USERNAME_CHANGED_SUBSCRIPTION = gql`
subscription TalkAdmin_UsernameChanged {
usernameChanged {
...${getDefinitionName(FlaggedUser.fragments.user)}
}
}
${FlaggedUser.fragments.user}
`;
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
showRejectUsernameDialog,
viewUserDetail,
notify,
},
dispatch
);
@@ -120,6 +236,12 @@ export default compose(
withFragments({
root: gql`
fragment TalkAdminCommunity_FlaggedAccounts_root on RootQuery {
flaggedUsernamesCount: userCount(
query: {
action_type: FLAG
state: { status: { username: [SET, CHANGED] } }
}
)
flaggedUsers: users(query:{
action_type: FLAG,
state: {
@@ -17,6 +17,7 @@ export default withFragments({
fragment TalkAdminCommunity_FlaggedUser_user on User {
id
username
created_at
state {
status {
username {
@@ -0,0 +1,91 @@
import update from 'immutability-helper';
function shouldAddFlaggedUser(root, user) {
const isEmpty = !root.flaggedUsers.nodes.length;
if (isEmpty) {
return true;
}
if (hasFlaggedUser(root, user)) {
return false;
}
const oldest = root.flaggedUsers.nodes.reduce((cur, node) => {
const createdAt = new Date(node.created_at);
return createdAt < cur ? createdAt : cur;
}, new Date());
return new Date(user.created_at) >= oldest;
}
function hasFlaggedUser(root, user) {
return root.flaggedUsers.nodes.find(u => u.id === user.id);
}
function applyUserChanges(root, user) {
const index = root.flaggedUsers.nodes.findIndex(({ id }) => id === user.id);
if (index > -1) {
return update(root, {
flaggedUsers: {
nodes: {
[index]: { $merge: user },
},
},
});
}
return root;
}
function incrementFlaggedUserCount(root) {
return update(root, {
flaggedUsernamesCount: { $apply: count => count + 1 },
});
}
/**
* Assimilate flagged user 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 handleFlaggedUserChange(root, user, notify) {
if (!hasFlaggedUser(root, user)) {
switch (user.state.status.username.status) {
case 'SET':
case 'CHANGED':
root = incrementFlaggedUserCount(root);
if (!shouldAddFlaggedUser(root, user)) {
return root;
}
notify();
return update(root, {
flaggedUsernamesCount: { $apply: count => count + 1 },
flaggedUsers: {
nodes: { $push: [user] },
},
});
break;
case 'APPROVED':
case 'REJECTED':
return root;
default:
}
}
if (hasFlaggedUser(root, user)) {
switch (user.state.status.username.status) {
case 'SET':
case 'CHANGED':
return root;
case 'APPROVED':
case 'REJECTED':
notify();
return applyUserChanges(root, user);
default:
}
}
}
+1 -1
View File
@@ -83,7 +83,7 @@ const createAction = async (
case 'USERS':
// The item is a user, and this is a flag. Push that the user was
// flagged, don't wait for it to finish.
pubsub.publish('userFlagged', item);
pubsub.publish('usernameFlagged', item);
break;
default:
}
+3 -1
View File
@@ -56,7 +56,9 @@ const stopIgnoringUser = ({ user }, userToStopIgnoring) => {
};
const changeUsername = async (ctx, id, username) => {
return UsersService.changeUsername(id, username, ctx.user.id);
const user = await UsersService.changeUsername(id, username, ctx.user.id);
ctx.pubsub.publish('usernameChanged', user);
return user;
};
const setUsername = async (ctx, id, username) => {
+6
View File
@@ -23,12 +23,18 @@ const Subscription = {
userSuspended(user) {
return user;
},
usernameApproved(user) {
return user;
},
usernameRejected(user) {
return user;
},
usernameFlagged(user) {
return user;
},
usernameChanged(user) {
return user;
},
};
module.exports = Subscription;
+11
View File
@@ -10,6 +10,7 @@ const {
SUBSCRIBE_ALL_USERNAME_REJECTED,
SUBSCRIBE_ALL_USERNAME_APPROVED,
SUBSCRIBE_ALL_USERNAME_FLAGGED,
SUBSCRIBE_ALL_USERNAME_CHANGED,
} = require('../perms/constants');
const merge = require('lodash/merge');
@@ -128,6 +129,16 @@ const setupFunctions = {
}
return !args.user_id || user.id === args.user_id;
},
usernameChanged: (options, args, user, context) => {
if (
!context.user ||
(args.user_id !== user.id &&
!context.user.can(SUBSCRIBE_ALL_USERNAME_CHANGED))
) {
return false;
}
return !args.user_id || user.id === args.user_id;
},
};
/**
+5 -1
View File
@@ -1516,9 +1516,13 @@ type Subscription {
# users with the `ADMIN` or `MODERATOR` role.
usernameRejected(user_id: ID): User
# Gen an update whenever a username has been approved. `user_id` must match id
# Get an update whenever a username has been approved. `user_id` must match id
# of current user except for users with the `ADMIN` or `MODERATOR` role.
usernameApproved(user_id: ID): User
# Get an update whenever a username has been changed. `user_id` must match id
# of current user except for users with the `ADMIN` or `MODERATOR` role.
usernameChanged(user_id: ID): User
}
################################################################################
+1
View File
@@ -10,4 +10,5 @@ module.exports = {
SUBSCRIBE_ALL_USERNAME_REJECTED: 'SUBSCRIBE_ALL_USERNAME_REJECTED',
SUBSCRIBE_ALL_USERNAME_APPROVED: 'SUBSCRIBE_ALL_USERNAME_APPROVED',
SUBSCRIBE_ALL_USERNAME_FLAGGED: 'SUBSCRIBE_ALL_USERNAME_FLAGGED',
SUBSCRIBE_ALL_USERNAME_CHANGED: 'SUBSCRIBE_ALL_USERNAME_CHANGED',
};
+1
View File
@@ -14,6 +14,7 @@ module.exports = (user, perm) => {
case types.SUBSCRIBE_ALL_USERNAME_REJECTED:
case types.SUBSCRIBE_ALL_USERNAME_APPROVED:
case types.SUBSCRIBE_ALL_USERNAME_FLAGGED:
case types.SUBSCRIBE_ALL_USERNAME_CHANGED:
return check(user, ['ADMIN', 'MODERATOR']);
default:
break;