Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for read-only groups #8766

Merged
merged 1 commit into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: Add support for read-only groups

Read-only groups are now supported. Such groups can't be edited or assigned to/removed from users. They are indicated via a lock icon in the group list and all affected inputs.

https://github.com/owncloud/web/pull/8766
https://github.com/owncloud/web/issues/8729
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@
<template #avatar="rowData">
<avatar-image :width="32" :userid="rowData.item.id" :user-name="rowData.item.displayName" />
</template>
<template #displayName="rowData">
<div class="oc-flex oc-flex-middle">
{{ rowData.item.displayName }}
<oc-icon
v-if="rowData.item.groupTypes?.includes('ReadOnly')"
v-oc-tooltip="readOnlyLabel"
name="lock"
size="small"
fill-type="line"
class="oc-ml-s"
:accessible-label="readOnlyLabel"
/>
</div>
</template>
<template #members="rowData">
{{ rowData.item.members.length }}
</template>
Expand All @@ -59,6 +73,7 @@
<oc-icon name="information" fill-type="line" />
</oc-button>
<oc-button
v-if="!item.groupTypes?.includes('ReadOnly')"
v-oc-tooltip="$gettext('Edit')"
appearance="raw"
class="oc-mr-xs quick-action-button oc-p-xs groups-table-btn-edit"
Expand All @@ -76,11 +91,6 @@
<slot name="contextMenu" :group="item" />
</template>
</context-menu-quick-action>
<!-- Editing groups is currently not supported by backend
<oc-button v-oc-tooltip="$gettext('Edit')" class="oc-ml-s" @click="$emit('clickEdit', item)">
<oc-icon size="small" name="pencil" />
</oc-button>
-->
</template>
<template #footer>
<div class="oc-text-nowrap oc-text-center oc-width-1-1 oc-my-s">
Expand All @@ -93,13 +103,14 @@
</template>

<script lang="ts">
import { defineComponent, PropType, ref, unref, ComponentPublicInstance } from 'vue'
import { defineComponent, PropType, ref, unref, ComponentPublicInstance, computed } from 'vue'
import Fuse from 'fuse.js'
import Mark from 'mark.js'
import { displayPositionedDropdown, eventBus } from 'web-pkg'
import { SideBarEventTopics } from 'web-pkg/src/composables/sideBar'
import { Group } from 'web-client/src/generated'
import ContextMenuQuickAction from 'web-pkg/src/components/ContextActions/ContextMenuQuickAction.vue'
import { useGettext } from 'vue3-gettext'

export default defineComponent({
name: 'GroupsList',
Expand All @@ -120,6 +131,7 @@ export default defineComponent({
},
emits: ['toggleSelectAllGroups', 'unSelectAllGroups', 'toggleSelectGroup'],
setup(props, { emit }) {
const { $gettext } = useGettext()
const contextMenuButtonRef = ref(undefined)

const isGroupSelected = (group) => {
Expand Down Expand Up @@ -168,14 +180,17 @@ export default defineComponent({
eventBus.publish(SideBarEventTopics.openWithPanel, 'EditPanel')
}

const readOnlyLabel = computed(() => $gettext("This group is read-only and can't be edited"))

return {
showDetails,
rowClicked,
isGroupSelected,
showContextMenuOnBtnClick,
showContextMenuOnRightClick,
contextMenuButtonRef,
showEditPanel
showEditPanel,
readOnlyLabel
}
},
data() {
Expand Down Expand Up @@ -205,6 +220,7 @@ export default defineComponent({
{
name: 'displayName',
title: this.$gettext('Group name'),
type: 'slot',
sortable: true
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div id="user-group-select-form">
<oc-select
:model-value="selectedOption"
:model-value="selectedOptions"
class="oc-mb-s"
:multiple="true"
:options="groupOptions"
Expand Down Expand Up @@ -38,8 +38,7 @@
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, unref, watch } from 'vue'
import { computed } from 'vue'
import { computed, defineComponent, PropType, ref, unref, watch } from 'vue'
import { Group } from 'web-client/src/generated'

export default defineComponent({
Expand All @@ -56,18 +55,27 @@ export default defineComponent({
},
emits: ['selectedOptionChange'],
setup(props, { emit }) {
const selectedOption = ref(props.selectedGroups)
const selectedOptions = ref()
const onUpdate = (event) => {
selectedOption.value = event
emit('selectedOptionChange', unref(selectedOption))
selectedOptions.value = event
emit('selectedOptionChange', unref(selectedOptions))
}

const currentGroups = computed(() => props.selectedGroups)
watch(currentGroups, () => {
selectedOption.value = props.selectedGroups
})
watch(
currentGroups,
() => {
selectedOptions.value = props.selectedGroups
.map((g) => ({
...g,
readonly: g.groupTypes?.includes('ReadOnly')
}))
.sort((a: any, b: any) => b.readonly - a.readonly)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙈

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, I know 😄 But even when using the correct types here, TS would complain that you can't subtract boolean values from another.

},
{ immediate: true }
)

return { selectedOption, onUpdate }
return { selectedOptions, onUpdate }
}
})
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@confirm="$emit('confirm', { users, groups: selectedOptions })"
>
<template #content>
<GroupSelect
<group-select
:selected-groups="selectedOptions"
:group-options="groups"
@selected-option-change="changeSelectedGroupOption"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,9 @@ export default defineComponent({
})
const groupOptions = computed(() => {
const { memberOf: selectedGroups } = unref(editUser)
return props.groups.filter((g) => !selectedGroups.some((s) => s.id === g.id))
return props.groups.filter(
(g) => !selectedGroups.some((s) => s.id === g.id) && !g.groupTypes?.includes('ReadOnly')
)
})

const isLoginInputDisabled = computed(() => currentUser.uuid === (props.user as User).id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const useGroupActionsDelete = ({ store }: { store?: Store<any> }) => {
},
handler,
isEnabled: ({ resources }) => {
return !!resources.length
return !!resources.length && !resources.some((r) => r.groupTypes?.includes('ReadOnly'))
},
componentType: 'button',
class: 'oc-groups-actions-delete-trigger'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const useGroupActionsEdit = () => {
label: () => $gettext('Edit'),
handler: () => eventBus.publish(SideBarEventTopics.openWithPanel, 'EditPanel'),
isEnabled: ({ resources }) => {
return resources.length > 0
return resources.length === 1 && !resources[0].groupTypes?.includes('ReadOnly')
},
componentType: 'button',
class: 'oc-groups-actions-edit-trigger'
Expand Down
4 changes: 3 additions & 1 deletion packages/web-app-admin-settings/src/views/Groups.vue
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ export default defineComponent({
title: this.$gettext('Edit group'),
component: EditPanel,
default: false,
enabled: this.selectedGroups.length === 1,
enabled:
this.selectedGroups.length === 1 &&
!this.selectedGroups[0].groupTypes?.includes('ReadOnly'),
componentAttrs: {
group: this.selectedGroups.length === 1 ? this.selectedGroups[0] : null,
onConfirm: this.editGroup
Expand Down
13 changes: 9 additions & 4 deletions packages/web-app-admin-settings/src/views/Users.vue
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@
<groups-modal
v-if="addToGroupsModalIsOpen"
:title="addToGroupsModalTitle"
:groups="groups"
:groups="writableGroups"
:users="selectedUsers"
@cancel="() => (addToGroupsModalIsOpen = false)"
@confirm="addUsersToGroups"
/>
<groups-modal
v-if="removeFromGroupsModalIsOpen"
:title="removeFromGroupsModalTitle"
:groups="groups"
:groups="writableGroups"
:users="selectedUsers"
@cancel="() => (removeFromGroupsModalIsOpen = false)"
@confirm="removeUsersFromGroups"
Expand Down Expand Up @@ -180,7 +180,7 @@ import {
useUserActionsAddToGroups
} from '../composables/actions/users'
import { configurationManager } from 'web-pkg'
import { Drive } from 'web-client/src/generated'
import { Drive, Group } from 'web-client/src/generated'

export default defineComponent({
name: 'UsersView',
Expand Down Expand Up @@ -505,6 +505,10 @@ export default defineComponent({
}
}

const writableGroups = computed<Group[]>(() => {
return unref(groups).filter((g) => !g.groupTypes?.includes('ReadOnly'))
})

return {
...useSideBar(),
maxQuota: useCapabilitySpacesMaxQuota(),
Expand Down Expand Up @@ -533,7 +537,8 @@ export default defineComponent({
spaceQuotaUpdated,
selectedPersonalDrives,
addUsersToGroups,
removeUsersFromGroups
removeUsersFromGroups,
writableGroups
}
},
computed: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,33 @@ import { defaultPlugins, shallowMount } from 'web-test-helpers'
import { mock } from 'jest-mock-extended'
import { Group } from 'web-client/src/generated'

const groupMock = mock<Group>({ id: '1' })
const groupMock = mock<Group>({ id: '1', groupTypes: [] })

describe('GroupSelect', () => {
it('renders the select input', () => {
const { wrapper } = getWrapper()
expect(wrapper.html()).toMatchSnapshot()
})
it('correctly maps the read-only state', () => {
const groupMock = mock<Group>({ id: '1', groupTypes: ['ReadOnly'] })
const { wrapper } = getWrapper(groupMock)
expect(wrapper.vm.selectedOptions[0].readonly).toBeTruthy()
})
it('emits "selectedOptionChange" on update', () => {
const group = mock<Group>({ id: '2' })
const group = mock<Group>({ id: '2', groupTypes: [] })
const { wrapper } = getWrapper()
wrapper.vm.onUpdate(group)
expect(wrapper.emitted().selectedOptionChange).toBeTruthy()
expect(wrapper.vm.selectedOption).toEqual(group)
expect(wrapper.vm.selectedOptions).toEqual(group)
})
})

function getWrapper() {
function getWrapper(group = groupMock) {
return {
wrapper: shallowMount(GroupSelect, {
props: {
selectedGroups: [groupMock],
groupOptions: [groupMock]
selectedGroups: [group],
groupOptions: [group]
},
global: {
plugins: [...defaultPlugins()]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { Group } from 'web-client/src/generated'
import { AxiosResponse } from 'axios'

const availableGroupOptions = [
mock<Group>({ id: '1', displayName: 'group1' }),
mock<Group>({ id: '2', displayName: 'group2' })
mock<Group>({ id: '1', displayName: 'group1', groupTypes: [] }),
mock<Group>({ id: '2', displayName: 'group2', groupTypes: [] })
]
const selectors = {
groupSelectStub: 'group-select-stub'
Expand Down Expand Up @@ -132,9 +132,24 @@ describe('EditPanel', () => {
expect(wrapper.vm.invalidFormData).toBeTruthy()
})
})

describe('group select', () => {
it('takes all available groups', () => {
const { wrapper } = getWrapper()
expect(wrapper.findComponent<any>('group-select-stub').props('groupOptions').length).toBe(
availableGroupOptions.length
)
})
it('filters out read-only groups', () => {
const { wrapper } = getWrapper({
groups: [mock<Group>({ id: '1', displayName: 'group1', groupTypes: ['ReadOnly'] })]
})
expect(wrapper.findComponent<any>('group-select-stub').props('groupOptions').length).toBe(0)
})
})
})

function getWrapper({ selectedGroups = [] } = {}) {
function getWrapper({ selectedGroups = [], groups = availableGroupOptions } = {}) {
const mocks = defaultComponentMocks()
const storeOptions = defaultStoreMockOptions
const store = createStore(storeOptions)
Expand All @@ -151,7 +166,7 @@ function getWrapper({ selectedGroups = [] } = {}) {
memberOf: selectedGroups
},
roles: [{ id: '1', displayName: 'admin' }],
groups: availableGroupOptions
groups
},
global: {
mocks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

exports[`GroupSelect renders the select input 1`] = `
<div id="user-group-select-form">
<oc-select-stub class="oc-mb-s" clearable="false" disabled="false" filter="[Function]" fixmessageline="true" id="oc-select-1" label="Groups" loading="false" model-value="undefined" multiple="true" optionlabel="displayName" options="undefined" searchable="true"></oc-select-stub>
<oc-select-stub class="oc-mb-s" clearable="false" disabled="false" filter="[Function]" fixmessageline="true" id="oc-select-1" label="Groups" loading="false" model-value="[object Object]" multiple="true" optionlabel="displayName" options="undefined" searchable="true"></oc-select-stub>
</div>
`;
Loading