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

refactor: remove aggregateResourceShares and share prop on share resources #10426

Merged
merged 2 commits into from
Feb 6, 2024
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 @@ -65,7 +65,6 @@ import {
} from '@ownclouders/web-pkg'
import { computed, defineComponent, inject, Ref, ref, unref, watch } from 'vue'
import { isShareSpaceResource, Resource, SpaceResource } from '@ownclouders/web-client/src/helpers'
import { SharePermissions, isShareResource } from '@ownclouders/web-client/src/helpers/share'
import { useTask } from 'vue-concurrency'
import { useGettext } from 'vue3-gettext'

Expand Down Expand Up @@ -120,10 +119,6 @@ export default defineComponent({
if (unref(resource).permissions !== undefined) {
return unref(resource).permissions.includes(DavPermission.Updateable)
}
const res = unref(resource)
if (isShareResource(res) && res.share?.role) {
return res.share.role.hasPermission(SharePermissions.update)
}
Comment on lines -123 to -126
Copy link
Contributor Author

Choose a reason for hiding this comment

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

File versions currently can't be loaded for shares (including files inside a shared folder), so this check is not needed at the moment. If needed later it should check the graph permissions instead.

}

return true
Expand Down
23 changes: 10 additions & 13 deletions packages/web-app-files/src/services/folder/loaderSpace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { unref } from 'vue'
import { FolderLoaderOptions } from './types'
import { authService } from 'web-runtime/src/services/auth'
import { useFileRouteReplace } from '@ownclouders/web-pkg'
import { aggregateResourceShares } from '@ownclouders/web-client/src/helpers/share'
import { IncomingShareResource } from '@ownclouders/web-client/src/helpers/share'
import { getIndicators } from '@ownclouders/web-pkg'

export class FolderLoaderSpace implements FolderLoader {
Expand All @@ -32,9 +32,8 @@ export class FolderLoaderSpace implements FolderLoader {
}

public getTask(context: TaskContext): FolderLoaderTask {
const { spacesStore, router, clientService, configStore, capabilityStore, resourcesStore } =
context
const { owncloudSdk: client, webdav } = clientService
const { router, clientService, resourcesStore } = context
const { webdav } = clientService
const { replaceInvalidFileRoute } = useFileRouteReplace({ router })

return useTask(function* (
Expand All @@ -60,15 +59,13 @@ export class FolderLoaderSpace implements FolderLoader {

if (path === '/') {
if (isShareSpaceResource(space)) {
const parentShare = yield client.shares.getShare(space.shareId)
const aggregatedShares = aggregateResourceShares({
shares: [parentShare.shareInfo],
spaces: spacesStore.spaces,
allowSharePermission: capabilityStore.sharingResharing,
incomingShares: true,
fullShareOwnerPaths: configStore.options.routing.fullShareOwnerPaths
})
currentFolder = aggregatedShares[0]
// FIXME: it would be cleaner to fetch the driveItem as soon as graph api is capable of it
currentFolder = {
...currentFolder,
id: space.shareId,
syncEnabled: true,
canShare: () => false
} as IncomingShareResource
} else if (!isPersonalSpaceResource(space) && !isPublicSpaceResource(space)) {
// note: in the future we might want to show the space as root for personal spaces as well (to show quota and the like). Currently not needed.
currentFolder = space
Expand Down
6 changes: 4 additions & 2 deletions packages/web-app-files/src/views/shares/SharedWithOthers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export default defineComponent({
resourcesViewDefaults

const shareTypes = computed(() => {
const uniqueShareTypes = uniq(unref(paginatedResources).map((i) => i.share?.shareType))
const uniqueShareTypes = uniq(unref(paginatedResources).flatMap((i) => i.shareTypes))
return ShareTypes.getByValues(uniqueShareTypes)
})
const selectedShareTypesQuery = useRouteQuery('q_shareType')
Expand All @@ -155,7 +155,9 @@ export default defineComponent({
return unref(paginatedResources)
}
return unref(paginatedResources).filter((item) => {
return selectedShareTypes.map((t) => ShareTypes[t].value).includes(item.share.shareType)
return selectedShareTypes
.map((t) => ShareTypes[t].value)
.some((t) => item.shareTypes.includes(t))
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,15 @@ describe('FileVersions', () => {
expect(revertVersionButton.length).toBe(defaultVersions.length)
})
it('should be possible for a share with write permissions', async () => {
const resource = mockDeep<ShareResource>({
permissions: DavPermission.Updateable,
share: undefined
})
const resource = mockDeep<ShareResource>({ permissions: DavPermission.Updateable })
const space = mockDeep<ShareSpaceResource>({ driveType: 'share' })
const { wrapper } = getMountedWrapper({ resource, space })
await wrapper.vm.fetchVersionsTask.last
const revertVersionButton = wrapper.findAll(selectors.revertVersionButton)
expect(revertVersionButton.length).toBe(defaultVersions.length)
})
it('should not be possible for a share with read-only permissions', async () => {
const resource = mockDeep<ShareResource>({ permissions: '', share: undefined })
const resource = mockDeep<ShareResource>({ permissions: '' })
const space = mockDeep<ShareSpaceResource>({ driveType: 'share' })
const { wrapper } = getMountedWrapper({ resource, space })
await wrapper.vm.fetchVersionsTask.last
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ describe('SharedWithOthers view', () => {
it('shows filter if multiple share types are present', () => {
const { wrapper } = getMountedWrapper({
files: [
mock<ShareResource>({ share: { shareType: ShareTypes.user.value } }),
mock<ShareResource>({ share: { shareType: ShareTypes.group.value } })
mock<ShareResource>({ shareTypes: [ShareTypes.user.value] }),
mock<ShareResource>({ shareTypes: [ShareTypes.group.value] })
]
})
expect(wrapper.find('.share-type-filter').exists()).toBeTruthy()
})
it('does not show filter if only one share type is present', () => {
const { wrapper } = getMountedWrapper({
files: [mock<ShareResource>({ share: { shareType: ShareTypes.user.value } })]
files: [mock<ShareResource>({ shareTypes: [ShareTypes.user.value] })]
})
expect(wrapper.find('.share-type-filter').exists()).toBeFalsy()
})
Expand Down
191 changes: 1 addition & 190 deletions packages/web-client/src/helpers/share/functions.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { orderBy } from 'lodash-es'
import { DateTime } from 'luxon'
import {
Resource,
extractDomSelector,
extractExtensionFromFile,
extractStorageId
} from '../resource'
import { Resource } from '../resource'
import { ShareTypes } from './type'
import path from 'path'
import { SHARE_JAIL_ID, SpaceResource, buildWebDavSpacesPath } from '../space'
import { SharePermissions } from './permission'
import { buildSpaceShare } from './space'
import { LinkShareRoles, PeopleShareRoles } from './role'
Expand All @@ -26,187 +18,6 @@ export const isIncomingShareResource = (resource: Resource): resource is Incomin
return isShareResource(resource) && !resource.outgoing
}

/**
* Transforms given shares into a resource format and returns only their unique occurences
*
* @deprecated
*/
export function aggregateResourceShares({
shares,
spaces,
allowSharePermission = true,
incomingShares = false,
fullShareOwnerPaths = false
}: {
shares: Share[]
spaces: SpaceResource[]
allowSharePermission?: boolean
incomingShares?: boolean
fullShareOwnerPaths?: boolean
}): Resource[] {
shares.sort((a, b) => a.path.localeCompare(b.path))
if (spaces.length) {
shares = addMatchingSpaceToShares(shares, spaces)
}
if (incomingShares) {
shares = addSharedWithToShares(shares)
return orderBy(shares, ['file_source', 'permissions'], ['asc', 'desc']).map((share) => {
const resource = buildSharedResource(share, incomingShares, allowSharePermission)
resource.shareId = share.id

if (fullShareOwnerPaths) {
resource.path = spaces.find(
(space) =>
space.driveType === 'mountpoint' &&
space.id === `${SHARE_JAIL_ID}$${SHARE_JAIL_ID}!${resource.shareId}`
)?.root.remoteItem.path
resource.shareRoot = resource.path
}
return resource
})
}

const resources = addSharedWithToShares(shares)
return resources.map((share) => buildSharedResource(share, incomingShares, allowSharePermission))
}

function addSharedWithToShares(shares) {
const resources = []
let previousShare = null
for (const share of shares) {
if (
previousShare?.storage_id === share.storage_id &&
previousShare?.file_source === share.file_source
) {
if (ShareTypes.containsAnyValue(ShareTypes.authenticated, [parseInt(share.share_type)])) {
if (share.stime > previousShare.stime) {
previousShare.stime = share.stime
}
previousShare.sharedWith.push({
id: share.share_with,
displayName: share.share_with_displayname
})
} else if (parseInt(share.share_type) === ShareTypes.link.value) {
previousShare.sharedWith.push({
id: share.token,
displayName: share.name || share.token
})
}

continue
}

if (ShareTypes.containsAnyValue(ShareTypes.authenticated, [parseInt(share.share_type)])) {
share.sharedWith = [
{
id: share.share_with,
displayName: share.share_with_displayname
}
]
} else if (parseInt(share.share_type) === ShareTypes.link.value) {
share.sharedWith = [
{
id: share.token,
displayName: share.name || share.token
}
]
}

previousShare = share
resources.push(share)
}
return resources
}

function addMatchingSpaceToShares(shares, spaces) {
const resources = []
for (const share of shares) {
let matchingSpace
if (share.path === '/') {
const storageId = extractStorageId(share.item_source)
matchingSpace = spaces.find((s) => s.id === storageId && s.driveType === 'project')
}
resources.push({ ...share, matchingSpace })
}
return resources
}

/** @deprecated */
export function buildSharedResource(
share,
incomingShares = false,
allowSharePermission = true
): ShareResource {
const isFolder = share.item_type === 'folder'
const isRemoteShare = parseInt(share.share_type) === ShareTypes.remote.value
let resource: ShareResource = {
id: share.id,
fileId: share.item_source,
storageId: extractStorageId(share.item_source),
parentFolderId: share.file_parent,
type: share.item_type,
mimeType: share.mimetype,
isFolder,
sdate: DateTime.fromSeconds(parseInt(share.stime)).toRFC2822(),
indicators: [],
tags: [],
path: undefined,
webDavPath: undefined,
processing: share.processing || false,
shareTypes: [parseInt(share.share_type)],
owner: { id: share.uid_owner, displayName: share.displayname_owner },
sharedBy: { id: share.uid_owner, displayName: share.displayname_owner },
sharedWith: share.sharedWith || [],
outgoing: !incomingShares
}

if (incomingShares) {
;(resource as IncomingShareResource).syncEnabled = parseInt(share.state) === 0
;(resource as IncomingShareResource).hidden = share.hidden === 'true' || share.hidden === true
resource.name = isRemoteShare ? share.name : path.basename(share.file_target)
if (isRemoteShare) {
resource.path = '/'
resource.webDavPath = buildWebDavSpacesPath(share.space_id, '/')
} else {
// FIXME, HACK 1: path needs to be '/' because the share has it's own webdav endpoint (we access it's root). should ideally be removed backend side.
// FIXME, HACK 2: webDavPath points to `files/<user>/Shares/xyz` but now needs to point to a shares webdav root. should ideally be changed backend side.
resource.path = '/'
resource.webDavPath = buildWebDavSpacesPath([SHARE_JAIL_ID, resource.id].join('!'), '/')
}
resource.canDownload = () => parseInt(share.state) === 0
resource.canShare = () => SharePermissions.share.enabled(share.permissions)
resource.canRename = () => parseInt(share.state) === 0
resource.canBeDeleted = () => SharePermissions.delete.enabled(share.permissions)
resource.canEditTags = () =>
parseInt(share.state) === 0 && SharePermissions.update.enabled(share.permissions)
} else {
resource.name = isRemoteShare ? share.name : path.basename(share.path)
resource.path = share.path
resource.webDavPath = buildWebDavSpacesPath(resource.storageId, share.path)

resource.canDownload = () => true
resource.canShare = () => true
resource.canRename = () => true
resource.canBeDeleted = () => true
resource.canEditTags = () => true
}

resource.extension = extractExtensionFromFile(resource)
resource.isReceivedShare = () => incomingShares
resource.canUpload = () => SharePermissions.create.enabled(share.permissions)
resource.canCreate = () => SharePermissions.create.enabled(share.permissions)
resource.isMounted = () => false
resource.share = buildShare(share, resource, allowSharePermission)
resource.canDeny = () => SharePermissions.denied.enabled(share.permissions)
resource.getDomSelector = () => extractDomSelector(share.id)

if (share.matchingSpace) {
resource = { ...resource, ...share.matchingSpace }
}

return resource
}

export function buildShare(s, file, allowSharePermission): Share {
if (parseInt(s.share_type) === ShareTypes.link.value) {
return _buildLink(s)
Expand Down
2 changes: 0 additions & 2 deletions packages/web-client/src/helpers/share/functionsNG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ export function buildIncomingShareResource({
: { ...permission?.grantedToV2.user, shareType }
],
shareTypes: [shareType],
share: driveItem,
isFolder: !!driveItem.remoteItem.folder,
type: !!driveItem.remoteItem.folder ? 'folder' : 'file',
mimeType: driveItem.remoteItem.file?.mimeType || 'httpd/unix-directory',
Expand Down Expand Up @@ -166,7 +165,6 @@ export function buildOutgoingShareResource({
}
return ShareTypes.user.value
}),
share: driveItem,
isFolder: !!driveItem.folder,
type: !!driveItem.folder ? 'folder' : 'file',
mimeType: driveItem.file?.mimeType || 'httpd/unix-directory',
Expand Down
2 changes: 0 additions & 2 deletions packages/web-client/src/helpers/share/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ export interface ShareResource extends Resource {
sharedWith: Array<{ shareType: number } & Identity>
sharedBy: Identity
outgoing: boolean

share?: any // FIXME: type DriveItem OR remove? do we want to expose the share on each resource?
}
export interface OutgoingShareResource extends ShareResource {}

Expand Down
5 changes: 4 additions & 1 deletion tests/e2e/support/objects/app-files/resource/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@ export const clickResource = async ({
const itemId = await resource.locator(fileRow).getAttribute('data-item-id')
await Promise.all([
page.waitForResponse(
(resp) => resp.url().endsWith(encodeURIComponent(name)) || resp.url().endsWith(itemId)
(resp) =>
resp.url().endsWith(encodeURIComponent(name)) ||
resp.url().endsWith(itemId) ||
resp.url().endsWith(encodeURIComponent(itemId))
),
resource.click()
])
Expand Down