diff --git a/changelog/unreleased/enhancement-enable-rename-groups b/changelog/unreleased/enhancement-enable-rename-groups new file mode 100644 index 00000000000..62a28eac60c --- /dev/null +++ b/changelog/unreleased/enhancement-enable-rename-groups @@ -0,0 +1,4 @@ +Enhancement: Enable rename groups + +https://github.com/owncloud/web/pull/8715 +https://github.com/owncloud/web/issues/8714 diff --git a/packages/web-app-admin-settings/src/components/Groups/ContextActions.vue b/packages/web-app-admin-settings/src/components/Groups/ContextActions.vue index de1e316ef4f..c346e777707 100644 --- a/packages/web-app-admin-settings/src/components/Groups/ContextActions.vue +++ b/packages/web-app-admin-settings/src/components/Groups/ContextActions.vue @@ -9,7 +9,7 @@ import { useActionsShowDetails } from '../../../../web-pkg/src/composables/actio import { computed, defineComponent, PropType, unref } from 'vue' import ContextActionMenu from 'web-pkg/src/components/ContextActions/ContextActionMenu.vue' import { GroupActionOptions } from 'web-pkg/src/composables/actions' -import { useGroupActionsDelete } from '../../composables/actions/groups/useGroupActionsDelete' +import { useGroupActionsEdit, useGroupActionsDelete } from '../../composables/actions/groups' import { useStore } from 'web-pkg/src/composables' export default defineComponent({ @@ -25,9 +25,12 @@ export default defineComponent({ const store = useStore() const { actions: showDetailsActions } = useActionsShowDetails() const { actions: deleteActions } = useGroupActionsDelete({ store }) + const { actions: editActions } = useGroupActionsEdit() const menuItemsPrimaryActions = computed(() => - [...unref(deleteActions)].filter((item) => item.isEnabled(props.actionOptions)) + [...unref(editActions), ...unref(deleteActions)].filter((item) => + item.isEnabled(props.actionOptions) + ) ) const menuItemsSidebar = computed(() => diff --git a/packages/web-app-admin-settings/src/components/Groups/CreateGroupModal.vue b/packages/web-app-admin-settings/src/components/Groups/CreateGroupModal.vue index 24bc3babaf9..cdf081bc76a 100644 --- a/packages/web-app-admin-settings/src/components/Groups/CreateGroupModal.vue +++ b/packages/web-app-admin-settings/src/components/Groups/CreateGroupModal.vue @@ -27,19 +27,17 @@ + diff --git a/packages/web-app-admin-settings/src/composables/actions/groups/index.ts b/packages/web-app-admin-settings/src/composables/actions/groups/index.ts index b8647746c40..307a19f2241 100644 --- a/packages/web-app-admin-settings/src/composables/actions/groups/index.ts +++ b/packages/web-app-admin-settings/src/composables/actions/groups/index.ts @@ -1 +1,2 @@ export * from './useGroupActionsDelete' +export * from './useGroupActionsEdit' diff --git a/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsEdit.ts b/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsEdit.ts new file mode 100644 index 00000000000..9cc32e5bb06 --- /dev/null +++ b/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsEdit.ts @@ -0,0 +1,27 @@ +import { eventBus } from 'web-pkg' +import { SideBarEventTopics } from 'web-pkg/src/composables/sideBar' +import { useGettext } from 'vue3-gettext' +import { computed } from 'vue' +import { UserAction } from 'web-pkg/src/composables/actions' + +export const useGroupActionsEdit = () => { + const { $gettext } = useGettext() + + const actions = computed((): UserAction[] => [ + { + name: 'edit', + icon: 'pencil', + label: () => $gettext('Edit'), + handler: () => eventBus.publish(SideBarEventTopics.openWithPanel, 'EditPanel'), + isEnabled: ({ resources }) => { + return resources.length > 0 + }, + componentType: 'button', + class: 'oc-groups-actions-edit-trigger' + } + ]) + + return { + actions + } +} diff --git a/packages/web-app-admin-settings/src/views/Groups.vue b/packages/web-app-admin-settings/src/views/Groups.vue index c49a5b7c2a2..67cc8f99fdc 100644 --- a/packages/web-app-admin-settings/src/views/Groups.vue +++ b/packages/web-app-admin-settings/src/views/Groups.vue @@ -63,7 +63,6 @@ @@ -184,10 +183,11 @@ export default defineComponent({ title: this.$gettext('Edit group'), component: EditPanel, default: false, - enabled: false // this.selectedGroups.length === 1 - /** - * Editing groups is currently not supported by backend - */ + enabled: this.selectedGroups.length === 1, + componentAttrs: { + group: this.selectedGroups.length === 1 ? this.selectedGroups[0] : null, + onConfirm: this.editGroup + } } ].filter((p) => p.enabled) } @@ -236,12 +236,21 @@ export default defineComponent({ try { const client = this.clientService.graphAuthenticated await client.groups.editGroup(editGroup.id, editGroup) - const group = this.groups.find((group) => group.id === editGroup.id) - Object.assign(group, editGroup) - this.showMessage({ - title: this.$gettext('Group was edited successfully') - }) + const { data: updatedGroup } = await client.groups.getGroup(editGroup.id) + const groupIndex = this.groups.findIndex((group) => group.id === editGroup.id) + this.groups[groupIndex] = updatedGroup + const selectedGroupIndex = this.selectedGroups.findIndex( + (group) => group.id === updatedGroup.id + ) + if (selectedGroupIndex >= 0) { + // FIXME: why do we need to update selectedUsers? + this.selectedGroups[selectedGroupIndex] = updatedGroup + } + + eventBus.publish('sidebar.entity.saved') + + return updatedGroup } catch (error) { console.error(error) this.showMessage({ diff --git a/packages/web-app-admin-settings/tests/unit/components/Groups/SideBar/EditPanel.spec.ts b/packages/web-app-admin-settings/tests/unit/components/Groups/SideBar/EditPanel.spec.ts index 0b1b3286a5a..2862f9a79f1 100644 --- a/packages/web-app-admin-settings/tests/unit/components/Groups/SideBar/EditPanel.spec.ts +++ b/packages/web-app-admin-settings/tests/unit/components/Groups/SideBar/EditPanel.spec.ts @@ -1,17 +1,26 @@ import EditPanel from '../../../../../src/components/Groups/SideBar/EditPanel.vue' -import { defaultPlugins, mount } from 'web-test-helpers' +import { + createStore, + defaultComponentMocks, + defaultPlugins, + defaultStoreMockOptions, + mockAxiosReject, + mount +} from 'web-test-helpers' +import { mock } from 'jest-mock-extended' +import { AxiosResponse } from 'axios' describe('EditPanel', () => { + it('renders all available inputs', () => { + const { wrapper } = getWrapper() + expect(wrapper.html()).toMatchSnapshot() + }) describe('method "revertChanges"', () => { it('should revert changes on property editGroup', () => { - const { wrapper } = getWrapper({ - propsData: { - groups: [{ displayName: 'group' }] - } - }) - wrapper.vm.editGroup = { displayName: 'my group' } + const { wrapper } = getWrapper() + wrapper.vm.editGroup.dispayName = 'users' wrapper.vm.revertChanges() - expect(wrapper.vm.editGroup).toEqual({ displayName: 'group' }) + expect(wrapper.vm.editGroup.displayName).toEqual('group') }) it('should revert changes on property formData', () => { const { wrapper } = getWrapper({ @@ -28,17 +37,28 @@ describe('EditPanel', () => { }) describe('method "validateDisplayName"', () => { - it('should return true if displayName is valid', () => { - const { wrapper } = getWrapper() - wrapper.vm.editGroup.displayName = 'jan' - expect(wrapper.vm.validateDisplayName()).toBeTruthy() - expect(wrapper.vm.formData.displayName.valid).toBeTruthy() + it('should return true if displayName is valid', async () => { + const { wrapper, mocks } = getWrapper() + wrapper.vm.editGroup.displayName = 'users' + const graphMock = mocks.$clientService.graphAuthenticated + const getGroupStub = graphMock.groups.getGroup.mockRejectedValue(() => mockAxiosReject()) + expect(await wrapper.vm.validateDisplayName()).toBeTruthy() + expect(getGroupStub).toHaveBeenCalled() }) - it('should return false if displayName is not valid', () => { + it('should return false if displayName is empty', async () => { const { wrapper } = getWrapper() wrapper.vm.editGroup.displayName = '' - expect(wrapper.vm.validateDisplayName()).toBeFalsy() - expect(wrapper.vm.formData.displayName.valid).toBeFalsy() + expect(await wrapper.vm.validateDisplayName()).toBeFalsy() + }) + it('should return false if displayName is already existing', async () => { + const { wrapper, mocks } = getWrapper() + wrapper.vm.editGroup.displayName = 'users' + const graphMock = mocks.$clientService.graphAuthenticated + const getGroupStub = graphMock.groups.getGroup.mockResolvedValue( + mock({ data: { displayName: 'group' } }) + ) + expect(await wrapper.vm.validateDisplayName()).toBeFalsy() + expect(getGroupStub).toHaveBeenCalled() }) }) @@ -51,20 +71,26 @@ describe('EditPanel', () => { it('should be true if formData is valid', () => { const { wrapper } = getWrapper() wrapper.vm.formData.displayName.valid = false - expect(wrapper.vm.invalidFormData).toBeTruthy() + console.log(wrapper.vm.formData) + expect(wrapper.vm.invalidFormData).toBeFalsy() }) }) }) function getWrapper({ propsData = {} } = {}) { + const mocks = defaultComponentMocks() + const storeOptions = defaultStoreMockOptions + const store = createStore(storeOptions) + return { + mocks, wrapper: mount(EditPanel, { props: { - groups: [{ displayName: 'group' }], - ...propsData + group: { displayName: 'group', members: [] }, }, global: { - plugins: [...defaultPlugins()], + mocks, + plugins: [...defaultPlugins(), store], stubs: { 'oc-text-input': true, 'avatar-image': true, diff --git a/packages/web-app-admin-settings/tests/unit/components/Groups/SideBar/__snapshots__/EditPanel.spec.ts.snap b/packages/web-app-admin-settings/tests/unit/components/Groups/SideBar/__snapshots__/EditPanel.spec.ts.snap new file mode 100644 index 00000000000..5951d01f178 --- /dev/null +++ b/packages/web-app-admin-settings/tests/unit/components/Groups/SideBar/__snapshots__/EditPanel.spec.ts.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EditPanel renders all available inputs 1`] = ` +
+
+ + group + 0 members +
+
+ +
+ No changes +
+ + +
+
+
+
+`; diff --git a/packages/web-client/src/graph.ts b/packages/web-client/src/graph.ts index 90a11267eb8..c622cc95066 100644 --- a/packages/web-client/src/graph.ts +++ b/packages/web-client/src/graph.ts @@ -148,7 +148,8 @@ export const graph = (baseURI: string, axiosClient: AxiosInstance): Graph => { groups: { createGroup: (group: Group) => groupsApiFactory.createGroup(group), editGroup: (groupId: string, group: Group) => groupApiFactory.updateGroup(groupId, group), - getGroup: (groupId: string) => groupApiFactory.getGroup(groupId), + getGroup: (groupId: string) => + groupApiFactory.getGroup(groupId, new Set([]), new Set(['members'])), deleteGroup: (groupId: string) => groupApiFactory.deleteGroup(groupId), listGroups: (orderBy?: any) => groupsApiFactory.listGroups(