From f0899ec2639dd5f3fc09bd4324c14bdde8fd8ec5 Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Fri, 11 Oct 2019 02:49:16 +0300
Subject: [PATCH 01/12] Update api for AddRight and DeleteRight actions

---
 src/api/__mocks__/users.js |  2 +-
 src/api/users.js           | 14 ++++++++------
 src/store/modules/users.js | 24 ++++++++++++++++--------
 src/views/users/index.vue  |  4 +++-
 4 files changed, 28 insertions(+), 16 deletions(-)

diff --git a/src/api/__mocks__/users.js b/src/api/__mocks__/users.js
index 016772a7..07aca91e 100644
--- a/src/api/__mocks__/users.js
+++ b/src/api/__mocks__/users.js
@@ -48,7 +48,7 @@ export async function searchUsers(query, filters, authHost, token, page = 1) {
   }})
 }
 
-export async function addRight(nickname, right, authHost, token) {
+export async function addRight(nicknames, right, authHost, token) {
   return Promise.resolve({ data:
     { [`is_${right}`]: true }
   })
diff --git a/src/api/users.js b/src/api/users.js
index 1e97da92..9351cc45 100644
--- a/src/api/users.js
+++ b/src/api/users.js
@@ -2,12 +2,13 @@ import request from '@/utils/request'
 import { getToken } from '@/utils/auth'
 import { baseName } from './utils'
 
-export async function addRight(nickname, right, authHost, token) {
+export async function addRight(nicknames, right, authHost, token) {
   return await request({
     baseURL: baseName(authHost),
-    url: `/api/pleroma/admin/users/${nickname}/permission_group/${right}`,
+    url: `/api/pleroma/admin/users/permission_group/${right}`,
     method: 'post',
-    headers: authHeaders(token)
+    headers: authHeaders(token),
+    data: { nicknames }
   })
 }
 
@@ -21,12 +22,13 @@ export async function createNewAccount(nickname, email, password, authHost, toke
   })
 }
 
-export async function deleteRight(nickname, right, authHost, token) {
+export async function deleteRight(nicknames, right, authHost, token) {
   return await request({
     baseURL: baseName(authHost),
-    url: `/api/pleroma/admin/users/${nickname}/permission_group/${right}`,
+    url: `/api/pleroma/admin/users/permission_group/${right}`,
     method: 'delete',
-    headers: authHeaders(token)
+    headers: authHeaders(token),
+    data: { nicknames }
   })
 }
 
diff --git a/src/store/modules/users.js b/src/store/modules/users.js
index b3d35108..f087d930 100644
--- a/src/store/modules/users.js
+++ b/src/store/modules/users.js
@@ -76,6 +76,14 @@ const users = {
     }
   },
   actions: {
+    async AddRight({ dispatch, getters, state }, { users, right }) {
+      const usersNicknames = users.map(user => user.nickname)
+      await addRight(usersNicknames, right, getters.authHost, getters.token)
+
+      dispatch('FetchUsers', { page: state.currentPage })
+      // const updatedUser = { ...user, roles: { ...user.roles, [right]: !user.roles[right] }}
+      // commit('SWAP_USER', updatedUser)
+    },
     async AddTag({ commit, getters }, { users, tag }) {
       const nicknames = users.map(user => user.nickname)
       await tagUser(nicknames, [tag], getters.authHost, getters.token)
@@ -90,6 +98,14 @@ const users = {
       await createNewAccount(nickname, email, password, getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
     },
+    async DeleteRight({ dispatch, getters, state }, { users, right }) {
+      const usersNicknames = users.map(user => user.nickname)
+      await deleteRight(usersNicknames, right, getters.authHost, getters.token)
+
+      dispatch('FetchUsers', { page: state.currentPage })
+      // const updatedUser = { ...user, roles: { ...user.roles, [right]: !user.roles[right] }}
+      // commit('SWAP_USER', updatedUser)
+    },
     async DeleteUser({ commit, getters, state }, user) {
       const { data } = await deleteUser(user.nickname, getters.authHost, getters.token)
       const users = state.fetchedUsers.filter(user => user.nickname !== data)
@@ -146,14 +162,6 @@ const users = {
       const currentFilters = { ...defaultFilters, ...filters }
       commit('SET_USERS_FILTERS', currentFilters)
       dispatch('SearchUsers', { query: state.searchQuery, page: 1 })
-    },
-    async ToggleRight({ commit, getters }, { user, right }) {
-      user.roles[right]
-        ? await deleteRight(user.nickname, right, getters.authHost, getters.token)
-        : await addRight(user.nickname, right, getters.authHost, getters.token)
-
-      const updatedUser = { ...user, roles: { ...user.roles, [right]: !user.roles[right] }}
-      commit('SWAP_USER', updatedUser)
     }
   }
 }
diff --git a/src/views/users/index.vue b/src/views/users/index.vue
index 062b23c8..cce607dd 100644
--- a/src/views/users/index.vue
+++ b/src/views/users/index.vue
@@ -308,7 +308,9 @@ export default {
         : this.$store.dispatch('AddTag', { users: [user], tag })
     },
     toggleUserRight(user, right) {
-      this.$store.dispatch('ToggleRight', { user, right })
+      user.roles[right]
+        ? this.$store.dispatch('DeleteRight', { users: [user], right })
+        : this.$store.dispatch('AddRight', { users: [user], right })
     }
   }
 }
-- 
GitLab


From 77a153deac0c840ee25b0e12c046b546b90a91bf Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Fri, 11 Oct 2019 03:28:55 +0300
Subject: [PATCH 02/12] Update api for ActivateUsers and DeactivateUsers
 actions

---
 src/api/users.js                              | 29 +++++++++++++------
 src/store/modules/users.js                    | 15 ++++++----
 src/views/reports/components/TimelineItem.vue | 14 +++++----
 src/views/users/index.vue                     |  8 +++--
 4 files changed, 43 insertions(+), 23 deletions(-)

diff --git a/src/api/users.js b/src/api/users.js
index 9351cc45..12e13e6f 100644
--- a/src/api/users.js
+++ b/src/api/users.js
@@ -2,6 +2,16 @@ import request from '@/utils/request'
 import { getToken } from '@/utils/auth'
 import { baseName } from './utils'
 
+export async function activateUsers(nicknames, authHost, token) {
+  return await request({
+    baseURL: baseName(authHost),
+    url: `/api/pleroma/admin/users/activate`,
+    method: 'patch',
+    headers: authHeaders(token),
+    data: { nicknames }
+  })
+}
+
 export async function addRight(nicknames, right, authHost, token) {
   return await request({
     baseURL: baseName(authHost),
@@ -22,6 +32,16 @@ export async function createNewAccount(nickname, email, password, authHost, toke
   })
 }
 
+export async function deactivateUsers(nicknames, authHost, token) {
+  return await request({
+    baseURL: baseName(authHost),
+    url: `/api/pleroma/admin/users/deactivate`,
+    method: 'patch',
+    headers: authHeaders(token),
+    data: { nicknames }
+  })
+}
+
 export async function deleteRight(nicknames, right, authHost, token) {
   return await request({
     baseURL: baseName(authHost),
@@ -96,15 +116,6 @@ export async function tagUser(nicknames, tags, authHost, token) {
   })
 }
 
-export async function toggleUserActivation(nickname, authHost, token) {
-  return await request({
-    baseURL: baseName(authHost),
-    url: `/api/pleroma/admin/users/${nickname}/toggle_activation`,
-    method: 'patch',
-    headers: authHeaders(token)
-  })
-}
-
 export async function untagUser(nicknames, tags, authHost, token) {
   return await request({
     baseURL: baseName(authHost),
diff --git a/src/store/modules/users.js b/src/store/modules/users.js
index f087d930..f35ad9aa 100644
--- a/src/store/modules/users.js
+++ b/src/store/modules/users.js
@@ -1,13 +1,14 @@
 import {
+  activateUsers,
   addRight,
   createNewAccount,
+  deactivateUsers,
   deleteRight,
   deleteUser,
   fetchUsers,
   getPasswordResetToken,
   searchUsers,
   tagUser,
-  toggleUserActivation,
   untagUser,
   requirePasswordReset
 } from '@/api/users'
@@ -76,6 +77,10 @@ const users = {
     }
   },
   actions: {
+    async ActivateUsers({ commit, getters }, nicknames) {
+      const { data } = await activateUsers(nicknames, getters.authHost, getters.token)
+      commit('SWAP_USERS', data)
+    },
     async AddRight({ dispatch, getters, state }, { users, right }) {
       const usersNicknames = users.map(user => user.nickname)
       await addRight(usersNicknames, right, getters.authHost, getters.token)
@@ -98,6 +103,10 @@ const users = {
       await createNewAccount(nickname, email, password, getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
     },
+    async DeactivateUsers({ commit, getters }, nicknames) {
+      const { data } = await deactivateUsers(nicknames, getters.authHost, getters.token)
+      commit('SWAP_USERS', data)
+    },
     async DeleteRight({ dispatch, getters, state }, { users, right }) {
       const usersNicknames = users.map(user => user.nickname)
       await deleteRight(usersNicknames, right, getters.authHost, getters.token)
@@ -148,10 +157,6 @@ const users = {
         loadUsers(commit, page, response.data)
       }
     },
-    async ToggleUserActivation({ commit, getters }, nickname) {
-      const { data } = await toggleUserActivation(nickname, getters.authHost, getters.token)
-      commit('SWAP_USER', data)
-    },
     async ToggleUsersFilter({ commit, dispatch, state }, filters) {
       const defaultFilters = {
         local: false,
diff --git a/src/views/reports/components/TimelineItem.vue b/src/views/reports/components/TimelineItem.vue
index 771298aa..96433a22 100644
--- a/src/views/reports/components/TimelineItem.vue
+++ b/src/views/reports/components/TimelineItem.vue
@@ -21,7 +21,7 @@
             <el-dropdown-menu slot="dropdown">
               <el-dropdown-item
                 v-if="showDeactivatedButton(report.account)"
-                @click.native="handleDeactivation(report.account)">
+                @click.native="toggleActivation(report.account)">
                 {{ report.account.deactivated ? $t('users.activateAccount') : $t('users.deactivateAccount') }}
               </el-dropdown-item>
               <el-dropdown-item
@@ -139,17 +139,19 @@ export default {
           return 'primary'
       }
     },
+    handleDeletion(user) {
+      this.$store.dispatch('DeleteUser', user)
+    },
     parseTimestamp(timestamp) {
       return moment(timestamp).format('L HH:mm')
     },
     showDeactivatedButton(id) {
       return this.$store.state.user.id !== id
     },
-    handleDeactivation({ nickname }) {
-      this.$store.dispatch('ToggleUserActivation', nickname)
-    },
-    handleDeletion(user) {
-      this.$store.dispatch('DeleteUser', user)
+    toggleActivation({ deactivated, nickname }) {
+      deactivated
+        ? this.$store.dispatch('ActivateUsers', [nickname])
+        : this.$store.dispatch('DeactivateUsers', [nickname])
     },
     toggleTag(user, tag) {
       user.tags.includes(tag)
diff --git a/src/views/users/index.vue b/src/views/users/index.vue
index cce607dd..bd2cd7c4 100644
--- a/src/views/users/index.vue
+++ b/src/views/users/index.vue
@@ -80,7 +80,7 @@
               <el-dropdown-item
                 v-if="showDeactivatedButton(scope.row.id)"
                 :divided="showAdminAction(scope.row)"
-                @click.native="handleDeactivation(scope.row)">
+                @click.native="toggleActivation(scope.row)">
                 {{ scope.row.deactivated ? $t('users.activateAccount') : $t('users.deactivateAccount') }}
               </el-dropdown-item>
               <el-dropdown-item
@@ -275,8 +275,10 @@ export default {
 
       this.$store.dispatch('RequirePasswordReset', { nickname })
     },
-    handleDeactivation({ nickname }) {
-      this.$store.dispatch('ToggleUserActivation', nickname)
+    toggleActivation({ deactivated, nickname }) {
+      deactivated
+        ? this.$store.dispatch('ActivateUsers', [nickname])
+        : this.$store.dispatch('DeactivateUsers', [nickname])
     },
     handleDeletion(user) {
       this.$store.dispatch('DeleteUser', user)
-- 
GitLab


From 42f73e6c7900d6490f488e0727b85d5d71983a97 Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Sat, 12 Oct 2019 09:44:44 +0300
Subject: [PATCH 03/12] Pass users instead of nicknames to actions that toggle
 activation

---
 src/store/modules/users.js                    | 10 ++++++----
 src/views/reports/components/TimelineItem.vue |  8 ++++----
 src/views/users/index.vue                     |  8 ++++----
 3 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/src/store/modules/users.js b/src/store/modules/users.js
index f35ad9aa..b26024dc 100644
--- a/src/store/modules/users.js
+++ b/src/store/modules/users.js
@@ -77,8 +77,9 @@ const users = {
     }
   },
   actions: {
-    async ActivateUsers({ commit, getters }, nicknames) {
-      const { data } = await activateUsers(nicknames, getters.authHost, getters.token)
+    async ActivateUsers({ commit, getters }, users) {
+      const usersNicknames = users.map(user => user.nickname)
+      const { data } = await activateUsers(usersNicknames, getters.authHost, getters.token)
       commit('SWAP_USERS', data)
     },
     async AddRight({ dispatch, getters, state }, { users, right }) {
@@ -103,8 +104,9 @@ const users = {
       await createNewAccount(nickname, email, password, getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
     },
-    async DeactivateUsers({ commit, getters }, nicknames) {
-      const { data } = await deactivateUsers(nicknames, getters.authHost, getters.token)
+    async DeactivateUsers({ commit, getters }, users) {
+      const usersNicknames = users.map(user => user.nickname)
+      const { data } = await deactivateUsers(usersNicknames, getters.authHost, getters.token)
       commit('SWAP_USERS', data)
     },
     async DeleteRight({ dispatch, getters, state }, { users, right }) {
diff --git a/src/views/reports/components/TimelineItem.vue b/src/views/reports/components/TimelineItem.vue
index 96433a22..34336f79 100644
--- a/src/views/reports/components/TimelineItem.vue
+++ b/src/views/reports/components/TimelineItem.vue
@@ -148,10 +148,10 @@ export default {
     showDeactivatedButton(id) {
       return this.$store.state.user.id !== id
     },
-    toggleActivation({ deactivated, nickname }) {
-      deactivated
-        ? this.$store.dispatch('ActivateUsers', [nickname])
-        : this.$store.dispatch('DeactivateUsers', [nickname])
+    toggleActivation(user) {
+      user.deactivated
+        ? this.$store.dispatch('ActivateUsers', [user])
+        : this.$store.dispatch('DeactivateUsers', [user])
     },
     toggleTag(user, tag) {
       user.tags.includes(tag)
diff --git a/src/views/users/index.vue b/src/views/users/index.vue
index bd2cd7c4..b5cf1251 100644
--- a/src/views/users/index.vue
+++ b/src/views/users/index.vue
@@ -275,10 +275,10 @@ export default {
 
       this.$store.dispatch('RequirePasswordReset', { nickname })
     },
-    toggleActivation({ deactivated, nickname }) {
-      deactivated
-        ? this.$store.dispatch('ActivateUsers', [nickname])
-        : this.$store.dispatch('DeactivateUsers', [nickname])
+    toggleActivation(user) {
+      user.deactivated
+        ? this.$store.dispatch('ActivateUsers', [user])
+        : this.$store.dispatch('DeactivateUsers', [user])
     },
     handleDeletion(user) {
       this.$store.dispatch('DeleteUser', user)
-- 
GitLab


From d0025dfe7a4400a4fbd1a7203a0b70ac1a9896ea Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Sat, 12 Oct 2019 09:45:43 +0300
Subject: [PATCH 04/12] Update test mocks for api calls that toggle activation

---
 src/api/__mocks__/users.js | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/src/api/__mocks__/users.js b/src/api/__mocks__/users.js
index 07aca91e..f2e2103b 100644
--- a/src/api/__mocks__/users.js
+++ b/src/api/__mocks__/users.js
@@ -33,11 +33,6 @@ export async function getPasswordResetToken(nickname, authHost, token) {
   return Promise.resolve({ data: { token: 'g05lxnBJQnL', link: 'http://url/api/pleroma/password_reset/g05lxnBJQnL' }})
 }
 
-export async function toggleUserActivation(nickname, authHost, token) {
-  const response = users.find(user => user.nickname === nickname)
-  return Promise.resolve({ data: { ...response, deactivated: !response.deactivated }})
-}
-
 export async function searchUsers(query, filters, authHost, token, page = 1) {
   const filteredUsers = filterUsers(filters)
   const response = filteredUsers.filter(user => user.nickname === query)
@@ -48,12 +43,29 @@ export async function searchUsers(query, filters, authHost, token, page = 1) {
   }})
 }
 
+export async function activateUsers(nicknames, authHost, token) {
+  const response = nicknames.map(nickname => {
+    const currentUser = users.find(user => user.nickname === nickname)
+    return { ...currentUser, deactivated: false }
+  })
+  return Promise.resolve({ data: response })
+}
+
 export async function addRight(nicknames, right, authHost, token) {
+  console.log(nicknames)
   return Promise.resolve({ data:
     { [`is_${right}`]: true }
   })
 }
 
+export async function deactivateUsers(nicknames, authHost, token) {
+  const response = nicknames.map(nickname => {
+    const currentUser = users.find(user => user.nickname === nickname)
+    return { ...currentUser, deactivated: true }
+  })
+  return Promise.resolve({ data: response })
+}
+
 export async function deleteRight(nickname, right, authHost, token) {
   return Promise.resolve({ data:
     { [`is_${right}`]: false }
-- 
GitLab


From 58573a7de8773c406e114633cd2b2f7bb5fc8be4 Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Sat, 12 Oct 2019 09:53:02 +0300
Subject: [PATCH 05/12] Remove multiple calls of functions that moderate users

---
 .../users/components/MultipleUsersMenu.vue    | 82 ++++++++-----------
 1 file changed, 32 insertions(+), 50 deletions(-)

diff --git a/src/views/users/components/MultipleUsersMenu.vue b/src/views/users/components/MultipleUsersMenu.vue
index ecd2baf2..2c6f630c 100644
--- a/src/views/users/components/MultipleUsersMenu.vue
+++ b/src/views/users/components/MultipleUsersMenu.vue
@@ -150,87 +150,69 @@ export default {
   },
   methods: {
     mappers() {
-      const applyActionToAllUsers = (filteredUsers, fn) => Promise.all(filteredUsers.map(fn))
-        .then(() => {
-          this.$message({
-            type: 'success',
-            message: this.$t('users.completed')
-          })
-          this.$emit('apply-action')
-        }).catch((err) => {
+      const applyAction = (users, dispatchAction) => {
+        try {
+          dispatchAction(users)
+        } catch (err) {
           console.log(err)
           return
+        }
+        this.$message({
+          type: 'success',
+          message: this.$t('users.completed')
         })
+        this.$emit('apply-action')
+      }
       return {
         grantRight: (right) => () => {
           const filterUsersFn = user => user.local && !user.roles[right] && this.$store.state.user.id !== user.id
-          const toggleRightFn = async(user) => await this.$store.dispatch('ToggleRight', { user, right })
+          const addRightFn = async(users) => await this.$store.dispatch('AddRight', { users, right })
           const filtered = this.selectedUsers.filter(filterUsersFn)
 
-          applyActionToAllUsers(filtered, toggleRightFn)
+          applyAction(filtered, addRightFn)
         },
         revokeRight: (right) => () => {
           const filterUsersFn = user => user.local && user.roles[right] && this.$store.state.user.id !== user.id
-          const toggleRightFn = async(user) => await this.$store.dispatch('ToggleRight', { user, right })
+          const deleteRightFn = async(users) => await this.$store.dispatch('DeleteRight', { users, right })
           const filtered = this.selectedUsers.filter(filterUsersFn)
 
-          applyActionToAllUsers(filtered, toggleRightFn)
+          applyAction(filtered, deleteRightFn)
         },
         activate: () => {
           const filtered = this.selectedUsers.filter(user => user.deactivated && this.$store.state.user.id !== user.id)
-          const toggleActivationFn = async(user) => await this.$store.dispatch('ToggleUserActivation', user.nickname)
+          const activateUsersFn = async(users) => await this.$store.dispatch('ActivateUsers', users)
 
-          applyActionToAllUsers(filtered, toggleActivationFn)
+          applyAction(filtered, activateUsersFn)
         },
         deactivate: () => {
           const filtered = this.selectedUsers.filter(user => !user.deactivated && this.$store.state.user.id !== user.id)
-          const toggleActivationFn = async(user) => await this.$store.dispatch('ToggleUserActivation', user.nickname)
+          const deactivateUsersFn = async(users) => await this.$store.dispatch('DeactivateUsers', users)
 
-          applyActionToAllUsers(filtered, toggleActivationFn)
+          applyAction(filtered, deactivateUsersFn)
         },
         remove: () => {
           const filtered = this.selectedUsers.filter(user => this.$store.state.user.id !== user.id)
           const deleteAccountFn = async(user) => await this.$store.dispatch('DeleteUser', user)
 
-          applyActionToAllUsers(filtered, deleteAccountFn)
+          applyAction(filtered, deleteAccountFn)
         },
-        addTag: (tag) => async() => {
-          const filterUsersFn = user => tag === 'disable_remote_subscription' || tag === 'disable_any_subscription'
-            ? user.local && !user.tags.includes(tag)
-            : !user.tags.includes(tag)
-          const users = this.selectedUsers.filter(filterUsersFn)
+        addTag: (tag) => () => {
+          const filtered = this.selectedUsers.filter(user =>
+            tag === 'disable_remote_subscription' || tag === 'disable_any_subscription'
+              ? user.local && !user.tags.includes(tag)
+              : !user.tags.includes(tag))
+          const addTagFn = async(users) => await this.$store.dispatch('AddTag', { users, tag })
 
-          try {
-            await this.$store.dispatch('AddTag', { users, tag })
-          } catch (err) {
-            console.log(err)
-            return
-          }
-
-          this.$message({
-            type: 'success',
-            message: this.$t('users.completed')
-          })
-          this.$emit('apply-action')
+          applyAction(filtered, addTagFn)
         },
         removeTag: (tag) => async() => {
-          const filterUsersFn = user => tag === 'disable_remote_subscription' || tag === 'disable_any_subscription'
-            ? user.local && user.tags.includes(tag)
-            : user.tags.includes(tag)
-          const users = this.selectedUsers.filter(filterUsersFn)
-
-          try {
-            await this.$store.dispatch('RemoveTag', { users, tag })
-          } catch (err) {
-            console.log(err)
-            return
-          }
+          const filtered = this.selectedUsers.filter(user =>
+            tag === 'disable_remote_subscription' || tag === 'disable_any_subscription'
+              ? user.local && user.tags.includes(tag)
+              : user.tags.includes(tag))
+          const removeTagFn = async(users) => await this.$store.dispatch('RemoveTag', { users, tag })
 
-          this.$message({
-            type: 'success',
-            message: this.$t('users.completed')
-          })
-          this.$emit('apply-action')
+          applyAction(filtered, removeTagFn)
         },
         requirePasswordReset: () => {
           this.selectedUsers.map(user => this.$store.dispatch('RequirePasswordReset', user))
-- 
GitLab


From 4d58527201cdbeea34e00ec3f6fb093402ab7b68 Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Wed, 16 Oct 2019 14:24:35 +0200
Subject: [PATCH 06/12] Update api function and module action for user deletion

---
 src/api/users.js                              |  7 +--
 src/store/modules/users.js                    | 45 ++++++++-----------
 src/views/reports/components/TimelineItem.vue |  2 +-
 src/views/users/index.vue                     |  2 +-
 4 files changed, 24 insertions(+), 32 deletions(-)

diff --git a/src/api/users.js b/src/api/users.js
index 12e13e6f..fb168d6c 100644
--- a/src/api/users.js
+++ b/src/api/users.js
@@ -52,12 +52,13 @@ export async function deleteRight(nicknames, right, authHost, token) {
   })
 }
 
-export async function deleteUser(nickname, authHost, token) {
+export async function deleteUsers(nicknames, authHost, token) {
   return await request({
     baseURL: baseName(authHost),
-    url: `/api/pleroma/admin/users?nickname=${nickname}`,
+    url: `/api/pleroma/admin/users`,
     method: 'delete',
-    headers: authHeaders(token)
+    headers: authHeaders(token),
+    data: { nicknames }
   })
 }
 
diff --git a/src/store/modules/users.js b/src/store/modules/users.js
index b26024dc..497f0b87 100644
--- a/src/store/modules/users.js
+++ b/src/store/modules/users.js
@@ -4,7 +4,7 @@ import {
   createNewAccount,
   deactivateUsers,
   deleteRight,
-  deleteUser,
+  deleteUsers,
   fetchUsers,
   getPasswordResetToken,
   searchUsers,
@@ -77,24 +77,20 @@ const users = {
     }
   },
   actions: {
-    async ActivateUsers({ commit, getters }, users) {
+    async ActivateUsers({ dispatch, getters, state }, users) {
       const usersNicknames = users.map(user => user.nickname)
-      const { data } = await activateUsers(usersNicknames, getters.authHost, getters.token)
-      commit('SWAP_USERS', data)
+      await activateUsers(usersNicknames, getters.authHost, getters.token)
+      dispatch('FetchUsers', { page: state.currentPage })
     },
     async AddRight({ dispatch, getters, state }, { users, right }) {
       const usersNicknames = users.map(user => user.nickname)
       await addRight(usersNicknames, right, getters.authHost, getters.token)
-
       dispatch('FetchUsers', { page: state.currentPage })
-      // const updatedUser = { ...user, roles: { ...user.roles, [right]: !user.roles[right] }}
-      // commit('SWAP_USER', updatedUser)
     },
-    async AddTag({ commit, getters }, { users, tag }) {
+    async AddTag({ dispatch, getters, state }, { users, tag }) {
       const nicknames = users.map(user => user.nickname)
       await tagUser(nicknames, [tag], getters.authHost, getters.token)
-
-      commit('SWAP_USERS', users.map(user => ({ ...user, tags: [...user.tags, tag] })))
+      dispatch('FetchUsers', { page: state.currentPage })
     },
     async ClearFilters({ commit, dispatch, state }) {
       commit('CLEAR_USERS_FILTERS')
@@ -104,46 +100,41 @@ const users = {
       await createNewAccount(nickname, email, password, getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
     },
-    async DeactivateUsers({ commit, getters }, users) {
+    async DeactivateUsers({ dispatch, getters, state }, users) {
       const usersNicknames = users.map(user => user.nickname)
-      const { data } = await deactivateUsers(usersNicknames, getters.authHost, getters.token)
-      commit('SWAP_USERS', data)
+      await deactivateUsers(usersNicknames, getters.authHost, getters.token)
+      dispatch('FetchUsers', { page: state.currentPage })
     },
     async DeleteRight({ dispatch, getters, state }, { users, right }) {
       const usersNicknames = users.map(user => user.nickname)
       await deleteRight(usersNicknames, right, getters.authHost, getters.token)
-
       dispatch('FetchUsers', { page: state.currentPage })
-      // const updatedUser = { ...user, roles: { ...user.roles, [right]: !user.roles[right] }}
-      // commit('SWAP_USER', updatedUser)
     },
-    async DeleteUser({ commit, getters, state }, user) {
-      const { data } = await deleteUser(user.nickname, getters.authHost, getters.token)
-      const users = state.fetchedUsers.filter(user => user.nickname !== data)
-      commit('SET_USERS', users)
+    async DeleteUsers({ dispatch, getters, state }, users) {
+      const usersNicknames = users.map(user => user.nickname)
+      await deleteUsers(usersNicknames, getters.authHost, getters.token)
     },
-    async RequirePasswordReset({ commit, getters, state }, user) {
+    async RequirePasswordReset({ getters }, user) {
       await requirePasswordReset(user.nickname, getters.authHost, getters.token)
     },
-    async FetchUsers({ commit, state, getters, dispatch }, { page }) {
+    async FetchUsers({ commit, dispatch, getters, state }, { page }) {
       commit('SET_LOADING', true)
       const filters = Object.keys(state.filters).filter(filter => state.filters[filter]).join()
       const response = await fetchUsers(filters, getters.authHost, getters.token, page)
-      await dispatch('GetNodeInfo')
+      // await dispatch('GetNodeInfo')
       loadUsers(commit, page, response.data)
     },
-    async GetPasswordResetToken({ commit, state, getters }, nickname) {
+    async GetPasswordResetToken({ commit, getters }, nickname) {
       const { data } = await getPasswordResetToken(nickname, getters.authHost, getters.token)
       commit('SET_PASSWORD_RESET_TOKEN', data)
     },
     RemovePasswordToken({ commit }) {
       commit('SET_PASSWORD_RESET_TOKEN', { link: '', token: '' })
     },
-    async RemoveTag({ commit, getters }, { users, tag }) {
+    async RemoveTag({ dispatch, getters, state }, { users, tag }) {
       const nicknames = users.map(user => user.nickname)
       await untagUser(nicknames, [tag], getters.authHost, getters.token)
-
-      commit('SWAP_USERS', users.map(user => ({ ...user, tags: user.tags.filter(userTag => userTag !== tag) })))
+      dispatch('FetchUsers', { page: state.currentPage })
     },
     async SearchUsers({ commit, dispatch, state, getters }, { query, page }) {
       if (query.length === 0) {
diff --git a/src/views/reports/components/TimelineItem.vue b/src/views/reports/components/TimelineItem.vue
index 34336f79..e7e41e7b 100644
--- a/src/views/reports/components/TimelineItem.vue
+++ b/src/views/reports/components/TimelineItem.vue
@@ -140,7 +140,7 @@ export default {
       }
     },
     handleDeletion(user) {
-      this.$store.dispatch('DeleteUser', user)
+      this.$store.dispatch('DeleteUsers', [user])
     },
     parseTimestamp(timestamp) {
       return moment(timestamp).format('L HH:mm')
diff --git a/src/views/users/index.vue b/src/views/users/index.vue
index b5cf1251..bd255c7b 100644
--- a/src/views/users/index.vue
+++ b/src/views/users/index.vue
@@ -281,7 +281,7 @@ export default {
         : this.$store.dispatch('DeactivateUsers', [user])
     },
     handleDeletion(user) {
-      this.$store.dispatch('DeleteUser', user)
+      this.$store.dispatch('DeleteUsers', [user])
     },
     handlePageChange(page) {
       const searchQuery = this.$store.state.users.searchQuery
-- 
GitLab


From d47b3287bdd594c78d1d3844695115f6f08936e1 Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Wed, 16 Oct 2019 21:00:25 +0200
Subject: [PATCH 07/12] Fix user deletion for multiple users

---
 src/views/users/components/MultipleUsersMenu.vue | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/views/users/components/MultipleUsersMenu.vue b/src/views/users/components/MultipleUsersMenu.vue
index 2c6f630c..53f778b8 100644
--- a/src/views/users/components/MultipleUsersMenu.vue
+++ b/src/views/users/components/MultipleUsersMenu.vue
@@ -150,9 +150,9 @@ export default {
   },
   methods: {
     mappers() {
-      const applyAction = (users, dispatchAction) => {
+      const applyAction = async(users, dispatchAction) => {
         try {
-          dispatchAction(users)
+          await dispatchAction(users)
         } catch (err) {
           console.log(err)
           return
@@ -192,7 +192,7 @@ export default {
         },
         remove: () => {
           const filtered = this.selectedUsers.filter(user => this.$store.state.user.id !== user.id)
-          const deleteAccountFn = async(user) => await this.$store.dispatch('DeleteUser', user)
+          const deleteAccountFn = async(users) => await this.$store.dispatch('DeleteUsers', users)
 
           applyAction(filtered, deleteAccountFn)
         },
-- 
GitLab


From 408b9dcc64bea70d8a6ad25ef121c20c2aa9f252 Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Wed, 16 Oct 2019 22:30:29 +0200
Subject: [PATCH 08/12] Fix displaying error message when there is no error
 response

---
 src/utils/request.js | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/utils/request.js b/src/utils/request.js
index bbed6501..51e74601 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -10,14 +10,19 @@ const service = axios.create({
 service.interceptors.response.use(
   response => response,
   error => {
+    let errorMessage
     console.log(`Error ${error}`)
-    console.log(error.response.data)
 
-    // If there's an "error" property in the json, use it
-    const edata = error.response.data.error ? error.response.data.error : error.response.data
+    if (error.response) {
+      // If there's an "error" property in the json, use it
+      const edata = error.response.data.error ? error.response.data.error : error.response.data
+      errorMessage = `${error.message} - ${edata}`
+    } else {
+      errorMessage = error
+    }
 
     Message({
-      message: `${error.message} - ${edata}`,
+      message: errorMessage,
       type: 'error',
       duration: 5 * 1000
     })
-- 
GitLab


From 6975f435272cd47c2374663ef4ea2e50a5a5197e Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Thu, 17 Oct 2019 22:42:04 +0200
Subject: [PATCH 09/12] Add optimistic update for actions that moderate users

---
 src/store/modules/users.js | 54 +++++++++++++++++++++++++++++---------
 1 file changed, 41 insertions(+), 13 deletions(-)

diff --git a/src/store/modules/users.js b/src/store/modules/users.js
index 497f0b87..c7b54010 100644
--- a/src/store/modules/users.js
+++ b/src/store/modules/users.js
@@ -38,12 +38,6 @@ const users = {
     SET_LOADING: (state, status) => {
       state.loading = status
     },
-    SWAP_USER: (state, updatedUser) => {
-      const updated = state.fetchedUsers.map(user => user.id === updatedUser.id ? updatedUser : user)
-      state.fetchedUsers = updated
-        .map(user => user.nickname ? user : { ...user, nickname: '' })
-        .sort((a, b) => a.nickname.localeCompare(b.nickname))
-    },
     SWAP_USERS: (state, users) => {
       const usersWithoutSwapped = users.reduce((acc, user) => {
         return acc.filter(u => u.id !== user.id)
@@ -77,17 +71,32 @@ const users = {
     }
   },
   actions: {
-    async ActivateUsers({ dispatch, getters, state }, users) {
+    async ActivateUsers({ commit, dispatch, getters, state }, users) {
+      const updatedUsers = users.map(user => {
+        return { ...user, deactivated: false }
+      })
+      commit('SWAP_USERS', updatedUsers)
+
       const usersNicknames = users.map(user => user.nickname)
       await activateUsers(usersNicknames, getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
     },
-    async AddRight({ dispatch, getters, state }, { users, right }) {
+    async AddRight({ commit, dispatch, getters, state }, { users, right }) {
+      const updatedUsers = users.map(user => {
+        return user.local ? { ...user, roles: { ...user.roles, [right]: true }} : user
+      })
+      commit('SWAP_USERS', updatedUsers)
+
       const usersNicknames = users.map(user => user.nickname)
       await addRight(usersNicknames, right, getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
     },
-    async AddTag({ dispatch, getters, state }, { users, tag }) {
+    async AddTag({ commit, dispatch, getters, state }, { users, tag }) {
+      const updatedUsers = users.map(user => {
+        return { ...user, tags: [...user.tags, tag] }
+      })
+      commit('SWAP_USERS', updatedUsers)
+
       const nicknames = users.map(user => user.nickname)
       await tagUser(nicknames, [tag], getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
@@ -100,17 +109,31 @@ const users = {
       await createNewAccount(nickname, email, password, getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
     },
-    async DeactivateUsers({ dispatch, getters, state }, users) {
+    async DeactivateUsers({ commit, dispatch, getters, state }, users) {
+      const updatedUsers = users.map(user => {
+        return { ...user, deactivated: true }
+      })
+      commit('SWAP_USERS', updatedUsers)
+
       const usersNicknames = users.map(user => user.nickname)
       await deactivateUsers(usersNicknames, getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
     },
-    async DeleteRight({ dispatch, getters, state }, { users, right }) {
+    async DeleteRight({ commit, dispatch, getters, state }, { users, right }) {
+      const updatedUsers = users.map(user => {
+        return user.local ? { ...user, roles: { ...user.roles, [right]: false }} : user
+      })
+      commit('SWAP_USERS', updatedUsers)
+
       const usersNicknames = users.map(user => user.nickname)
       await deleteRight(usersNicknames, right, getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
     },
-    async DeleteUsers({ dispatch, getters, state }, users) {
+    async DeleteUsers({ commit, getters, state }, users) {
+      const deletedUsersIds = users.map(deletedUser => deletedUser.id)
+      const updatedUsers = state.fetchedUsers.filter(user => !deletedUsersIds.includes(user.id))
+      commit('SET_USERS', updatedUsers)
+
       const usersNicknames = users.map(user => user.nickname)
       await deleteUsers(usersNicknames, getters.authHost, getters.token)
     },
@@ -131,7 +154,12 @@ const users = {
     RemovePasswordToken({ commit }) {
       commit('SET_PASSWORD_RESET_TOKEN', { link: '', token: '' })
     },
-    async RemoveTag({ dispatch, getters, state }, { users, tag }) {
+    async RemoveTag({ commit, dispatch, getters, state }, { users, tag }) {
+      const updatedUsers = users.map(user => {
+        return { ...user, tags: user.tags.filter(userTag => userTag !== tag) }
+      })
+      commit('SWAP_USERS', updatedUsers)
+
       const nicknames = users.map(user => user.nickname)
       await untagUser(nicknames, [tag], getters.authHost, getters.token)
       dispatch('FetchUsers', { page: state.currentPage })
-- 
GitLab


From fa2410d61409f926ecd047f0b2879d9daf8cbf42 Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Fri, 18 Oct 2019 13:57:48 +0200
Subject: [PATCH 10/12] Fix tests for updated user actions

---
 src/api/__mocks__/users.js                 |  5 +--
 test/views/users/index.test.js             | 46 +++++++++-------------
 test/views/users/multipleUsersMenu.test.js |  7 ----
 3 files changed, 20 insertions(+), 38 deletions(-)

diff --git a/src/api/__mocks__/users.js b/src/api/__mocks__/users.js
index f2e2103b..b5aca6a1 100644
--- a/src/api/__mocks__/users.js
+++ b/src/api/__mocks__/users.js
@@ -52,7 +52,6 @@ export async function activateUsers(nicknames, authHost, token) {
 }
 
 export async function addRight(nicknames, right, authHost, token) {
-  console.log(nicknames)
   return Promise.resolve({ data:
     { [`is_${right}`]: true }
   })
@@ -72,9 +71,9 @@ export async function deleteRight(nickname, right, authHost, token) {
   })
 }
 
-export async function deleteUser(nickname, authHost, token) {
+export async function deleteUsers(nicknames, authHost, token) {
   return Promise.resolve({ data:
-    nickname
+    nicknames
   })
 }
 
diff --git a/test/views/users/index.test.js b/test/views/users/index.test.js
index d435e1ce..63473aaf 100644
--- a/test/views/users/index.test.js
+++ b/test/views/users/index.test.js
@@ -75,7 +75,7 @@ describe('Users actions', () => {
     store = new Vuex.Store(cloneDeep(storeConfig))
   })
 
-  it('grants admin and moderator rights to a local user', async (done) => {
+  it('grants admin right to a local user', async (done) => {
     const wrapper = mount(Users, {
       store,
       localVue,
@@ -87,14 +87,28 @@ describe('Users actions', () => {
     const user = store.state.users.fetchedUsers[2]
     expect(user.roles.admin).toBe(false)
     expect(user.roles.moderator).toBe(false)
-
     wrapper.find(htmlElement(3, 1)).trigger('click')
+
+    const updatedUser = store.state.users.fetchedUsers[2]
+    expect(updatedUser.roles.admin).toBe(true)
+    done()
+  })
+
+  it('grants moderator right to a local user', async (done) => {
+    const wrapper = mount(Users, {
+      store,
+      localVue,
+      sync: false,
+      stubs: ['router-link']
+    })
     await flushPromises()
+
+    const user = store.state.users.fetchedUsers[2]
+    expect(user.roles.admin).toBe(false)
+    expect(user.roles.moderator).toBe(false)
     wrapper.find(htmlElement(3, 2)).trigger('click')
-    await flushPromises()
 
     const updatedUser = store.state.users.fetchedUsers[2]
-    expect(updatedUser.roles.admin).toBe(true)
     expect(updatedUser.roles.moderator).toBe(true)
     done()
   })
@@ -126,9 +140,7 @@ describe('Users actions', () => {
 
     const user = store.state.users.fetchedUsers[1]
     expect(user.deactivated).toBe(false)
-
     wrapper.find(htmlElement(2, 1)).trigger('click')
-    await flushPromises()
 
     const updatedUser = store.state.users.fetchedUsers[1]
     expect(updatedUser.deactivated).toBe(true)
@@ -166,9 +178,7 @@ describe('Users actions', () => {
     expect(user2.tags.length).toBe(1)
 
     wrapper.find(htmlElement(1, 5)).trigger('click')
-    await flushPromises()
     wrapper.find(htmlElement(2, 5)).trigger('click')
-    await flushPromises()
 
     const updatedUser1 = store.state.users.fetchedUsers[0]
     const updatedUser2 = store.state.users.fetchedUsers[1]
@@ -188,33 +198,13 @@ describe('Users actions', () => {
 
     const user = store.state.users.fetchedUsers[1]
     expect(user.tags.length).toBe(1)
-
     wrapper.find(htmlElement(2, 6)).trigger('click')
-    await flushPromises()
 
     const updatedUser = store.state.users.fetchedUsers[1]
     expect(updatedUser.tags.length).toBe(0)
     done()
   })
 
-  it('shows check icon when tag is added', async (done) => {
-    const wrapper = mount(Users, {
-      store,
-      localVue,
-      sync: false,
-      stubs: ['router-link']
-    })
-    await flushPromises()
-
-    expect(wrapper.find(`${htmlElement(1, 5)} i`).exists()).toBe(false)
-
-    wrapper.find(htmlElement(1, 5)).trigger('click')
-    await flushPromises()
-
-    expect(wrapper.find(`${htmlElement(1, 5)} i`).exists()).toBe(true)
-    done()
-  })
-
   it('does not change user index in array when tag is added', async (done) => {
     const wrapper = mount(Users, {
       store,
diff --git a/test/views/users/multipleUsersMenu.test.js b/test/views/users/multipleUsersMenu.test.js
index c92232bf..7770a501 100644
--- a/test/views/users/multipleUsersMenu.test.js
+++ b/test/views/users/multipleUsersMenu.test.js
@@ -51,7 +51,6 @@ describe('Apply users actions to multiple users', () => {
     expect(user2.roles.admin).toBe(false)
     expect(user3.roles.admin).toBe(false)
     grantRight('admin')()
-    await flushPromises()
 
     const updatedUser1 = store.state.users.fetchedUsers[0]
     const updatedUser2 = store.state.users.fetchedUsers[1]
@@ -88,7 +87,6 @@ describe('Apply users actions to multiple users', () => {
     expect(user2.roles.moderator).toBe(false)
     expect(user3.roles.moderator).toBe(false)
     grantRight('moderator')()
-    await flushPromises()
 
     const updatedUser1 = store.state.users.fetchedUsers[0]
     const updatedUser2 = store.state.users.fetchedUsers[1]
@@ -123,7 +121,6 @@ describe('Apply users actions to multiple users', () => {
     expect(user1.roles.admin).toBe(true)
     expect(user2.roles.admin).toBe(false)
     revokeRight('admin')()
-    await flushPromises()
 
     const updatedUser1 = store.state.users.fetchedUsers[0]
     const updatedUser2 = store.state.users.fetchedUsers[2]
@@ -173,7 +170,6 @@ describe('Apply users actions to multiple users', () => {
     const user = store.state.users.fetchedUsers[2]
     expect(user.deactivated).toBe(true)
     activate()
-    await flushPromises()
 
     const updatedUser = store.state.users.fetchedUsers[2]
     expect(updatedUser.deactivated).toBe(false)
@@ -203,7 +199,6 @@ describe('Apply users actions to multiple users', () => {
     expect(user1.deactivated).toBe(false)
     expect(user2.deactivated).toBe(false)
     deactivate()
-    await flushPromises()
 
     const updatedUser1 = store.state.users.fetchedUsers[0]
     const updatedUser2 = store.state.users.fetchedUsers[1]
@@ -270,7 +265,6 @@ describe('Apply users actions to multiple users', () => {
     expect(user1.tags.length).toBe(0)
     expect(user2.tags.length).toBe(1)
     addTag('strip_media')()
-    await flushPromises()
 
     const updatedUser1 = store.state.users.fetchedUsers[0]
     const updatedUser2 = store.state.users.fetchedUsers[1]
@@ -311,7 +305,6 @@ describe('Apply users actions to multiple users', () => {
     expect(user1.tags.length).toBe(1)
     expect(user2.tags.length).toBe(1)
     removeTag('strip_media')()
-    await flushPromises()
 
     const updatedUser1 = store.state.users.fetchedUsers[1]
     const updatedUser2 = store.state.users.fetchedUsers[2]
-- 
GitLab


From caba7eaefd9e2945a5eda918f470ad60d14c8ceb Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Fri, 18 Oct 2019 18:12:55 +0200
Subject: [PATCH 11/12] Return dispatching action that gets node info

---
 src/store/modules/users.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/store/modules/users.js b/src/store/modules/users.js
index c7b54010..df3c946d 100644
--- a/src/store/modules/users.js
+++ b/src/store/modules/users.js
@@ -144,7 +144,7 @@ const users = {
       commit('SET_LOADING', true)
       const filters = Object.keys(state.filters).filter(filter => state.filters[filter]).join()
       const response = await fetchUsers(filters, getters.authHost, getters.token, page)
-      // await dispatch('GetNodeInfo')
+      await dispatch('GetNodeInfo')
       loadUsers(commit, page, response.data)
     },
     async GetPasswordResetToken({ commit, getters }, nickname) {
-- 
GitLab


From 4bd8015dfa58a2a14a50247681c4b8f0deae179e Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Fri, 18 Oct 2019 18:27:46 +0200
Subject: [PATCH 12/12] Update Changelog

---
 CHANGELOG.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index be0fb971..8efd8c5c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Changed
 
 - `mailerEnabled` must be set to `true` in order to require password reset (password reset currently only works via email)
+- Actions in users module (ActivateUsers, AddRight, DeactivateUsers, DeleteRight, DeleteUsers) now accept an array of users instead of one user
+
+### Added
+- Optimistic update for actions in users module and fetching users after api function finished its execution
 
 ## [1.2.0] - 2019-09-27
 
-- 
GitLab