Skip to content

Commit 709fa4b

Browse files
authored
Merge pull request #53050 from nextcloud/backport/52833/stable30
[stable30] fix(settings): Send update request when clearing user manager
2 parents 5f41964 + bcf9a48 commit 709fa4b

File tree

8 files changed

+159
-74
lines changed

8 files changed

+159
-74
lines changed

apps/settings/src/components/Users/UserRow.vue

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -249,16 +249,17 @@
249249
data-cy-user-list-input-manager
250250
:data-loading="loading.manager || undefined"
251251
:input-id="'manager' + uniqueId"
252-
:close-on-select="true"
253252
:disabled="isLoadingField"
254-
:append-to-body="false"
255253
:loading="loadingPossibleManagers || loading.manager"
256-
label="displayname"
257254
:options="possibleManagers"
258255
:placeholder="managerLabel"
256+
label="displayname"
257+
:filterable="false"
258+
:internal-search="false"
259+
:clearable="true"
259260
@open="searchInitialUserManager"
260261
@search="searchUserManager"
261-
@option:selected="updateUserManager" />
262+
@update:model-value="updateUserManager" />
262263
</template>
263264
<span v-else-if="!isObfuscated">
264265
{{ user.manager }}
@@ -508,7 +509,6 @@ export default {
508509
return this.languages[0].languages.concat(this.languages[1].languages)
509510
},
510511
},
511-
512512
async beforeMount() {
513513
if (this.user.manager) {
514514
await this.initManager(this.user.manager)
@@ -629,11 +629,12 @@ export default {
629629
})
630630
},
631631
632-
async updateUserManager(manager) {
633-
if (manager === null) {
634-
this.currentManager = ''
635-
}
632+
async updateUserManager() {
636633
this.loading.manager = true
634+
635+
// Store the current manager before making changes
636+
const previousManager = this.user.manager
637+
637638
try {
638639
await this.$store.dispatch('setUserData', {
639640
userid: this.user.id,
@@ -643,7 +644,10 @@ export default {
643644
} catch (error) {
644645
// TRANSLATORS This string describes a line manager in the context of an organization
645646
showError(t('settings', 'Failed to update line manager'))
646-
console.error(error)
647+
logger.error('Failed to update manager:', { error })
648+
649+
// Revert to the previous manager in the UI on error
650+
this.currentManager = previousManager
647651
} finally {
648652
this.loading.manager = false
649653
}

apps/settings/src/store/users.js

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -766,24 +766,25 @@ const actions = {
766766
*/
767767
async setUserData(context, { userid, key, value }) {
768768
const allowedEmpty = ['email', 'displayname', 'manager']
769-
if (['email', 'language', 'quota', 'displayname', 'password', 'manager'].indexOf(key) !== -1) {
770-
// We allow empty email or displayname
771-
if (typeof value === 'string'
772-
&& (
773-
(allowedEmpty.indexOf(key) === -1 && value.length > 0)
774-
|| allowedEmpty.indexOf(key) !== -1
775-
)
776-
) {
777-
try {
778-
await api.requireAdmin()
779-
await api.put(generateOcsUrl('cloud/users/{userid}', { userid }), { key, value })
780-
return context.commit('setUserData', { userid, key, value })
781-
} catch (error) {
782-
context.commit('API_FAILURE', { userid, error })
783-
}
784-
}
769+
const validKeys = ['email', 'language', 'quota', 'displayname', 'password', 'manager']
770+
771+
if (!validKeys.includes(key)) {
772+
throw new Error('Invalid request data')
773+
}
774+
775+
// If value is empty and the key doesn't allow empty values, throw error
776+
if (value === '' && !allowedEmpty.includes(key)) {
777+
throw new Error('Value cannot be empty for this field')
778+
}
779+
780+
try {
781+
await api.requireAdmin()
782+
await api.put(generateOcsUrl('cloud/users/{userid}', { userid }), { key, value })
783+
return context.commit('setUserData', { userid, key, value })
784+
} catch (error) {
785+
context.commit('API_FAILURE', { userid, error })
786+
throw error
785787
}
786-
return Promise.reject(new Error('Invalid request data'))
787788
},
788789

789790
/**
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { User } from '@nextcloud/cypress'
7+
import { getUserListRow, handlePasswordConfirmation, toggleEditButton, waitLoading } from './usersUtils'
8+
import { clearState } from '../../support/commonUtils'
9+
10+
const admin = new User('admin', 'admin')
11+
12+
describe('Settings: User Manager Management', function() {
13+
let user: User
14+
let manager: User
15+
16+
beforeEach(function() {
17+
clearState()
18+
cy.createRandomUser().then(($user) => {
19+
manager = $user
20+
return cy.createRandomUser()
21+
}).then(($user) => {
22+
user = $user
23+
cy.login(admin)
24+
cy.intercept('PUT', `/ocs/v2.php/cloud/users/${user.userId}*`).as('updateUser')
25+
})
26+
})
27+
28+
it('Can assign and remove a manager through the UI', function() {
29+
cy.visit('/settings/users')
30+
31+
toggleEditButton(user, true)
32+
33+
// Scroll to manager cell and wait for it to be visible
34+
getUserListRow(user.userId)
35+
.find('[data-cy-user-list-cell-manager]')
36+
.scrollIntoView()
37+
.should('be.visible')
38+
39+
// Assign a manager
40+
getUserListRow(user.userId).find('[data-cy-user-list-cell-manager]').within(() => {
41+
// Verify no manager is set initially
42+
cy.get('.vs__selected').should('not.exist')
43+
44+
// Open the dropdown menu
45+
cy.get('[role="combobox"]').click({ force: true })
46+
47+
// Wait for the dropdown to be visible and initialized
48+
waitLoading('[data-cy-user-list-input-manager]')
49+
50+
// Type the manager's username to search
51+
cy.get('input[type="search"]').type(manager.userId, { force: true })
52+
53+
// Wait for the search results to load
54+
waitLoading('[data-cy-user-list-input-manager]')
55+
})
56+
57+
// Now select the manager from the filtered results
58+
// Since the dropdown is floating, we need to search globally
59+
cy.get('.vs__dropdown-menu').find('li').contains('span', manager.userId).should('be.visible').click({ force: true })
60+
61+
// Handle password confirmation if needed
62+
handlePasswordConfirmation(admin.password)
63+
64+
// Verify the manager is selected in the UI
65+
cy.get('.vs__selected').should('exist').and('contain.text', manager.userId)
66+
67+
// Verify the PUT request was made to set the manager
68+
cy.wait('@updateUser').then((interception) => {
69+
// Verify the request URL and body
70+
expect(interception.request.url).to.match(/\/cloud\/users\/.+/)
71+
expect(interception.request.body).to.deep.equal({
72+
key: 'manager',
73+
value: manager.userId
74+
})
75+
expect(interception.response?.statusCode).to.equal(200)
76+
})
77+
78+
// Wait for the save to complete
79+
waitLoading('[data-cy-user-list-input-manager]')
80+
81+
// Verify the manager is set in the backend
82+
cy.getUserData(user).then(($result) => {
83+
expect($result.body).to.contain(`<manager>${manager.userId}</manager>`)
84+
})
85+
86+
// Now remove the manager
87+
getUserListRow(user.userId).find('[data-cy-user-list-cell-manager]').within(() => {
88+
// Clear the manager selection
89+
cy.get('.vs__clear').click({ force: true })
90+
91+
// Verify the manager is cleared in the UI
92+
cy.get('.vs__selected').should('not.exist')
93+
94+
// Handle password confirmation if needed
95+
handlePasswordConfirmation(admin.password)
96+
})
97+
98+
// Verify the PUT request was made to clear the manager
99+
cy.wait('@updateUser').then((interception) => {
100+
// Verify the request URL and body
101+
expect(interception.request.url).to.match(/\/cloud\/users\/.+/)
102+
expect(interception.request.body).to.deep.equal({
103+
key: 'manager',
104+
value: '',
105+
})
106+
expect(interception.response?.statusCode).to.equal(200)
107+
})
108+
109+
// Wait for the save to complete
110+
waitLoading('[data-cy-user-list-input-manager]')
111+
112+
// Verify the manager is cleared in the backend
113+
cy.getUserData(user).then(($result) => {
114+
expect($result.body).to.not.contain(`<manager>${manager.userId}</manager>`)
115+
expect($result.body).to.contain('<manager></manager>')
116+
})
117+
118+
// Finish editing the user
119+
toggleEditButton(user, false)
120+
})
121+
})

cypress/e2e/settings/users_modify.cy.ts

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -181,47 +181,6 @@ describe('Settings: Change user properties', function() {
181181
})
182182
})
183183

184-
it('Can set manager of a user', function() {
185-
// create the manager
186-
let manager: User
187-
cy.createRandomUser().then(($user) => { manager = $user })
188-
189-
// open the User settings as admin
190-
cy.login(admin)
191-
cy.visit('/settings/users')
192-
193-
// toggle edit button into edit mode
194-
toggleEditButton(user, true)
195-
196-
getUserListRow(user.userId)
197-
.find('[data-cy-user-list-cell-manager]')
198-
.scrollIntoView()
199-
200-
getUserListRow(user.userId).find('[data-cy-user-list-cell-manager]').within(() => {
201-
// see that the user has no manager
202-
cy.get('.vs__selected').should('not.exist')
203-
// Open the dropdown menu
204-
cy.get('[role="combobox"]').click({ force: true })
205-
// select the manager
206-
cy.contains('li', manager.userId).click({ force: true })
207-
208-
// Handle password confirmation on time out
209-
handlePasswordConfirmation(admin.password)
210-
211-
// see that the user has a manager set
212-
cy.get('.vs__selected').should('exist').and('contain.text', manager.userId)
213-
})
214-
215-
// see that the changes are loading
216-
waitLoading('[data-cy-user-list-input-manager]')
217-
218-
// finish editing the user
219-
toggleEditButton(user, false)
220-
221-
// validate the manager is set
222-
cy.getUserData(user).then(($result) => expect($result.body).to.contain(`<manager>${manager.userId}</manager>`))
223-
})
224-
225184
it('Can make user a subadmin of a group', function() {
226185
// create a group
227186
const groupName = 'userstestgroup'

dist/settings-users-3239.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/settings-users-3239.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/settings-vue-settings-apps-users-management.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/settings-vue-settings-apps-users-management.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)