Skip to content
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ playwright-report

/.pnpm-store/

packages/web-client/src/graph/generated/docs/

# VitePress
packages/design-system/docs/.vitepress/cache
packages/design-system/docs/.vitepress/dist
Expand Down
2 changes: 1 addition & 1 deletion .woodpecker.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# The version of OpenCloud to use in pipelines
OPENCLOUD_COMMITID=db7d0535f61e587c78a085dc713445d8d40e1399
OPENCLOUD_COMMITID=c29b777638075cbd0a94c638f0f694310886705d
OPENCLOUD_BRANCH=main
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,34 @@
:label="$gettext('Filter members')"
/>
<div ref="membersListRef" data-testid="space-members">
<div v-if="!filteredSpaceMembers.length">
<div v-if="!filteredPermissions.length">
<h3 class="oc-text-bold oc-text-medium" v-text="$gettext('No members found')" />
</div>
<div v-for="(role, i) in availableRoles" :key="i">
<div
v-if="getMembersForRole(role).length"
v-if="getPermissionsForRole(role).length"
class="oc-mb-m"
:data-testid="`space-members-role-${role.displayName}`"
>
<h3 class="oc-text-bold oc-text-medium" v-text="role.displayName" />
<members-role-section :members="getMembersForRole(role)" />
<members-role-section :permissions="getPermissionsForRole(role)" />
</div>
</div>
<div v-if="membersWithoutRole.length" class="space-members-custom">
<div v-if="permissionsWithoutRole.length" class="space-members-custom">
<h3 class="oc-text-bold oc-text-medium" v-text="$gettext('Custom role')" />
<members-role-section :members="membersWithoutRole" />
<members-role-section :permissions="permissionsWithoutRole" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, inject, ref, watch, unref } from 'vue'
import { ShareRole, SpaceMember, SpaceResource } from '@opencloud-eu/web-client'
import { ShareRole, SpaceResource } from '@opencloud-eu/web-client'
import MembersRoleSection from './MembersRoleSection.vue'
import Fuse from 'fuse.js'
import Mark from 'mark.js'
import { defaultFuseOptions, useSharesStore } from '@opencloud-eu/web-pkg'
import { Permission } from '@opencloud-eu/web-client/graph/generated'

const sharesStore = useSharesStore()

Expand All @@ -41,29 +42,29 @@ const filterTerm = ref('')
const markInstance = ref(null)
const membersListRef = ref(null)

const filterMembers = (collection: SpaceMember[], term: string) => {
const filterMembers = (collection: Permission[], term: string) => {
if (!(term || '').trim()) {
return collection
}

const searchEngine = new Fuse(collection, {
...defaultFuseOptions,
keys: ['grantedTo.user.displayName', 'grantedTo.group.displayName']
keys: ['grantedToV2.user.displayName', 'grantedToV2.group.displayName']
})
return searchEngine.search(term).map((r) => r.item)
}

const spaceMembers = computed<SpaceMember[]>(() => {
return Object.values(unref(resource).members)
const permissions = computed(() => {
return Object.values(unref(resource).root.permissions)
})

const filteredSpaceMembers = computed<SpaceMember[]>(() => {
return filterMembers(unref(spaceMembers), unref(filterTerm))
const filteredPermissions = computed(() => {
return filterMembers(unref(permissions), unref(filterTerm))
})

const availableRoles = computed<ShareRole[]>(() => {
const permissionsWithRole = unref(spaceMembers).filter((p) => !!p.roleId)
const roleIds = [...new Set(permissionsWithRole.map((p) => p.roleId))]
const permissionsWithRole = unref(permissions).filter((p) => !!p.roles.length)
const roleIds = [...new Set(permissionsWithRole.map((p) => p.roles).flat())]
return roleIds
.map((r) => sharesStore.graphRoles[r])
.filter(Boolean)
Expand All @@ -75,12 +76,12 @@ const availableRoles = computed<ShareRole[]>(() => {
})
})

const membersWithoutRole = computed<SpaceMember[]>(() => {
return unref(filteredSpaceMembers).filter(({ roleId }) => !roleId)
const permissionsWithoutRole = computed(() => {
return unref(filteredPermissions).filter(({ roles }) => !roles.length)
})

const getMembersForRole = (role: ShareRole): SpaceMember[] => {
return unref(filteredSpaceMembers).filter(({ roleId }) => roleId === role.id)
const getPermissionsForRole = (role: ShareRole) => {
return unref(filteredPermissions).filter(({ roles }) => roles.includes(role.id))
}

watch(filterTerm, () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,37 @@
<template>
<ul class="oc-list">
<li
v-for="(m, index) in members"
v-for="(m, index) in permissions"
:key="index"
class="oc-flex oc-flex-middle oc-mb-s"
data-testid="space-members-list"
>
<user-avatar
v-if="m.grantedTo.user"
:user-id="m.grantedTo.user.id"
:user-name="m.grantedTo.user.displayName"
v-if="m.grantedToV2.user"
:user-id="m.grantedToV2.user.id"
:user-name="getDisplayName(m)"
class="oc-mr-s"
/>
<oc-avatar-item
v-else
:width="36"
icon-size="medium"
:icon="groupIcon"
:icon="ShareTypes.group.icon"
name="group"
class="oc-mr-s"
/>
{{ (m.grantedTo.user || m.grantedTo.group).displayName }}
{{ getDisplayName(m) }}
</li>
</ul>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { ShareTypes, SpaceMember } from '@opencloud-eu/web-client'
<script setup lang="ts">
import { ShareTypes } from '@opencloud-eu/web-client'
import { Permission } from '@opencloud-eu/web-client/graph/generated'
import { UserAvatar } from '@opencloud-eu/web-pkg'

export default defineComponent({
name: 'MembersRoleSection',
components: { UserAvatar },
props: {
members: {
type: Array as PropType<SpaceMember[]>,
required: true
}
},
setup() {
const groupIcon = computed(() => {
return ShareTypes.group.icon
})
return { groupIcon }
}
})
const { permissions } = defineProps<{ permissions: Permission[] }>()

const getDisplayName = (permission: Permission) => {
return permission.grantedToV2.user?.displayName || permission.grantedToV2.group?.displayName || ''
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ import {
defaultFuseOptions,
useKeyboardActions,
ContextMenuBtnClickEventData,
useIsTopBarSticky
useIsTopBarSticky,
useSharesStore
} from '@opencloud-eu/web-pkg'
import {
ComponentPublicInstance,
Expand All @@ -136,7 +137,7 @@ import {
unref,
watch
} from 'vue'
import { getSpaceManagers, SpaceResource } from '@opencloud-eu/web-client'
import { GraphSharePermission, SpaceResource } from '@opencloud-eu/web-client'
import Mark from 'mark.js'
import Fuse from 'fuse.js'
import { useGettext } from 'vue3-gettext'
Expand Down Expand Up @@ -169,6 +170,7 @@ export default defineComponent({
const language = useGettext()
const { $gettext } = language
const { isSticky } = useIsTopBarSticky()
const sharesStore = useSharesStore()

const { y: fileListHeaderY } = useFileListHeaderPosition('#admin-settings-app-bar')
const contextMenuButtonRef = ref(undefined)
Expand Down Expand Up @@ -356,11 +358,29 @@ export default defineComponent({
}
])

const getSpaceManagers = (space: SpaceResource) => {
return space.root.permissions.filter((p) => {
let permissionActions: string[] = []
if (p['@libre.graph.permissions.actions']) {
permissionActions = p['@libre.graph.permissions.actions']
}
const role = sharesStore.graphRoles[p.roles?.[0]]
if (role && !permissionActions.length) {
const permissions = role.rolePermissions.find(
({ condition }) => condition === 'exists @Resource.Root'
)
permissionActions = permissions?.allowedResourceActions || []
}

return permissionActions.includes(GraphSharePermission.deletePermissions)
})
}

const getManagerNames = (space: SpaceResource) => {
const allManagers = getSpaceManagers(space)
const managers = allManagers.length > 2 ? allManagers.slice(0, 2) : allManagers
let managerStr = managers
.map(({ grantedTo }) => (grantedTo.user || grantedTo.group).displayName)
.map(({ grantedToV2 }) => (grantedToV2.user || grantedToV2.group).displayName)
.join(', ')
if (allManagers.length > 2) {
managerStr += `... +${allManagers.length - 2}`
Expand Down Expand Up @@ -396,7 +416,7 @@ export default defineComponent({
return formatFileSize(space.spaceQuota.remaining, language.current)
}
const getMemberCount = (space: SpaceResource) => {
return Object.keys(space.members).length
return space.root.permissions.length
}

const getSelectSpaceLabel = (space: SpaceResource) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@ import {
useCapabilityStore,
useEventBus,
useMessages,
useSpacesStore,
useSharesStore
useSpacesStore
} from '@opencloud-eu/web-pkg'
import GroupSelect from '../GroupSelect.vue'
import { cloneDeep, isEmpty, isEqual, omit } from 'lodash-es'
Expand Down Expand Up @@ -166,7 +165,6 @@ export default defineComponent({
const userStore = useUserStore()
const userSettingsStore = useUserSettingsStore()
const spacesStore = useSpacesStore()
const sharesStore = useSharesStore()
const eventBus = useEventBus()
const { showErrorMessage } = useMessages()
const { $gettext } = useGettext()
Expand Down Expand Up @@ -227,13 +225,9 @@ export default defineComponent({

const onUpdateUserDrive = async (editUser: User) => {
const client = clientService.graphAuthenticated
const updateSpace = await client.drives.updateDrive(
editUser.drive.id,
{
quota: { total: editUser.drive.quota.total }
},
sharesStore.graphRoles
)
const updateSpace = await client.drives.updateDrive(editUser.drive.id, {
quota: { total: editUser.drive.quota.total }
})

if (editUser.id === userStore.user.id) {
// Load current user quota
Expand Down
6 changes: 2 additions & 4 deletions packages/web-app-admin-settings/src/views/Spaces.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ import {
useSpaceActionsRestore,
useSpaceActionsEditQuota,
AppLoadingSpinner,
useSharesStore,
useAbility,
CreateSpace
} from '@opencloud-eu/web-pkg'
Expand Down Expand Up @@ -112,7 +111,6 @@ export default defineComponent({
const clientService = useClientService()
const { $gettext } = useGettext()
const { isSideBarOpen, sideBarActivePanel } = useSideBar()
const sharesStore = useSharesStore()
const { can } = useAbility()

const loadResourcesEventToken = ref(null)
Expand All @@ -136,10 +134,10 @@ export default defineComponent({
const loadResourcesTask = useTask(function* (signal) {
const drives = yield* call(
clientService.graphAuthenticated.drives.listAllDrives(
sharesStore.graphRoles,
{
orderBy: 'name asc',
filter: 'driveType eq project'
filter: 'driveType eq project',
expand: 'root($expand=permissions)'
},
{ signal }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defaultPlugins, shallowMount } from '@opencloud-eu/web-test-helpers'
import { mock } from 'vitest-mock-extended'
import { ShareRole, SpaceResource } from '@opencloud-eu/web-client'
import MembersRoleSection from '../../../../../src/components/Spaces/SideBar/MembersRoleSection.vue'
import { Permission } from '@opencloud-eu/web-client/graph/generated'

const graphRoles = {
'1': mock<ShareRole>({ id: '1', displayName: 'Managers', rolePermissions: [] }),
Expand All @@ -11,10 +12,12 @@ const graphRoles = {
}

const spaceMock = {
members: {
'1': { roleId: '1', grantedTo: { user: { displayName: 'admin' } } },
'2': { roleId: '2', grantedTo: { user: { displayName: 'marie' } } },
'3': { roleId: '3', grantedTo: { user: { displayName: 'einstein' } } }
root: {
permissions: [
mock<Permission>({ grantedToV2: { user: { displayName: 'admin' } }, roles: ['1'] }),
mock<Permission>({ grantedToV2: { user: { displayName: 'marie' } }, roles: ['2'] }),
mock<Permission>({ grantedToV2: { user: { displayName: 'einstein' } }, roles: ['3'] })
]
}
} as undefined as SpaceResource

Expand All @@ -29,16 +32,16 @@ describe('MembersPanel', () => {
expect(wrapper.html()).toMatchSnapshot()
})
it('should filter members accordingly to the entered search term', async () => {
const userToFilterFor = spaceMock.members['3']
const userToFilterFor = spaceMock.root.permissions[2]
const { wrapper } = getWrapper()
const input = wrapper.find('input')
await input.setValue('ein')
await wrapper.vm.$nextTick()
expect(wrapper.findAll(selectors.membersRolePanelStub).length).toBe(1)
expect(
wrapper.findComponent<typeof MembersRoleSection>(selectors.membersRolePanelStub).props()
.members[0].grantedTo.user.displayName
).toEqual(userToFilterFor.grantedTo.user.displayName)
.permissions[0].grantedToV2.user.displayName
).toEqual(userToFilterFor.grantedToV2.user.displayName)
})
it('should display an empty result if no matching members found', async () => {
const { wrapper } = getWrapper()
Expand All @@ -50,8 +53,8 @@ describe('MembersPanel', () => {
})
it('should display members without role under the custom section', () => {
const spaceResource = {
members: {
'1': { grantedTo: { user: { displayName: 'admin' } } }
root: {
permissions: [mock<Permission>({ grantedToV2: { user: { displayName: 'admin' } } })]
}
} as undefined as SpaceResource
const { wrapper } = getWrapper({ spaceResource })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { Permission } from '@opencloud-eu/web-client/graph/generated'
import MembersRoleSection from '../../../../../src/components/Spaces/SideBar/MembersRoleSection.vue'
import { defaultPlugins, shallowMount } from '@opencloud-eu/web-test-helpers'
import { mock } from 'vitest-mock-extended'
import { SpaceMember } from '@opencloud-eu/web-client'

describe('MembersRoleSection', () => {
it('should render all members accordingly', () => {
const members = [
mock<SpaceMember>({ grantedTo: { user: { displayName: 'einstein' }, group: undefined } }),
mock<SpaceMember>({ grantedTo: { group: { displayName: 'physic-lovers' }, user: undefined } })
const permissions = [
mock<Permission>({ grantedToV2: { user: { displayName: 'einstein' }, group: undefined } }),
mock<Permission>({
grantedToV2: { group: { displayName: 'physic-lovers' }, user: undefined }
})
]
const { wrapper } = getWrapper({ members })
const { wrapper } = getWrapper({ permissions })
expect(wrapper.html()).toMatchSnapshot()
})
})

function getWrapper({ members = [] }: { members?: SpaceMember[] } = {}) {
function getWrapper({ permissions = [] }: { permissions?: Permission[] } = {}) {
return {
wrapper: shallowMount(MembersRoleSection, {
props: {
members
permissions
},
global: {
plugins: [...defaultPlugins()]
Expand Down
Loading