Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2bd1d16
perf(settings): Remove computation of all groups
Pytal Mar 25, 2025
fcf42c4
fix(settings): Fix infinitely loading account management page with pa…
Pytal Mar 25, 2025
a1a4988
feat(provisioning_api): Add endpoint for fetching user groups with de…
Pytal Mar 25, 2025
8a3a388
perf(settings): Cancel request on new search
Pytal Mar 25, 2025
ace13ca
fix(settings): Allow searching for groups in user row
Pytal Mar 25, 2025
1c7ea50
fix(settings): Allow searching for groups in new account dialog
Pytal Mar 25, 2025
a1bf497
perf(settings): Make scrolling smooth when a large number of groups a…
Pytal Mar 25, 2025
bf01685
refactor(settings): Consolidate group formatting
Pytal Mar 25, 2025
374197c
chore(settings): Add note on groups sorting
Pytal Mar 25, 2025
d32f2c7
fix(settings): Fix loaded groups being undefined
Pytal Mar 25, 2025
a5885d4
fix(settings): Prevent selection of invalid groups that are not fully…
Pytal Mar 25, 2025
8e0f507
fix(settings): Fix erroneous hiding of group admin column with pagina…
Pytal Mar 25, 2025
7e4d264
feat(provisioning_api): Add endpoint for fetching user subadmin group…
Pytal Mar 25, 2025
31ffa33
fix(settings): Fix editing groups and subadmin groups of user
Pytal Mar 25, 2025
6edbeb7
fix(settings): Only change usercount if group can be found
Pytal Mar 25, 2025
b093eff
fix(settings): Fix group creation when editing users
Pytal Mar 25, 2025
82f0957
fix(settings): Fix group creation in new account dialog
Pytal Mar 25, 2025
d32b76b
fix(settings): Fix duplicated group options when editing account
Pytal Mar 25, 2025
eb60f6a
fix(settings): Fix duplicated group options in new account dialog
Pytal Mar 25, 2025
7c976a9
fix(settings): Natural order groups
Pytal Mar 25, 2025
e2c2419
chore(openapi): Update spec
Pytal Mar 25, 2025
06dbcde
fix(settings): Preserve system groups on reset
Pytal Mar 25, 2025
f46f36b
fix(settings): Fix initialization of store
Pytal Mar 25, 2025
b9b44ca
fix(settings): Separate subadmin options
Pytal Mar 25, 2025
7b47c5a
test(settings): Wait until groups list has loaded
Pytal Mar 25, 2025
c617f26
test(settings): Correctly find group in select
Pytal Mar 25, 2025
1d0ae0c
test(settings): Fix group items not being found
Pytal Mar 25, 2025
437146b
chore(assets): Recompile assets
nextcloud-command Mar 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(settings): Allow searching for groups in user row
Signed-off-by: Christopher Ng <[email protected]>
  • Loading branch information
Pytal committed Mar 28, 2025
commit ace13ca64ff202d9d726ee8c9cf2086c9a7415cc
7 changes: 0 additions & 7 deletions apps/settings/src/components/UserList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
users,
settings,
hasObfuscated,
groups,
subAdminsGroups,
quotaOptions,
languages,
externalActions,
Expand Down Expand Up @@ -179,11 +177,6 @@ export default {
.sort((a, b) => a.name.localeCompare(b.name))
},

subAdminsGroups() {
// data provided php side
return this.$store.getters.getSubadminGroups
},

quotaOptions() {
// convert the preset array into objects
const quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({
Expand Down
81 changes: 63 additions & 18 deletions apps/settings/src/components/Users/UserRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@
label="name"
:no-wrap="true"
:create-option="(value) => ({ name: value, isCreating: true })"
@open="loadGroupDetails"
@search="searchGroups"
@option:created="createGroup"
@option:selected="options => addUserGroup(options.at(-1))"
@option:deselected="removeUserGroup" />
Expand All @@ -127,10 +129,10 @@
</span>
</td>

<td v-if="subAdminsGroups.length > 0 && (settings.isAdmin || settings.isDelegatedAdmin)"
<td v-if="userSubAdminGroups.length > 0 && (settings.isAdmin || settings.isDelegatedAdmin)"
data-cy-user-list-cell-subadmins
class="row__cell row__cell--large row__cell--multiline">
<template v-if="editing && (settings.isAdmin || settings.isDelegatedAdmin) && subAdminsGroups.length > 0">
<template v-if="editing && (settings.isAdmin || settings.isDelegatedAdmin) && userSubAdminGroups.length > 0">
<label class="hidden-visually"
:for="'subadmins' + uniqueId">
{{ t('settings', 'Set account as admin for') }}
Expand All @@ -145,15 +147,17 @@
:append-to-body="false"
:multiple="true"
:no-wrap="true"
:options="subAdminsGroups"
:options="availableSubAdminGroups"
:placeholder="t('settings', 'Set account as admin for')"
:value="userSubAdminsGroups"
:value="userSubAdminGroups"
@open="loadGroupDetails"
@search="searchGroups"
@option:deselected="removeUserSubAdmin"
@option:selected="options => addUserSubAdmin(options.at(-1))" />
</template>
<span v-else-if="!isObfuscated"
:title="userSubAdminsGroupsLabels?.length > 40 ? userSubAdminsGroupsLabels : null">
{{ userSubAdminsGroupsLabels }}
:title="userSubAdminGroupsLabels?.length > 40 ? userSubAdminGroupsLabels : null">
{{ userSubAdminGroupsLabels }}
</span>
</td>

Expand Down Expand Up @@ -296,6 +300,8 @@ import UserRowActions from './UserRowActions.vue'

import UserRowMixin from '../../mixins/UserRowMixin.js'
import { isObfuscated, unlimitedQuota } from '../../utils/userUtils.ts'
import { formatGroup } from '../../utils/groups.ts'
import logger from '../../logger.ts'

export default {
name: 'UserRow',
Expand Down Expand Up @@ -330,14 +336,6 @@ export default {
type: Boolean,
required: true,
},
groups: {
type: Array,
default: () => [],
},
subAdminsGroups: {
type: Array,
required: true,
},
quotaOptions: {
type: Array,
required: true,
Expand Down Expand Up @@ -381,6 +379,8 @@ export default {
editedDisplayName: this.user.displayname,
editedPassword: '',
editedMail: this.user.email ?? '',
// Cancelable promise for search groups request
promise: null,
}
},

Expand Down Expand Up @@ -412,13 +412,13 @@ export default {

userGroupsLabels() {
return this.userGroups
.map(group => group.name)
.map(group => group.name ?? group.id)
.join(', ')
},

userSubAdminsGroupsLabels() {
return this.userSubAdminsGroups
.map(group => group.name)
userSubAdminGroupsLabels() {
return this.userSubAdminGroups
.map(group => group.name ?? group.id)
.join(', ')
},

Expand Down Expand Up @@ -554,6 +554,46 @@ export default {
this.loadingPossibleManagers = false
},

async loadGroupDetails() {
this.loading.groups = true
try {
const { data } = await this.$store.dispatch('getUserGroups', {
userId: this.user.id,
})
const groups = data.ocs?.data?.groups
if (!groups) {
logger.error(t('settings', 'Failed to load groups with details'))
return
}
this.availableGroups = this.availableGroups.map(availableGroup => groups.find(group => group.id === availableGroup.id) ?? availableGroup)
} catch (error) {
logger.error(t('settings', 'Failed to load groups with details'), { error })
}
this.loading.groups = false
},

async searchGroups(query, toggleLoading) {
if (this.promise) {
this.promise.cancel()
}
this.loading.groups = true
toggleLoading(true)
try {
this.promise = await searchGroups({
search: query,
offset: 0,
limit: 25,
})
const groups = (await this.promise).data.ocs?.data?.groups ?? []
this.availableGroups = groups.map(formatGroup)
} catch (error) {
logger.error(t('settings', 'Failed to search groups'), { error })
}
this.promise = null
this.loading.groups = false
toggleLoading(false)
},

async searchUserManager(query) {
await this.$store.dispatch('searchUsers', { offset: 0, limit: 10, search: query }).then(response => {
const users = response?.data ? this.filterManagers(Object.values(response?.data.ocs.data.users)) : []
Expand Down Expand Up @@ -703,6 +743,7 @@ export default {
this.loading = { groups: true, subadmins: true }
try {
await this.$store.dispatch('addGroup', gid)
this.availableGroups.push({ id: gid, name: gid })
const userid = this.user.id
await this.$store.dispatch('addUserGroup', { userid, gid })
} catch (error) {
Expand Down Expand Up @@ -732,6 +773,7 @@ export default {
}
try {
await this.$store.dispatch('addUserGroup', { userid, gid })
this.userGroups.push({ id: group.id, name: group.id })
} catch (error) {
console.error(error)
} finally {
Expand All @@ -756,6 +798,7 @@ export default {
userid,
gid,
})
this.userGroups = this.userGroups.filter(group => group.id !== gid)
this.loading.groups = false
// remove user from current list if current list is the removed group
if (this.$route.params.selectedGroup === gid) {
Expand All @@ -780,6 +823,7 @@ export default {
userid,
gid,
})
this.userSubAdminGroups.push({ id: group.id, name: group.id })
this.loading.subadmins = false
} catch (error) {
console.error(error)
Expand All @@ -801,6 +845,7 @@ export default {
userid,
gid,
})
this.userSubAdminGroups = this.userSubAdminGroups.filter(group => group.id !== gid)
} catch (error) {
console.error(error)
} finally {
Expand Down
43 changes: 8 additions & 35 deletions apps/settings/src/mixins/UserRowMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,6 @@ export default {
type: Object,
default: () => ({}),
},
groups: {
type: Array,
default: () => [],
},
subAdminsGroups: {
type: Array,
default: () => [],
},
quotaOptions: {
type: Array,
default: () => [],
Expand All @@ -49,38 +41,19 @@ export default {
formattedFullTime,
}
},
data() {
return {
availableGroups: this.user.groups.map(id => ({ id, name: id })),
availableSubAdminGroups: this.user.subadmin.map(id => ({ id, name: id })),
userGroups: this.user.groups.map(id => ({ id, name: id })),
userSubAdminGroups: this.user.subadmin.map(id => ({ id, name: id })),
}
},
computed: {
showConfig() {
return this.$store.getters.getShowConfig
},

/* GROUPS MANAGEMENT */
userGroups() {
const userGroups = this.groups.filter(group => this.user.groups.includes(group.id))
return userGroups
},
userSubAdminsGroups() {
const userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id))
return userSubAdminsGroups
},
availableGroups() {
return this.groups.map((group) => {
// clone object because we don't want
// to edit the original groups
const groupClone = Object.assign({}, group)

// two settings here:
// 1. user NOT in group but no permission to add
// 2. user is in group but no permission to remove
groupClone.$isDisabled
= (group.canAdd === false
&& !this.user.groups.includes(group.id))
|| (group.canRemove === false
&& this.user.groups.includes(group.id))
return groupClone
})
},

/* QUOTA MANAGEMENT */
usedSpace() {
const quotaUsed = this.user.quota.used > 0 ? this.user.quota.used : 0
Expand Down
18 changes: 18 additions & 0 deletions apps/settings/src/store/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,24 @@ const actions = {
.catch((error) => context.commit('API_FAILURE', error))
},

/**
* Get user groups
*
* @param {object} context store context
* @param {object} options destructuring object
* @param {number} options.userId User id
* @return {Promise}
*/
async getUserGroups(context, { userId }) {
const url = generateOcsUrl('cloud/users/{userId}/groups/details', { userId })
try {
const response = await api.get(url)
return response
} catch (error) {
context.commit('API_FAILURE', error)
}
},

/**
* Get all users with full details
*
Expand Down