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
This commit is contained in:
Angelina Filippova 2020-08-23 21:59:08 +00:00
commit b4ed2dc869
8 changed files with 125 additions and 23 deletions

View file

@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
### Added
- Allow managing user's actor_type field via Admin API
### Fixed
- Fix following and unfollowing relays from Admin-FE, update mobile UI

View file

@ -55,11 +55,11 @@ export async function fetchStatusesByInstance({ instance, authHost, token, pageS
'nickname': 'sky',
'url': 'http://localhost:4000/users/sky'
},
'content': 'A nice young couple contacted us from Brazil to decorate their newly acquired apartment.',
'created_at': '2020-01-31T18:20:01.000Z',
'id': '9rZIr0Jzao5Gjgfmro',
'content': 'i love parks&rec',
'created_at': '2020-04-12T18:20:01.000Z',
'id': 'o5Gjgfmro9rZIr0Jza',
'sensitive': false,
'url': 'http://localhost:4000/objects/7af9abbd-fb6c-4318-aeb7-6636c138ac98',
'url': 'http://localhost:4000/objects/7af9abbd-aeb7-6636c138ac98-fb6c-4318',
'visibility': 'unlisted'
},
{

View file

@ -1,8 +1,8 @@
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: '10', nickname: 'bob', local: false, external: true, roles: { admin: false, moderator: false }, tags: ['mrf_tag:sandbox'] },
{ 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: true, approval_pending: true, deactivated: false, id: '100', nickname: 'sally', local: true, external: false, roles: { admin: false, 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'], 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'], actor_type: 'Person' },
{ 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 }
@ -119,3 +119,7 @@ export async function createNewAccount(nickname, email, password, authHost, toke
users = [...users, newUser]
return Promise.resolve()
}
export async function updateUserCredentials(nickname, credentials, authHost, token) {
return Promise.resolve()
}

View file

@ -271,7 +271,9 @@ export default {
invalidNickname: 'invalid nickname',
passwordResetTokenGenerated: 'Password reset token was generated:',
linkToResetPassword: 'You can also use this link to reset password:',
registrationReason: 'Registration Reason'
registrationReason: 'Registration Reason',
service: 'Service',
person: 'Person'
},
statuses: {
statuses: 'Statuses',
@ -292,7 +294,8 @@ export default {
admin: 'Admin',
local: 'Local',
external: 'External',
accountType: 'Account type',
accountType: 'Account Type',
actorType: 'Actor Type',
nickname: 'Nickname',
recentStatuses: 'Recent Statuses',
roles: 'Roles',

View file

@ -16,7 +16,8 @@ import {
forcePasswordReset,
approveUserAccount,
confirmUserEmail,
resendConfirmationEmail
resendConfirmationEmail,
updateUserCredentials
} from '@/api/users'
const users = {
@ -277,6 +278,14 @@ const users = {
const currentFilters = { ...defaultFilters, ...filters }
commit('SET_USERS_FILTERS', currentFilters)
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 })
}
}
}

View file

@ -16,8 +16,16 @@
</el-button>
</div>
<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
v-if="showAdminAction(user)"
divided
@click.native="toggleUserRight(user, 'admin')">
{{ user.roles.admin ? $t('users.revokeAdmin') : $t('users.grantAdmin') }}
</el-dropdown-item>
@ -138,6 +146,19 @@ export default {
}
},
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() {
return this.$store.state.app.device === 'desktop'
}
@ -225,6 +246,39 @@ export default {
</script>
<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 {
text-align: left;
width: 350px;

View file

@ -46,10 +46,19 @@
<tbody>
<tr class="el-table__row">
<td class="name-col">ID</td>
<td class="value-col">
<td>
{{ user.id }}
</td>
</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">
<td>{{ $t('userProfile.tags') }}</td>
<td>

View file

@ -70,7 +70,7 @@ describe('Search and filter users', () => {
describe('Users actions', () => {
let store
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(() => {
store = new Vuex.Store(cloneDeep(storeConfig))
@ -88,7 +88,7 @@ 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')
wrapper.find(htmlElement(3, 2)).trigger('click')
const updatedUser = store.state.users.fetchedUsers[2]
expect(updatedUser.roles.admin).toBe(true)
@ -107,7 +107,7 @@ 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, 2)).trigger('click')
wrapper.find(htmlElement(3, 3)).trigger('click')
const updatedUser = store.state.users.fetchedUsers[2]
expect(updatedUser.roles.moderator).toBe(true)
@ -124,9 +124,9 @@ describe('Users actions', () => {
await flushPromises()
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()
})
@ -141,7 +141,7 @@ describe('Users actions', () => {
const user = store.state.users.fetchedUsers[1]
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]
expect(updatedUser.deactivated).toBe(true)
@ -158,7 +158,7 @@ describe('Users actions', () => {
await flushPromises()
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'] }] })
await flushPromises()
@ -180,8 +180,8 @@ describe('Users actions', () => {
expect(user1.tags.length).toBe(0)
expect(user2.tags.length).toBe(1)
wrapper.find(htmlElement(1, 5)).trigger('click')
wrapper.find(htmlElement(2, 5)).trigger('click')
wrapper.find(htmlElement(1, 6)).trigger('click')
wrapper.find(htmlElement(2, 6)).trigger('click')
const updatedUser1 = store.state.users.fetchedUsers[0]
const updatedUser2 = store.state.users.fetchedUsers[1]
@ -201,7 +201,7 @@ describe('Users actions', () => {
const user = store.state.users.fetchedUsers[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]
expect(updatedUser.tags.length).toBe(0)
@ -247,7 +247,7 @@ describe('Users actions', () => {
expect(wrapper.vm.resetPasswordDialogOpen).toBe(false)
expect(store.state.users.passwordResetToken.token).toBe('')
wrapper.find(htmlElement(1, 11)).trigger('click')
wrapper.find(htmlElement(1, 12)).trigger('click')
await flushPromises()
expect(wrapper.vm.resetPasswordDialogOpen).toBe(true)
@ -353,7 +353,26 @@ describe('Creates new account', () => {
expect(wrapper.vm.validatePassword(validatePasswordRule, '', identity)).toBeInstanceOf(Error)
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()
})
})