Skip to content

Commit

Permalink
refactor: remove hard-coded space roles, use permissions instead
Browse files Browse the repository at this point in the history
  • Loading branch information
Jannik Stehle committed Aug 21, 2024
1 parent c4cc29f commit 1401f72
Show file tree
Hide file tree
Showing 61 changed files with 1,002 additions and 842 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,103 +9,72 @@
<div v-if="!filteredSpaceMembers.length">
<h3 class="oc-text-bold oc-text-medium" v-text="$gettext('No members found')" />
</div>
<div
v-if="filteredSpaceManagers.length"
class="oc-mb-m"
data-testid="space-members-role-managers"
>
<h3 class="oc-text-bold oc-text-medium" v-text="$gettext('Managers')" />
<members-role-section :members="filteredSpaceManagers" />
<div v-for="(role, i) in availableRoles" :key="i" class="oc-mb-m">
<div v-if="getMembersForRole(role).length">
<h3 class="oc-text-bold oc-text-medium" v-text="role.displayName" />
<members-role-section :members="getMembersForRole(role)" />
</div>
</div>
<div
v-if="filteredSpaceEditors.length"
class="oc-mb-m"
data-testid="space-members-role-editors"
>
<h3 class="oc-text-bold oc-text-medium" v-text="$gettext('Editors')" />
<members-role-section :members="filteredSpaceEditors" />
</div>
<div
v-if="filteredSpaceViewers.length"
class="oc-mb-m"
data-testid="space-members-role-viewers"
>
<h3 class="oc-text-bold oc-text-medium" v-text="$gettext('Viewers')" />
<members-role-section :members="filteredSpaceViewers" />
</div>
<div
v-if="filteredSpaceSecureViewers.length"
class="oc-mb-m"
data-testid="space-members-role-secure-viewers"
>
<h3 class="oc-text-bold oc-text-medium" v-text="$gettext('Secure viewers')" />
<members-role-section :members="filteredSpaceSecureViewers" />
<div v-if="membersWithoutRole.length">
<h3 class="oc-text-bold oc-text-medium" v-text="$gettext('Custom role')" />
<members-role-section :members="membersWithoutRole" />
</div>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, inject, ref, watch, unref } from 'vue'
import { SpaceResource, SpaceRole } from '@ownclouders/web-client'
import { ShareRole, SpaceMember, SpaceResource } from '@ownclouders/web-client'
import MembersRoleSection from './MembersRoleSection.vue'
import Fuse from 'fuse.js'
import Mark from 'mark.js'
import { defaultFuseOptions } from '@ownclouders/web-pkg'
import { defaultFuseOptions, useSharesStore } from '@ownclouders/web-pkg'
export default defineComponent({
name: 'MembersPanel',
components: { MembersRoleSection },
setup() {
const sharesStore = useSharesStore()
const resource = inject<SpaceResource>('resource')
const filterTerm = ref('')
const markInstance = ref(null)
const membersListRef = ref(null)
const filterMembers = (collection: Array<SpaceRole & { roleType: string }>, term: string) => {
const filterMembers = (collection: SpaceMember[], term: string) => {
if (!(term || '').trim()) {
return collection
}
const searchEngine = new Fuse(collection, { ...defaultFuseOptions, keys: ['displayName'] })
const searchEngine = new Fuse(collection, {
...defaultFuseOptions,
keys: ['grantedTo.user.displayName', 'grantedTo.group.displayName']
})
return searchEngine.search(term).map((r) => r.item)
}
const spaceMembers = computed(() => {
return [
...unref(resource).spaceRoles.manager.map((r) => ({
...r,
roleType: 'manager'
})),
...unref(resource).spaceRoles.editor.map((r) => ({
...r,
roleType: 'editor'
})),
...unref(resource).spaceRoles.viewer.map((r) => ({
...r,
roleType: 'viewer'
})),
...unref(resource).spaceRoles['secure-viewer'].map((r) => ({
...r,
roleType: 'secure-viewer'
}))
].sort((a, b) => a.displayName.localeCompare(b.displayName))
const spaceMembers = computed<SpaceMember[]>(() => {
return Object.values(unref(resource).members)
})
const filteredSpaceMembers = computed(() => {
const filteredSpaceMembers = computed<SpaceMember[]>(() => {
return filterMembers(unref(spaceMembers), unref(filterTerm))
})
const filteredSpaceManagers = computed(() => {
return unref(filteredSpaceMembers).filter((m) => m.roleType === 'manager')
})
const filteredSpaceEditors = computed(() => {
return unref(filteredSpaceMembers).filter((m) => m.roleType === 'editor')
})
const filteredSpaceViewers = computed(() => {
return unref(filteredSpaceMembers).filter((m) => m.roleType === 'viewer')
const availableRoles = computed<ShareRole[]>(() => {
const permissionsWithRole = unref(spaceMembers).filter((p) => !!p.roleId)
const roleIds = [...new Set(permissionsWithRole.map((p) => p.roleId))]
return roleIds.map((r) => sharesStore.graphRoles.find(({ id }) => id === r)).filter(Boolean)
})
const filteredSpaceSecureViewers = computed(() => {
return unref(filteredSpaceMembers).filter((m) => m.roleType === 'secure-viewer')
const membersWithoutRole = computed<SpaceMember[]>(() => {
return unref(filteredSpaceMembers).filter(({ roleId }) => !roleId)
})
const getMembersForRole = (role: ShareRole): SpaceMember[] => {
return unref(filteredSpaceMembers).filter(({ roleId }) => roleId === role.id)
}
watch(filterTerm, () => {
if (unref(membersListRef)) {
markInstance.value = new Mark(unref(membersListRef))
Expand All @@ -120,11 +89,10 @@ export default defineComponent({
return {
filterTerm,
filteredSpaceMembers,
filteredSpaceManagers,
filteredSpaceEditors,
filteredSpaceViewers,
filteredSpaceSecureViewers,
membersListRef
membersListRef,
availableRoles,
membersWithoutRole,
getMembersForRole
}
}
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<template>
<ul class="oc-list">
<li
v-for="(member, index) in members"
v-for="(m, index) in members"
:key="index"
class="oc-flex oc-flex-middle oc-mb-s"
data-testid="space-members-list"
>
<oc-avatar
v-if="member.kind === 'user'"
:user-name="member.displayName"
v-if="m.grantedTo.user"
:user-name="m.grantedTo.user.displayName"
:width="36"
class="oc-mr-s"
/><oc-avatar-item
Expand All @@ -19,20 +19,19 @@
name="group"
class="oc-mr-s"
/>
{{ member.displayName }}
{{ (m.grantedTo.user || m.grantedTo.group).displayName }}
</li>
</ul>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { ShareTypes } from '@ownclouders/web-client'
import { SpaceRole } from '@ownclouders/web-client'
import { ShareTypes, SpaceMember } from '@ownclouders/web-client'
export default defineComponent({
name: 'MembersRoleSection',
props: {
members: {
type: Array as PropType<SpaceRole[]>,
type: Array as PropType<SpaceMember[]>,
required: true
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ import {
unref,
watch
} from 'vue'
import { SpaceResource } from '@ownclouders/web-client'
import { getSpaceManagers, SpaceResource } from '@ownclouders/web-client'
import Mark from 'mark.js'
import Fuse from 'fuse.js'
import { useGettext } from 'vue3-gettext'
Expand Down Expand Up @@ -352,9 +352,11 @@ export default defineComponent({
])
const getManagerNames = (space: SpaceResource) => {
const allManagers = space.spaceRoles.manager
const allManagers = getSpaceManagers(space)
const managers = allManagers.length > 2 ? allManagers.slice(0, 2) : allManagers
let managerStr = managers.map((m) => m.displayName).join(', ')
let managerStr = managers
.map(({ grantedTo }) => (grantedTo.user || grantedTo.group).displayName)
.join(', ')
if (allManagers.length > 2) {
managerStr += `... +${allManagers.length - 2}`
}
Expand Down Expand Up @@ -386,11 +388,7 @@ export default defineComponent({
return formatFileSize(space.spaceQuota.remaining, language.current)
}
const getMemberCount = (space: SpaceResource) => {
return (
space.spaceRoles.manager.length +
space.spaceRoles.editor.length +
space.spaceRoles.viewer.length
)
return Object.keys(space.members).length
}
const getSelectSpaceLabel = (space: SpaceResource) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ import {
useCapabilityStore,
useEventBus,
useMessages,
useSpacesStore
useSpacesStore,
useSharesStore
} from '@ownclouders/web-pkg'
import GroupSelect from '../GroupSelect.vue'
import { cloneDeep, isEmpty, isEqual, omit } from 'lodash-es'
Expand Down Expand Up @@ -165,6 +166,7 @@ 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 @@ -225,10 +227,14 @@ export default defineComponent({
const onUpdateUserDrive = async (editUser: User) => {
const client = clientService.graphAuthenticated
const updateSpace = await client.drives.updateDrive(editUser.drive.id, {
name: editUser.drive.name,
quota: { total: editUser.drive.quota.total }
})
const updateSpace = await client.drives.updateDrive(
editUser.drive.id,
{
name: editUser.drive.name,
quota: { total: editUser.drive.quota.total }
},
sharesStore.graphRoles
)
if (editUser.id === userStore.user.id) {
// Load current user quota
Expand Down
6 changes: 4 additions & 2 deletions packages/web-app-admin-settings/src/views/Spaces.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ import {
useSpaceActionsDisable,
useSpaceActionsRestore,
useSpaceActionsEditQuota,
AppLoadingSpinner
AppLoadingSpinner,
useSharesStore
} from '@ownclouders/web-pkg'
import { call, SpaceResource } from '@ownclouders/web-client'
import { computed, defineComponent, onBeforeUnmount, onMounted, ref, unref } from 'vue'
Expand Down Expand Up @@ -100,6 +101,7 @@ export default defineComponent({
const clientService = useClientService()
const { $gettext } = useGettext()
const { isSideBarOpen, sideBarActivePanel } = useSideBar()
const sharesStore = useSharesStore()
const loadResourcesEventToken = ref(null)
let updateQuotaForSpaceEventToken: string
Expand All @@ -119,7 +121,7 @@ export default defineComponent({
const loadResourcesTask = useTask(function* (signal) {
const drives = yield* call(
clientService.graphAuthenticated.drives.listAllDrives({
clientService.graphAuthenticated.drives.listAllDrives(sharesStore.graphRoles, {
orderBy: 'name asc',
filter: 'driveType eq project'
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import MembersPanel from '../../../../../src/components/Spaces/SideBar/MembersPanel.vue'
import { defaultPlugins, shallowMount } from 'web-test-helpers'
import { mock } from 'vitest-mock-extended'
import { SpaceResource } from '@ownclouders/web-client'
import { ShareRole, SpaceMember, SpaceResource } from '@ownclouders/web-client'
import MembersRoleSection from '../../../../../src/components/Spaces/SideBar/MembersRoleSection.vue'

const graphRoles = {
'1': mock<ShareRole>({ displayName: 'managers' }),
'2': mock<ShareRole>({ displayName: 'editors' }),
'3': mock<ShareRole>({ displayName: 'viewers' })
}

const spaceMock = mock<SpaceResource>({
spaceRoles: {
manager: [{ kind: 'user', displayName: 'admin' }],
editor: [
{ kind: 'user', displayName: 'einstein' },
{ kind: 'group', displayName: 'physic-haters' }
],
viewer: [{ kind: 'user', displayName: 'marie' }],
'secure-viewer': [{ kind: 'user', displayName: 'kathrine' }]
}
members: [
mock<SpaceMember>({ roleId: '1', grantedTo: { user: { displayName: 'admin' } } }),
mock<SpaceMember>({ roleId: '2', grantedTo: { user: { displayName: 'marie' } } }),
mock<SpaceMember>({ roleId: '3', grantedTo: { user: { displayName: 'einstein' } } })
]
})

const selectors = {
Expand All @@ -26,15 +28,15 @@ describe('MembersPanel', () => {
expect(wrapper.html()).toMatchSnapshot()
})
it('should filter members accordingly to the entered search term', async () => {
const userToFilterFor = spaceMock.spaceRoles.editor[0]
const userToFilterFor = spaceMock.members[2]
const { wrapper } = getWrapper()
wrapper.vm.filterTerm = 'ein'
await wrapper.vm.$nextTick
await wrapper.vm.$nextTick()
expect(wrapper.findAll(selectors.membersRolePanelStub).length).toBe(1)
expect(
wrapper.findComponent<typeof MembersRoleSection>(selectors.membersRolePanelStub).props()
.members[0].displayName
).toEqual(userToFilterFor.displayName)
.members[0].grantedTo.user.displayName
).toEqual(userToFilterFor.grantedTo.user.displayName)
})
it('should display an empty result if no matching members found', async () => {
const { wrapper } = getWrapper()
Expand All @@ -49,7 +51,7 @@ function getWrapper({ spaceResource = spaceMock } = {}) {
return {
wrapper: shallowMount(MembersPanel, {
global: {
plugins: [...defaultPlugins()],
plugins: [...defaultPlugins({ piniaOptions: { sharesState: { graphRoles } } })],
provide: { resource: spaceResource }
}
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import MembersRoleSection from '../../../../../src/components/Spaces/SideBar/MembersRoleSection.vue'
import { defaultPlugins, shallowMount } from 'web-test-helpers'
import { mock } from 'vitest-mock-extended'
import { SpaceRole } from '@ownclouders/web-client'
import { SpaceMember } from '@ownclouders/web-client'

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

function getWrapper({ members = [] }: { members?: SpaceRole[] } = {}) {
function getWrapper({ members = [] }: { members?: SpaceMember[] } = {}) {
return {
wrapper: shallowMount(MembersRoleSection, {
props: {
Expand Down
Loading

0 comments on commit 1401f72

Please sign in to comment.