Skip to content
Snippets Groups Projects
Commit b4ed2dc8 authored by Angelina Filippova's avatar Angelina Filippova
Browse files

Merge branch 'feature/allow-setting-actor-type' into 'develop'

Allow setting actor_type field via Admin API.

Closes #114

See merge request pleroma/admin-fe!161
parents 3471f888 50bd7782
No related branches found
No related tags found
No related merge requests found
...@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ...@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased ## Unreleased
### Added
- Allow managing user's actor_type field via Admin API
### Fixed ### Fixed
- Fix following and unfollowing relays from Admin-FE, update mobile UI - Fix following and unfollowing relays from Admin-FE, update mobile UI
......
...@@ -55,11 +55,11 @@ export async function fetchStatusesByInstance({ instance, authHost, token, pageS ...@@ -55,11 +55,11 @@ export async function fetchStatusesByInstance({ instance, authHost, token, pageS
'nickname': 'sky', 'nickname': 'sky',
'url': 'http://localhost:4000/users/sky' 'url': 'http://localhost:4000/users/sky'
}, },
'content': 'A nice young couple contacted us from Brazil to decorate their newly acquired apartment.', 'content': 'i love parks&rec',
'created_at': '2020-01-31T18:20:01.000Z', 'created_at': '2020-04-12T18:20:01.000Z',
'id': '9rZIr0Jzao5Gjgfmro', 'id': 'o5Gjgfmro9rZIr0Jza',
'sensitive': false, 'sensitive': false,
'url': 'http://localhost:4000/objects/7af9abbd-fb6c-4318-aeb7-6636c138ac98', 'url': 'http://localhost:4000/objects/7af9abbd-aeb7-6636c138ac98-fb6c-4318',
'visibility': 'unlisted' 'visibility': 'unlisted'
}, },
{ {
......
export let users = [ export let users = [
{ active: true, approval_pending: false, deactivated: false, id: '2', nickname: 'allis', local: true, external: false, roles: { admin: true, moderator: false }, tags: [] }, { active: true, approval_pending: false, deactivated: false, id: '2', nickname: 'allis', local: true, external: false, roles: { admin: true, moderator: false }, tags: [], actor_type: 'Person' },
{ active: true, approval_pending: false, deactivated: false, id: '10', nickname: 'bob', local: false, external: true, roles: { admin: false, moderator: false }, tags: ['mrf_tag:sandbox'] }, { active: true, approval_pending: false, deactivated: false, id: '10', nickname: 'bob', local: false, external: true, roles: { admin: false, moderator: false }, tags: ['mrf_tag:sandbox'], actor_type: 'Person' },
{ active: false, approval_pending: false, deactivated: true, id: 'abc', nickname: 'john', local: true, external: false, roles: { admin: false, moderator: false }, tags: ['mrf_tag:media-strip'] }, { active: false, approval_pending: false, deactivated: true, id: 'abc', nickname: 'john', local: true, external: false, roles: { admin: false, moderator: false }, tags: ['mrf_tag:media-strip'], actor_type: 'Person' },
{ active: true, approval_pending: true, deactivated: false, id: '100', nickname: 'sally', local: true, external: false, roles: { admin: false, moderator: false }, tags: [] } { active: true, approval_pending: true, deactivated: false, id: '100', nickname: 'sally', local: true, external: false, roles: { admin: false, moderator: false }, tags: [], actor_type: 'Service' }
] ]
const userProfile = { avatar: 'avatar.jpg', nickname: 'allis', id: '2', tags: [], roles: { admin: true, moderator: false }, local: true, external: false } const userProfile = { avatar: 'avatar.jpg', nickname: 'allis', id: '2', tags: [], roles: { admin: true, moderator: false }, local: true, external: false }
...@@ -119,3 +119,7 @@ export async function createNewAccount(nickname, email, password, authHost, toke ...@@ -119,3 +119,7 @@ export async function createNewAccount(nickname, email, password, authHost, toke
users = [...users, newUser] users = [...users, newUser]
return Promise.resolve() return Promise.resolve()
} }
export async function updateUserCredentials(nickname, credentials, authHost, token) {
return Promise.resolve()
}
...@@ -271,7 +271,9 @@ export default { ...@@ -271,7 +271,9 @@ export default {
invalidNickname: 'invalid nickname', invalidNickname: 'invalid nickname',
passwordResetTokenGenerated: 'Password reset token was generated:', passwordResetTokenGenerated: 'Password reset token was generated:',
linkToResetPassword: 'You can also use this link to reset password:', linkToResetPassword: 'You can also use this link to reset password:',
registrationReason: 'Registration Reason' registrationReason: 'Registration Reason',
service: 'Service',
person: 'Person'
}, },
statuses: { statuses: {
statuses: 'Statuses', statuses: 'Statuses',
...@@ -292,7 +294,8 @@ export default { ...@@ -292,7 +294,8 @@ export default {
admin: 'Admin', admin: 'Admin',
local: 'Local', local: 'Local',
external: 'External', external: 'External',
accountType: 'Account type', accountType: 'Account Type',
actorType: 'Actor Type',
nickname: 'Nickname', nickname: 'Nickname',
recentStatuses: 'Recent Statuses', recentStatuses: 'Recent Statuses',
roles: 'Roles', roles: 'Roles',
......
...@@ -16,7 +16,8 @@ import { ...@@ -16,7 +16,8 @@ import {
forcePasswordReset, forcePasswordReset,
approveUserAccount, approveUserAccount,
confirmUserEmail, confirmUserEmail,
resendConfirmationEmail resendConfirmationEmail,
updateUserCredentials
} from '@/api/users' } from '@/api/users'
const users = { const users = {
...@@ -277,6 +278,14 @@ const users = { ...@@ -277,6 +278,14 @@ const users = {
const currentFilters = { ...defaultFilters, ...filters } const currentFilters = { ...defaultFilters, ...filters }
commit('SET_USERS_FILTERS', currentFilters) commit('SET_USERS_FILTERS', currentFilters)
dispatch('SearchUsers', { query: state.searchQuery, page: 1 }) dispatch('SearchUsers', { query: state.searchQuery, page: 1 })
},
async UpdateActorType({ dispatch, getters }, { user, type, _userId, _statusId }) {
const updatedUsers = [{ ...user, actor_type: type }]
const credentials = { actor_type: type }
const callApiFn = async() => await updateUserCredentials(user.nickname, credentials, getters.authHost, getters.token)
dispatch('ApplyChanges', { updatedUsers, callApiFn, userId: _userId, statusId: _statusId })
} }
} }
} }
......
...@@ -16,8 +16,16 @@ ...@@ -16,8 +16,16 @@
</el-button> </el-button>
</div> </div>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item
class="actor-type-dropdown">
<el-select v-model="actorType" :placeholder="$t('userProfile.actorType')" class="actor-type-select">
<el-option :label="$t('users.service')" value="Service"/>
<el-option :label="$t('users.person')" value="Person"/>
</el-select>
</el-dropdown-item>
<el-dropdown-item <el-dropdown-item
v-if="showAdminAction(user)" v-if="showAdminAction(user)"
divided
@click.native="toggleUserRight(user, 'admin')"> @click.native="toggleUserRight(user, 'admin')">
{{ user.roles.admin ? $t('users.revokeAdmin') : $t('users.grantAdmin') }} {{ user.roles.admin ? $t('users.revokeAdmin') : $t('users.grantAdmin') }}
</el-dropdown-item> </el-dropdown-item>
...@@ -138,6 +146,19 @@ export default { ...@@ -138,6 +146,19 @@ export default {
} }
}, },
computed: { computed: {
actorType: {
get() {
return this.user.actor_type
},
set(type) {
this.$store.dispatch('UpdateActorType', {
user: this.user,
type,
_userId: this.user.id,
_statusId: this.statusId
})
}
},
isDesktop() { isDesktop() {
return this.$store.state.app.device === 'desktop' return this.$store.state.app.device === 'desktop'
} }
...@@ -225,6 +246,39 @@ export default { ...@@ -225,6 +246,39 @@ export default {
</script> </script>
<style rel='stylesheet/scss' lang='scss'> <style rel='stylesheet/scss' lang='scss'>
.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided.actor-type-dropdown:before {
margin: 0 0;
height: 0;
}
.el-dropdown-menu--small .actor-type-dropdown {
padding: 0;
}
.actor-type-select {
width: 100%;
input {
border-color: transparent;
color: #606266;
}
.el-input__inner:hover {
border-color: transparent;
background-color: #ecf5ff;
}
.el-input.is-focus {
border-color: transparent;
}
.el-input__suffix-inner {
pointer-events: none;
}
.el-select .el-input__inner:focus {
border-color: transparent;
}
.el-input.is-active .el-input__inner, .el-input__inner:focus {
border-color: transparent;
}
}
.actor-type-select .el-input.is-focus .el-input__inner {
border-color: transparent;
}
.moderate-user-button { .moderate-user-button {
text-align: left; text-align: left;
width: 350px; width: 350px;
......
...@@ -46,10 +46,19 @@ ...@@ -46,10 +46,19 @@
<tbody> <tbody>
<tr class="el-table__row"> <tr class="el-table__row">
<td class="name-col">ID</td> <td class="name-col">ID</td>
<td class="value-col"> <td>
{{ user.id }} {{ user.id }}
</td> </td>
</tr> </tr>
<tr class="el-table__row">
<td>{{ $t('userProfile.actorType') }}</td>
<td>
<el-tag
:type="userCredentials.actor_type === 'Person' ? 'success' : 'warning'">
{{ userCredentials.actor_type }}
</el-tag>
</td>
</tr>
<tr class="el-table__row"> <tr class="el-table__row">
<td>{{ $t('userProfile.tags') }}</td> <td>{{ $t('userProfile.tags') }}</td>
<td> <td>
......
...@@ -70,7 +70,7 @@ describe('Search and filter users', () => { ...@@ -70,7 +70,7 @@ describe('Search and filter users', () => {
describe('Users actions', () => { describe('Users actions', () => {
let store let store
const htmlElement = (trChild, liChild) => const htmlElement = (trChild, liChild) =>
`.el-table__fixed-body-wrapper table tr:nth-child(${trChild}) ul.el-dropdown-menu li:nth-child(${liChild})` `.el-table__fixed-body-wrapper table tr:nth-child(${trChild}) ul.el-dropdown-menu > li:nth-child(${liChild})`
beforeEach(() => { beforeEach(() => {
store = new Vuex.Store(cloneDeep(storeConfig)) store = new Vuex.Store(cloneDeep(storeConfig))
...@@ -88,7 +88,7 @@ describe('Users actions', () => { ...@@ -88,7 +88,7 @@ describe('Users actions', () => {
const user = store.state.users.fetchedUsers[2] const user = store.state.users.fetchedUsers[2]
expect(user.roles.admin).toBe(false) expect(user.roles.admin).toBe(false)
expect(user.roles.moderator).toBe(false) expect(user.roles.moderator).toBe(false)
wrapper.find(htmlElement(3, 1)).trigger('click') wrapper.find(htmlElement(3, 2)).trigger('click')
const updatedUser = store.state.users.fetchedUsers[2] const updatedUser = store.state.users.fetchedUsers[2]
expect(updatedUser.roles.admin).toBe(true) expect(updatedUser.roles.admin).toBe(true)
...@@ -107,7 +107,7 @@ describe('Users actions', () => { ...@@ -107,7 +107,7 @@ describe('Users actions', () => {
const user = store.state.users.fetchedUsers[2] const user = store.state.users.fetchedUsers[2]
expect(user.roles.admin).toBe(false) expect(user.roles.admin).toBe(false)
expect(user.roles.moderator).toBe(false) expect(user.roles.moderator).toBe(false)
wrapper.find(htmlElement(3, 2)).trigger('click') wrapper.find(htmlElement(3, 3)).trigger('click')
const updatedUser = store.state.users.fetchedUsers[2] const updatedUser = store.state.users.fetchedUsers[2]
expect(updatedUser.roles.moderator).toBe(true) expect(updatedUser.roles.moderator).toBe(true)
...@@ -124,9 +124,9 @@ describe('Users actions', () => { ...@@ -124,9 +124,9 @@ describe('Users actions', () => {
await flushPromises() await flushPromises()
const dropdownMenuItems = wrapper.findAll( const dropdownMenuItems = wrapper.findAll(
`.el-table__fixed-body-wrapper table tr:nth-child(2) ul.el-dropdown-menu li` `.el-table__fixed-body-wrapper table tr:nth-child(2) ul.el-dropdown-menu > li`
) )
expect(dropdownMenuItems.length).toBe(6) expect(dropdownMenuItems.length).toBe(7)
done() done()
}) })
...@@ -141,7 +141,7 @@ describe('Users actions', () => { ...@@ -141,7 +141,7 @@ describe('Users actions', () => {
const user = store.state.users.fetchedUsers[1] const user = store.state.users.fetchedUsers[1]
expect(user.deactivated).toBe(false) expect(user.deactivated).toBe(false)
wrapper.find(htmlElement(2, 1)).trigger('click') wrapper.find(htmlElement(2, 2)).trigger('click')
const updatedUser = store.state.users.fetchedUsers[1] const updatedUser = store.state.users.fetchedUsers[1]
expect(updatedUser.deactivated).toBe(true) expect(updatedUser.deactivated).toBe(true)
...@@ -158,7 +158,7 @@ describe('Users actions', () => { ...@@ -158,7 +158,7 @@ describe('Users actions', () => {
await flushPromises() await flushPromises()
expect(store.state.users.fetchedUsers[1].deactivated).toBe(false) expect(store.state.users.fetchedUsers[1].deactivated).toBe(false)
wrapper.find(htmlElement(2, 2)).trigger('click') wrapper.find(htmlElement(2, 3)).trigger('click')
store.dispatch('DeleteUsers', { users: [{ active: true, deactivated: false, id: '10', nickname: 'bob', local: false, external: true, roles: { admin: false, moderator: false }, tags: ['mrf_tag:sandbox'] }] }) store.dispatch('DeleteUsers', { users: [{ active: true, deactivated: false, id: '10', nickname: 'bob', local: false, external: true, roles: { admin: false, moderator: false }, tags: ['mrf_tag:sandbox'] }] })
await flushPromises() await flushPromises()
...@@ -180,8 +180,8 @@ describe('Users actions', () => { ...@@ -180,8 +180,8 @@ describe('Users actions', () => {
expect(user1.tags.length).toBe(0) expect(user1.tags.length).toBe(0)
expect(user2.tags.length).toBe(1) expect(user2.tags.length).toBe(1)
wrapper.find(htmlElement(1, 5)).trigger('click') wrapper.find(htmlElement(1, 6)).trigger('click')
wrapper.find(htmlElement(2, 5)).trigger('click') wrapper.find(htmlElement(2, 6)).trigger('click')
const updatedUser1 = store.state.users.fetchedUsers[0] const updatedUser1 = store.state.users.fetchedUsers[0]
const updatedUser2 = store.state.users.fetchedUsers[1] const updatedUser2 = store.state.users.fetchedUsers[1]
...@@ -201,7 +201,7 @@ describe('Users actions', () => { ...@@ -201,7 +201,7 @@ describe('Users actions', () => {
const user = store.state.users.fetchedUsers[1] const user = store.state.users.fetchedUsers[1]
expect(user.tags.length).toBe(1) expect(user.tags.length).toBe(1)
wrapper.find(htmlElement(2, 6)).trigger('click') wrapper.find(htmlElement(2, 7)).trigger('click')
const updatedUser = store.state.users.fetchedUsers[1] const updatedUser = store.state.users.fetchedUsers[1]
expect(updatedUser.tags.length).toBe(0) expect(updatedUser.tags.length).toBe(0)
...@@ -247,7 +247,7 @@ describe('Users actions', () => { ...@@ -247,7 +247,7 @@ describe('Users actions', () => {
expect(wrapper.vm.resetPasswordDialogOpen).toBe(false) expect(wrapper.vm.resetPasswordDialogOpen).toBe(false)
expect(store.state.users.passwordResetToken.token).toBe('') expect(store.state.users.passwordResetToken.token).toBe('')
wrapper.find(htmlElement(1, 11)).trigger('click') wrapper.find(htmlElement(1, 12)).trigger('click')
await flushPromises() await flushPromises()
expect(wrapper.vm.resetPasswordDialogOpen).toBe(true) expect(wrapper.vm.resetPasswordDialogOpen).toBe(true)
...@@ -353,7 +353,26 @@ describe('Creates new account', () => { ...@@ -353,7 +353,26 @@ describe('Creates new account', () => {
expect(wrapper.vm.validatePassword(validatePasswordRule, '', identity)).toBeInstanceOf(Error) expect(wrapper.vm.validatePassword(validatePasswordRule, '', identity)).toBeInstanceOf(Error)
expect(wrapper.vm.validatePassword(validatePasswordRule, '1234', identity)).toBeUndefined() expect(wrapper.vm.validatePassword(validatePasswordRule, '1234', identity)).toBeUndefined()
})
it('updates actor type', async (done) => {
const wrapper = mount(Users, {
store,
localVue,
sync: false,
stubs: ['router-link']
})
await flushPromises()
const user = store.state.users.fetchedUsers[0]
expect(user.actor_type).toBe('Person')
const findWrapper = (trChild, liChild1, liChild2) =>
`.el-table__fixed-body-wrapper table tr:nth-child(${trChild}) ul.el-dropdown-menu > li:nth-child(${liChild1}) ul li:nth-child(${liChild2})`
wrapper.find(findWrapper(1, 1, 1)).trigger('click')
const updatedUser = store.state.users.fetchedUsers[0]
expect(updatedUser.actor_type).toBe('Service')
done()
}) })
}) })
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment