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
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,7 @@ export default defineComponent({
},

showSpaceMembers() {
return (
this.space?.driveType === 'project' &&
this.resource.type !== 'space' &&
this.space?.isMember(this.user)
)
return isProjectSpaceResource(this.space) && this.resource.type !== 'space'
}
},
methods: {
Expand Down
24 changes: 0 additions & 24 deletions packages/web-app-files/src/views/spaces/Projects.vue
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,6 @@
/>
</span>
</template>
<template #manager="{ resource }">
{{ getManagerNames(resource) }}
</template>
<template #members="{ resource }">
{{ getMemberCount(resource) }}
</template>
<template #totalQuota="{ resource }">
{{ getTotalQuota(resource) }}
</template>
Expand Down Expand Up @@ -186,7 +180,6 @@ import {
} from '@opencloud-eu/web-pkg'
import SpaceContextActions from '../../components/Spaces/SpaceContextActions.vue'
import {
getSpaceManagers,
isProjectSpaceResource,
ProjectSpaceResource,
SpaceResource
Expand Down Expand Up @@ -261,8 +254,6 @@ const selectedSpace = computed(() => {
const tableDisplayFields = [
'image',
'name',
'manager',
'members',
'totalQuota',
'usedQuota',
'remainingQuota',
Expand Down Expand Up @@ -355,18 +346,6 @@ useKeyboardFileNavigation(keyActions, runtimeSpaces, viewMode)
useKeyboardFileMouseActions(keyActions, viewMode)
useKeyboardFileActions(keyActions)

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)
.join(', ')
if (allManagers.length > 2) {
managerStr += `... +${allManagers.length - 2}`
}
return managerStr
}

const getTotalQuota = (space: SpaceResource) => {
if (space.spaceQuota.total === 0) {
return $gettext('Unrestricted')
Expand All @@ -389,9 +368,6 @@ const getRemainingQuota = (space: SpaceResource) => {
}
return formatFileSize(space.spaceQuota.remaining, language.current)
}
const getMemberCount = (space: SpaceResource) => {
return Object.keys(space.members).length
}

onMounted(async () => {
await loadResourcesTask.perform()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ describe('FileShares', () => {
describe('current space', () => {
it('loads space members if a space is given and the current user is member', () => {
const user = { id: '1' } as User
const space = mock<SpaceResource>({ driveType: 'project', isMember: () => true })
const space = mock<SpaceResource>({ driveType: 'project' })
const spaceMembers = [
{ sharedWith: { id: user.id, displayName: '' }, resourceId: space.id, permissions: [] },
{ sharedWith: { id: '2', displayName: '' }, resourceId: space.id, permissions: [] }
Expand All @@ -134,20 +134,6 @@ describe('FileShares', () => {
expect(wrapper.findAll('#files-collaborators-list li').length).toBe(1)
expect(wrapper.html()).toMatchSnapshot()
})
it('does not load space members if a space is given but the current user not a member', () => {
const user = { id: '1' } as User
const space = mock<SpaceResource>({ driveType: 'project' })
const spaceMembers = [
{ sharedWith: { id: `${user}-2`, displayName: '' }, resourceId: space.id, permissions: [] }
] as CollaboratorShare[]
const collaborator = getCollaborator()
collaborator.sharedWith = {
...collaborator.sharedWith,
id: user.id
}
const { wrapper } = getWrapper({ space, collaborators: [collaborator], user, spaceMembers })
expect(wrapper.find('#space-collaborators-list').exists()).toBeFalsy()
})
})

describe('"deleteShareConfirmation" method', () => {
Expand Down
17 changes: 6 additions & 11 deletions packages/web-client/src/helpers/space/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
} from '../resource'
import {
isPersonalSpaceResource,
isPublicSpaceResource,
PublicSpaceResource,
ShareSpaceResource,
SpaceMember,
Expand All @@ -19,7 +18,7 @@ import { DavProperty } from '../../webdav/constants'
import { buildWebDavPublicPath, buildWebDavOcmPath } from '../publicLink'
import { urlJoin } from '../../utils'
import { Drive, DriveItem } from '@opencloud-eu/web-client/graph/generated'
import { GraphSharePermission, ShareRole } from '../share'
import { CollaboratorShare, GraphSharePermission, ShareRole } from '../share'

export function buildWebDavSpacesPath(storageId: string, path?: string) {
return urlJoin('spaces', storageId, path, {
Expand Down Expand Up @@ -55,6 +54,11 @@ export function getSpaceManagers(space: SpaceResource) {
)
}

export function isManager(share: CollaboratorShare) {
Copy link

Copilot AI May 20, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider adding a code comment to clarify that the isManager function interprets delete permissions as a sign of management rights, which aids clarity for future maintainers.

Copilot uses AI. Check for mistakes.
// delete permissions implies that the user/group is a manager
return share.permissions.includes(GraphSharePermission.deletePermissions)
}

export type PublicLinkType = 'ocm' | 'public-link'
export function buildPublicSpaceResource(
data: any & { publicLinkType: PublicLinkType }
Expand Down Expand Up @@ -332,15 +336,6 @@ export function buildSpace(
getWebDavTrashUrl({ path }: { path: string }): string {
return urlJoin(webDavTrashUrl, path)
},
isMember(user: User): boolean {
if (isPublicSpaceResource(this)) {
return false
}
if (this.isOwner(user) || !!this.members[user.id]) {
return true
}
return user.memberOf?.some((group) => !!this.members[group.id])
},
isOwner(user: User): boolean {
return user?.id === this.owner?.id
}
Expand Down
1 change: 0 additions & 1 deletion packages/web-client/src/helpers/space/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export interface SpaceResource extends Resource {
getWebDavTrashUrl({ path }: { path: string }): string
getDriveAliasAndItem(resource: Resource): string

isMember(user: User): boolean
isOwner(user: User): boolean
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { inject, ref, Ref, computed, unref, watch } from 'vue'
import { buildSpaceImageResource, getSpaceManagers, SpaceResource } from '@opencloud-eu/web-client'
import { buildSpaceImageResource, isManager, SpaceResource } from '@opencloud-eu/web-client'
import {
useUserStore,
useSharesStore,
Expand Down Expand Up @@ -122,6 +122,10 @@ const spaceImage = ref('')

const { user } = storeToRefs(userStore)

const spaceMembers = computed(() => {
return spacesStore.getSpaceMembers(unref(resource))
})

const linkShareCount = computed(() => sharesStore.linkShares.length)
const showWebDavDetails = computed(() => resourcesStore.areWebDavDetailsShown)
const showSize = computed(() => {
Expand Down Expand Up @@ -153,14 +157,13 @@ watch(
)

const ownerUsernames = computed(() => {
const managers = getSpaceManagers(unref(resource))
return managers
.map((share) => {
const member = share.grantedTo.user || share.grantedTo.group
if (member.id === unref(user)?.id) {
return $gettext('%{displayName} (me)', { displayName: member.displayName })
const managerPermissions = unref(spaceMembers).filter(isManager)
return managerPermissions
.map(({ sharedWith }) => {
if (sharedWith.id === unref(user)?.id) {
return $gettext('%{displayName} (me)', { displayName: sharedWith.displayName })
}
return member.displayName
return sharedWith.displayName
})
.join(', ')
})
Expand Down Expand Up @@ -216,7 +219,7 @@ const hasLinkShares = computed(() => {
return unref(linkShareCount) > 0
})
const memberShareCount = computed(() => {
return Object.keys(unref(resource).members).length
return unref(spaceMembers).length
})
const memberShareLabel = computed(() => {
return $ngettext(
Expand Down
4 changes: 2 additions & 2 deletions packages/web-pkg/src/composables/piniaStores/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ export const useResourcesStore = defineStore('resources', () => {
let fullyAccessibleSpace = true
if (configStore.options.routing.fullShareOwnerPaths) {
// keep logic in sync with "isResourceAccessible" from useGetMatchingSpace
const projectSpace = spaces.find((s) => isProjectSpaceResource(s) && s.id === space.id)
fullyAccessibleSpace = space.isOwner(userStore.user) || projectSpace?.isMember(userStore.user)
const projectSpace = spaces.some((s) => isProjectSpaceResource(s) && s.id === space.id)
fullyAccessibleSpace = space.isOwner(userStore.user) || projectSpace
}

for (const path of parentPaths) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,8 @@ export const useGetMatchingSpace = (options?: GetMatchingSpaceOptions) => {
return true
}

const projectSpace = unref(spaces).find((s) => isProjectSpaceResource(s) && s.id === space.id)
const fullyAccessibleSpace =
space.isOwner(userStore.user) || projectSpace?.isMember(userStore.user)
const projectSpace = unref(spaces).some((s) => isProjectSpaceResource(s) && s.id === space.id)
const fullyAccessibleSpace = space.isOwner(userStore.user) || projectSpace

return (
fullyAccessibleSpace ||
Expand Down
3 changes: 1 addition & 2 deletions packages/web-pkg/src/helpers/statusIndicators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,7 @@ export const getIndicators = ({
}

const shareIndicatorsAllowed =
(isProjectSpaceResource(space) && space.isMember(user)) ||
(isPersonalSpaceResource(space) && space.isOwner(user))
isProjectSpaceResource(space) || (isPersonalSpaceResource(space) && space.isOwner(user))

if (shareIndicatorsAllowed) {
const parentShareTypes = Object.values(ancestorMetaData).reduce<number[]>((acc, data) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const spaceMock = {
type: 'space',
name: ' space',
id: '1',
driveType: 'project',
mdate: 'Wed, 21 Oct 2015 07:28:00 GMT',
members: [
mock<SpaceMember>({
Expand All @@ -32,10 +33,12 @@ const spaceMock = {
const spaceShare = {
id: '1',
sharedWith: {
id: 'Alice',
id: '1',
displayName: 'alice'
},
role: mock<ShareRole>()
resourceId: '1',
role: mock<ShareRole>(),
permissions: [GraphSharePermission.deletePermissions]
} as CollaboratorShare

const selectors = {
Expand Down Expand Up @@ -82,6 +85,7 @@ function createWrapper({ spaceResource = spaceMock, props = {} } = {}) {
plugins: [
...defaultPlugins({
piniaOptions: {
stubActions: false,
userState: { user: { id: '1', onPremisesSamAccountName: 'marie' } as User },
sharesState: { collaboratorShares: [spaceShare] },
resourcesStore: { resources: [mock<Resource>({ name: 'file1', type: 'file' })] }
Expand Down
11 changes: 2 additions & 9 deletions packages/web-pkg/tests/unit/helpers/statusIndicator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,6 @@ describe('status indicators', () => {
})

describe('sharing indicators', () => {
it('should not be present if the user is not a member of the project space', () => {
const space = mock<SpaceResource>({ driveType: 'project', isMember: () => false })
const resource = mock<Resource>({ id: 'resource', shareTypes: [0, 3] })
const indicators = getIndicators({ space, resource, ancestorMetaData: {}, user })

expect(indicators.some(({ category }) => category === 'sharing')).toBeFalsy()
})
it("should not be present in another user's personal space", () => {
const space = mock<SpaceResource>({ driveType: 'personal', isOwner: () => false })
const resource = mock<Resource>({ id: 'resource', shareTypes: [0, 3] })
Expand All @@ -57,7 +50,7 @@ describe('status indicators', () => {
expect(indicators.some(({ category }) => category === 'sharing')).toBeFalsy()
})
it('should be present for direct collaborator and link shares', () => {
const space = mock<SpaceResource>({ driveType: 'project', isMember: () => true })
const space = mock<SpaceResource>({ driveType: 'project' })
const resource = mock<Resource>({ id: 'resource', shareTypes: [0, 3] })
const indicators = getIndicators({ space, resource, ancestorMetaData: {}, user })

Expand All @@ -73,7 +66,7 @@ describe('status indicators', () => {
'/': mock<AncestorMetaDataValue>({ shareTypes: [0, 3] })
}

const space = mock<SpaceResource>({ driveType: 'project', isMember: () => true })
const space = mock<SpaceResource>({ driveType: 'project' })
const resource = mock<Resource>({ id: 'resource', shareTypes: [] })
const indicators = getIndicators({ space, resource, ancestorMetaData, user })

Expand Down