mirror of
https://github.com/wassname/talk.git
synced 2026-08-15 12:55:10 +08:00
Implement live update for reported users
This commit is contained in:
@@ -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:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
################################################################################
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user