From b747d299b66a2f9f11559c8c14ce3d6fd64a6135 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Fri, 6 May 2022 15:25:08 +0200 Subject: [PATCH] [full-ci] Sharing jail (#6593) * Fix unit tests * Code cleanup in folder service and tasks * Rename "isEnabled" to "isActive" in folder loaders * make loader tasks context aware (capabilities) * Add share routes in spaces namespace * Route shares into a new view for spaces-aware backend * Add error logging to folder service task execution * Share loader task * Introduce personal space resource loader * Load share root in new view * Fix share view breadcrumb * Fix path and selection handling in SharedResource view * relative path handling for shares * Fix file and folder creation inside share * Include SharedResource route in active state of Shares nav item * Make file actions available on share routes * Switch location picker to personal space dav endpoint * Include current route query in breadcrumb items As of now we want to keep all query options active when navigating up/down a folder hierarchy (sorting, pagination options, and now also the shareId for the SharedResource view). We even have code to restore query options if they are not set. This commit fixes navigation in SharedResource breadcrumbs (those NEED the shareId, otherwise we can't construct the correct webdav path at the moment) and also reduces the occurrences of reloading query options from the local storage (because they are already set via the breadcrumb now). * Add resourceId to whitelisted query items * Rename resourceId query option to shareId * Make use of share_jail feature flag in capabilities.spaces * Fix sharing quick action availability * Fix navigating into shares immediately after accept * Fix unit tests * Fix showing shares in right sidebar on shared with me page * Fix non-share jail shared with me page * Capability usage cleanup after rebase * Fix SharedResource view after rebase * Remove reportGenerator/cucumber_report.json from repo and ignore * Fix share name in right sidebar (shared with me page) * Fix uploads in share jail shares * Code cleanup * Fix folder uploads in shares * Disable renaming of shares for share jail until fixed in backend * Fix Open Folder action for shares * Exclude pending and declined shares for editor app open actions * Fix parent folder links in resource table * fix ocis kindergarten e2e test journey * REVERT: Skip unit&e2e tests * Adjust tests for sharing jail * Use personal space for fetching file info after upload * Load newly created files/folders via personal space * bring back ci e2e tests fix failing e2e tests fix clickResource e2e test helper * fix starlark * Skip last resharing test in oCIS CI Co-authored-by: Florian Schade Co-authored-by: Pascal Wengerter --- .drone.star | 2 +- .gitignore | 1 + .../src/components/AppBar/CreateAndUpload.vue | 82 ++++- .../components/FilesList/ResourceTable.vue | 90 +++-- .../src/components/Search/Preview.vue | 6 +- .../src/components/SideBar/SideBar.vue | 13 + .../composables/upload/useUploadHelpers.ts | 26 +- .../web-app-files/src/helpers/breadcrumbs.ts | 12 +- .../web-app-files/src/helpers/resources.ts | 31 +- .../src/helpers/share/triggerShareAction.js | 4 +- packages/web-app-files/src/index.js | 7 +- .../src/mixins/actions/acceptShare.js | 17 +- .../src/mixins/actions/createQuicklink.js | 1 - .../src/mixins/actions/declineShare.js | 17 +- .../src/mixins/actions/delete.js | 1 + .../src/mixins/actions/downloadArchive.js | 1 + .../src/mixins/actions/downloadFile.js | 3 +- .../src/mixins/actions/navigate.js | 44 ++- .../src/mixins/actions/rename.js | 14 +- .../src/mixins/actions/showShares.js | 1 - .../web-app-files/src/mixins/fileActions.js | 10 +- packages/web-app-files/src/quickActions.js | 13 +- packages/web-app-files/src/router/router.ts | 1 + packages/web-app-files/src/router/spaces.ts | 25 +- packages/web-app-files/src/services/folder.ts | 39 ++- .../src/services/folder/index.ts | 7 +- .../folder/{ => legacy}/loaderPersonal.ts | 40 +-- .../src/services/folder/loaderFavorites.ts | 8 +- .../src/services/folder/loaderPublicFiles.ts | 7 +- .../services/folder/loaderSharedViaLink.ts | 20 +- .../src/services/folder/loaderSharedWithMe.ts | 27 +- .../services/folder/loaderSharedWithOthers.ts | 20 +- .../src/services/folder/loaderTrashbin.ts | 8 +- .../services/folder/spaces/loaderPersonal.ts | 78 +++++ .../folder/{ => spaces}/loaderProject.ts | 16 +- .../src/services/folder/spaces/loaderShare.ts | 42 +++ .../web-app-files/src/services/folder/util.ts | 7 + .../src/views/LocationPicker.vue | 33 +- packages/web-app-files/src/views/Personal.vue | 12 +- .../web-app-files/src/views/PublicFiles.vue | 2 +- packages/web-app-files/src/views/Trashbin.vue | 6 +- .../src/views/shares/SharedResource.vue | 326 ++++++++++++++++++ .../src/views/shares/SharedWithMe.vue | 21 +- .../src/views/spaces/Project.vue | 6 +- .../unit/components/Search/Preview.spec.js | 6 +- .../tests/unit/helpers/breadcrumbs.spec.js | 6 +- .../helpers/share/triggerShareAction.spec.js | 8 +- .../__snapshots__/PublicFiles.spec.ts.snap | 2 +- .../tests/unit/views/spaces/Project.spec.js | 5 + .../tests/unit/views/views.setup.js | 4 + .../composables/capability/useCapability.ts | 5 + .../web-pkg/src/composables/router/index.ts | 1 + .../src/composables/router/useRouteParam.ts | 30 ++ .../web-runtime/src/components/UploadInfo.vue | 6 +- .../web-runtime/src/layouts/Application.vue | 18 +- ...-failures-with-ocis-server-ocis-storage.md | 13 +- .../webUIFilesActionMenu/versions.feature | 2 +- .../features/webUIFilesCopy/copy.feature | 4 +- .../accessToSharesFolder.feature | 58 ++-- .../webUIRenameFiles/renameFiles.feature | 18 +- .../webUIRenameFolders/renameFolders.feature | 14 +- .../webUIResharing1/reshareUsers.feature | 40 +-- .../acceptShares.feature | 8 +- .../shareWithGroups.feature | 4 +- .../shareWithGroupsEdgeCases.feature | 8 +- .../shareWithGroups.feature | 2 +- .../shareWithUsers.feature | 82 ++--- .../shareWithUsers.feature | 2 +- .../sharePermissionsUsers.feature | 20 -- .../{share.feature => share.oc10.feature} | 0 .../features/integrations/share.ocis.feature | 57 +++ .../integrations/spaces/project.ocis.feature | 10 +- ...rten.feature => kindergarten.oc10.feature} | 0 .../journeys/kindergarten.ocis.feature | 87 +++++ .../objects/app-files/resource/actions.ts | 9 +- 75 files changed, 1330 insertions(+), 346 deletions(-) rename packages/web-app-files/src/services/folder/{ => legacy}/loaderPersonal.ts (63%) create mode 100644 packages/web-app-files/src/services/folder/spaces/loaderPersonal.ts rename packages/web-app-files/src/services/folder/{ => spaces}/loaderProject.ts (83%) create mode 100644 packages/web-app-files/src/services/folder/spaces/loaderShare.ts create mode 100644 packages/web-app-files/src/services/folder/util.ts create mode 100644 packages/web-app-files/src/views/shares/SharedResource.vue create mode 100644 packages/web-pkg/src/composables/router/useRouteParam.ts rename tests/e2e/cucumber/features/integrations/{share.feature => share.oc10.feature} (100%) create mode 100644 tests/e2e/cucumber/features/integrations/share.ocis.feature rename tests/e2e/cucumber/features/journeys/{kindergarten.feature => kindergarten.oc10.feature} (100%) create mode 100644 tests/e2e/cucumber/features/journeys/kindergarten.ocis.feature diff --git a/.drone.star b/.drone.star index b4fa48dc03b..cfdbc77a64b 100644 --- a/.drone.star +++ b/.drone.star @@ -717,7 +717,7 @@ def checkTestSuites(): if (type(items) == "list"): suites += items elif (type(items) == "string"): - suites += [key] + suites.append(key) else: print("Error: invalid value for suite, it must be a list or string") return False diff --git a/.gitignore b/.gitignore index ebde8ea145c..766e9d8214a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ dist reports tests/e2e/cucumber/report/cucumber_report.json +tests/e2e/cucumber/reportGenerator/cucumber_report.json tests/ocis tests/testing-app diff --git a/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue b/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue index ea8b7624ccc..cc0ea12dfb0 100644 --- a/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue +++ b/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue @@ -108,7 +108,7 @@ import MixinFileActions, { EDITOR_MODE_CREATE } from '../../mixins/fileActions' import { buildResource, buildWebDavFilesPath, buildWebDavSpacesPath } from '../../helpers/resources' import { isLocationPublicActive, isLocationSpacesActive } from '../../router' import { useActiveLocation } from '../../composables' -import { useAppDefaults } from 'web-pkg/src/composables' +import { useAppDefaults, useCapabilityShareJailEnabled } from 'web-pkg/src/composables' import { DavProperties, DavProperty } from 'web-pkg/src/constants' @@ -116,6 +116,8 @@ import ResourceUpload from './Upload/ResourceUpload.vue' import { defineComponent, getCurrentInstance, onMounted } from '@vue/composition-api' import { UppyResource, useUpload } from 'web-runtime/src/composables/upload' import { useUploadHelpers } from '../../composables/upload' +import { SHARE_JAIL_ID } from '../../services/folder' +import { clientService } from 'web-pkg/src/services' export default defineComponent({ components: { @@ -153,9 +155,11 @@ export default defineComponent({ isPublicLocation: useActiveLocation(isLocationPublicActive, 'files-public-files'), isSpacesProjectsLocation: useActiveLocation(isLocationSpacesActive, 'files-spaces-projects'), isSpacesProjectLocation: useActiveLocation(isLocationSpacesActive, 'files-spaces-project'), + isSpacesShareLocation: useActiveLocation(isLocationSpacesActive, 'files-spaces-share'), ...useAppDefaults({ applicationName: 'files' - }) + }), + hasShareJail: useCapabilityShareJailEnabled() } }, data: () => ({ @@ -263,11 +267,27 @@ export default defineComponent({ let resource if (this.isPersonalLocation) { - path = buildWebDavFilesPath(this.user.id, path) + if (this.hasShareJail) { + const graphClient = clientService.graphAuthenticated( + this.configuration.server, + this.getToken + ) + const userResponse = await graphClient.users.getMe() + if (!userResponse.data) { + console.error('graph.user.getMe() has no data') + return + } + path = buildWebDavSpacesPath(userResponse.data.id, path || '') + } else { + path = buildWebDavFilesPath(this.user.id, path) + } resource = await this.$client.files.fileInfo(path, DavProperties.Default) } else if (this.isSpacesProjectLocation) { path = buildWebDavSpacesPath(this.$route.params.storageId, path) resource = await this.$client.files.fileInfo(path, DavProperties.Default) + } else if (this.isSpacesShareLocation) { + path = buildWebDavSpacesPath([SHARE_JAIL_ID, this.$route.query.shareId].join('!'), path) + resource = await this.$client.files.fileInfo(path, DavProperties.Default) } else { resource = await this.$client.publicFiles.getFileInfo( path, @@ -379,13 +399,30 @@ export default defineComponent({ let resource if (this.isPersonalLocation) { - path = buildWebDavFilesPath(this.user.id, path) + if (this.hasShareJail) { + const graphClient = clientService.graphAuthenticated( + this.configuration.server, + this.getToken + ) + const userResponse = await graphClient.users.getMe() + if (!userResponse.data) { + console.error('graph.user.getMe() has no data') + return + } + path = buildWebDavSpacesPath(userResponse.data.id, path || '') + } else { + path = buildWebDavFilesPath(this.user.id, path) + } await this.$client.files.createFolder(path) resource = await this.$client.files.fileInfo(path, DavProperties.Default) } else if (this.isSpacesProjectLocation) { path = buildWebDavSpacesPath(this.$route.params.storageId, path) await this.$client.files.createFolder(path) resource = await this.$client.files.fileInfo(path, DavProperties.Default) + } else if (this.isSpacesShareLocation) { + path = buildWebDavSpacesPath([SHARE_JAIL_ID, this.$route.query.shareId].join('!'), path) + await this.$client.files.createFolder(path) + resource = await this.$client.files.fileInfo(path, DavProperties.Default) } else { await this.$client.publicFiles.createFolder(path, null, this.publicLinkPassword) resource = await this.$client.publicFiles.getFileInfo( @@ -468,13 +505,30 @@ export default defineComponent({ let path = pathUtil.join(this.currentPath, fileName) if (this.isPersonalLocation) { - path = buildWebDavFilesPath(this.user.id, path) + if (this.hasShareJail) { + const graphClient = clientService.graphAuthenticated( + this.configuration.server, + this.getToken + ) + const userResponse = await graphClient.users.getMe() + if (!userResponse.data) { + console.error('graph.user.getMe() has no data') + return + } + path = buildWebDavSpacesPath(userResponse.data.id, path || '') + } else { + path = buildWebDavFilesPath(this.user.id, path) + } await this.$client.files.putFileContents(path, '') resource = await this.$client.files.fileInfo(path, DavProperties.Default) } else if (this.isSpacesProjectLocation) { path = buildWebDavSpacesPath(this.$route.params.storageId, path) await this.$client.files.putFileContents(path, '') resource = await this.$client.files.fileInfo(path, DavProperties.Default) + } else if (this.isSpacesShareLocation) { + path = buildWebDavSpacesPath([SHARE_JAIL_ID, this.$route.query.shareId].join('!'), path) + await this.$client.files.putFileContents(path, '') + resource = await this.$client.files.fileInfo(path, DavProperties.Default) } else { await this.$client.publicFiles.putFileContents('', path, this.publicLinkPassword, '') resource = await this.$client.publicFiles.getFileInfo( @@ -542,11 +596,27 @@ export default defineComponent({ let resource let path = pathUtil.join(this.currentPath, fileName) if (this.isPersonalLocation) { - path = buildWebDavFilesPath(this.user.id, path) + if (this.hasShareJail) { + const graphClient = clientService.graphAuthenticated( + this.configuration.server, + this.getToken + ) + const userResponse = await graphClient.users.getMe() + if (!userResponse.data) { + console.error('graph.user.getMe() has no data') + return + } + path = buildWebDavSpacesPath(userResponse.data.id, path || '') + } else { + path = buildWebDavFilesPath(this.user.id, path) + } resource = await this.$client.files.fileInfo(path, DavProperties.Default) } else if (this.isSpacesProjectLocation) { path = buildWebDavSpacesPath(this.$route.params.storageId, path) resource = await this.$client.files.fileInfo(path, DavProperties.Default) + } else if (this.isSpacesShareLocation) { + path = buildWebDavSpacesPath([SHARE_JAIL_ID, this.$route.query.shareId].join('!'), path) + resource = await this.$client.files.fileInfo(path, DavProperties.Default) } else { resource = await this.$client.publicFiles.getFileInfo( path, diff --git a/packages/web-app-files/src/components/FilesList/ResourceTable.vue b/packages/web-app-files/src/components/FilesList/ResourceTable.vue index cca3ceed7e5..053b5d4ad74 100644 --- a/packages/web-app-files/src/components/FilesList/ResourceTable.vue +++ b/packages/web-app-files/src/components/FilesList/ResourceTable.vue @@ -171,13 +171,23 @@ import { EVENT_TROW_MOUNTED, EVENT_FILE_DROPPED } from '../../constants' import { SortDir } from '../../composables' import * as path from 'path' import { determineSortFields } from '../../helpers/ui/resourceTable' -import { useCapabilitySpacesEnabled } from 'web-pkg/src/composables' +import { + useCapabilityProjectSpacesEnabled, + useCapabilityShareJailEnabled +} from 'web-pkg/src/composables' import Rename from '../../mixins/actions/rename' import { defineComponent, PropType } from '@vue/composition-api' import { extractDomSelector, Resource } from '../../helpers/resource' import { ShareTypes } from '../../helpers/share' import { createLocationSpaces } from '../../router' +const mapResourceFields = (resource: Resource, mapping = {}) => { + return Object.keys(mapping).reduce((result, resourceKey) => { + result[mapping[resourceKey]] = resource[resourceKey] + return result + }, {}) +} + export default defineComponent({ mixins: [Rename], model: { @@ -251,6 +261,33 @@ export default defineComponent({ required: false, default: null }, + /** + * Maps resource values to route params. Use `{ resourceFieldName: 'routeParamName' }` as format. + * + * An example would be `{ id: 'fileId' }` to map the value of the `id` field of a resource + * to the `fileId` param of the target route. + * + * Defaults to `{ storageId: 'storageId' } to map the value of the `storageId` field of a resource + * to the `storageId` param of the target route. + */ + targetRouteParamMapping: { + type: Object, + required: false, + default: () => ({ storageId: 'storageId' }) + }, + /** + * Maps resource values to route query options. Use `{ resourceFieldName: 'routeQueryName' }` as format. + * + * An example would be `{ id: 'fileId' }` to map the value of the `id` field of a resource + * to the `fileId` query option of the target route. + * + * Defaults to an empty object because no query options are expected as default. + */ + targetRouteQueryMapping: { + type: Object, + required: false, + default: () => ({}) + }, /** * Asserts whether clicking on the resource name triggers any action */ @@ -342,7 +379,8 @@ export default defineComponent({ }, setup() { return { - hasSpaces: useCapabilitySpacesEnabled() + hasShareJail: useCapabilityShareJailEnabled(), + hasProjectSpaces: useCapabilityProjectSpacesEnabled() } }, data() { @@ -532,32 +570,40 @@ export default defineComponent({ this.openWithPanel('sharing-item') }, folderLink(file) { - return this.createFolderLink(file.path, file.storageId) + return this.createFolderLink(file.path, file, false) }, parentFolderLink(file) { - return this.createFolderLink(path.dirname(file.path), file.storageId) + return this.createFolderLink(path.dirname(file.path), file, true) }, - createFolderLink(path, storageId) { + createFolderLink(path, resource, parentFolder) { if (this.targetRoute === null) { return {} } - const matchingSpace = this.getMatchingSpace(storageId) + const params = { + item: path.replace(/^\//, '') || '/', + ...this.targetRoute.params, + ...mapResourceFields(resource, this.targetRouteParamMapping) + } + const query = { + ...this.targetRoute.query, + ...mapResourceFields(resource, this.targetRouteQueryMapping) + } - if (matchingSpace && matchingSpace?.driveType === 'project') { - return createLocationSpaces('files-spaces-project', { - params: { storageId, item: path.replace(/^\//, '') || undefined } - }) + if (this.hasProjectSpaces) { + const matchingSpace = this.getMatchingSpace(resource.storageId) + if (matchingSpace?.driveType === 'project') { + return createLocationSpaces('files-spaces-project', { + params, + query + }) + } } return { name: this.targetRoute.name, - query: this.targetRoute.query, - params: { - item: path.replace(/^\//, '') || undefined, - ...this.targetRoute.params, - ...(storageId && { storageId }) - } + params, + query } }, fileDragged(file) { @@ -705,13 +751,15 @@ export default defineComponent({ return this.spaces.find((space) => space.id === storageId) }, getDefaultParentFolderName(resource) { - if (!this.hasSpaces) { - return this.$gettext('All files and folders') + if (this.hasProjectSpaces) { + const matchingSpace = this.getMatchingSpace(resource.storageId) + if (matchingSpace?.driveType === 'project') { + return matchingSpace.name + } } - const matchingSpace = this.getMatchingSpace(resource.storageId) - if (matchingSpace && matchingSpace?.driveType === 'project') { - return matchingSpace.name + if (!this.hasShareJail) { + return this.$gettext('All files and folders') } return this.$gettext('Personal') diff --git a/packages/web-app-files/src/components/Search/Preview.vue b/packages/web-app-files/src/components/Search/Preview.vue index 6e61f578744..aacf44a3917 100644 --- a/packages/web-app-files/src/components/Search/Preview.vue +++ b/packages/web-app-files/src/components/Search/Preview.vue @@ -22,7 +22,7 @@ import Vue from 'vue' import { mapGetters, mapState } from 'vuex' import { createLocationSpaces } from '../../router' import path from 'path' -import { useCapabilitySpacesEnabled } from 'web-pkg/src/composables' +import { useCapabilityShareJailEnabled } from 'web-pkg/src/composables' const visibilityObserver = new VisibilityObserver() @@ -44,7 +44,7 @@ export default { }, setup() { return { - hasSpaces: useCapabilitySpacesEnabled(), + hasShareJail: useCapabilityShareJailEnabled(), resourceTargetLocation: createLocationSpaces('files-spaces-personal-home'), resourceTargetLocationSpace: createLocationSpaces('files-spaces-project') } @@ -62,7 +62,7 @@ export default { return this.spaces.find((space) => space.id === this.resource.storageId) }, defaultParentFolderName() { - if (!this.hasSpaces) { + if (!this.hasShareJail) { return this.$gettext('All files and folders') } diff --git a/packages/web-app-files/src/components/SideBar/SideBar.vue b/packages/web-app-files/src/components/SideBar/SideBar.vue index 896f5611aae..650443ec522 100644 --- a/packages/web-app-files/src/components/SideBar/SideBar.vue +++ b/packages/web-app-files/src/components/SideBar/SideBar.vue @@ -32,6 +32,7 @@ import { computed, defineComponent } from '@vue/composition-api' import FileInfo from './FileInfo.vue' import SpaceInfo from './SpaceInfo.vue' +import { useCapabilityShareJailEnabled } from 'web-pkg/src/composables' export default defineComponent({ components: { FileInfo, SpaceInfo, SideBar }, @@ -42,6 +43,12 @@ export default defineComponent({ } }, + setup() { + return { + hasShareJail: useCapabilityShareJailEnabled() + } + }, + data() { return { focused: undefined, @@ -108,6 +115,9 @@ export default defineComponent({ // root path `/` like for personal home doesn't exist for public links return pathSegments.length === 1 } + if (isLocationSharesActive(this.$router, 'files-shares-with-me')) { + return !this.highlightedFile + } return !pathSegments.length }, highlightedFileThumbnail() { @@ -172,6 +182,9 @@ export default defineComponent({ this.highlightedFile.webDavPath, DavProperties.Default ) + if (this.hasShareJail && isLocationSharesActive(this.$router, 'files-shares-with-me')) { + item.name = this.highlightedFile.name + } } this.selectedFile = buildResource(item) diff --git a/packages/web-app-files/src/composables/upload/useUploadHelpers.ts b/packages/web-app-files/src/composables/upload/useUploadHelpers.ts index 5a248e7802b..2ceed2a16d7 100644 --- a/packages/web-app-files/src/composables/upload/useUploadHelpers.ts +++ b/packages/web-app-files/src/composables/upload/useUploadHelpers.ts @@ -9,6 +9,7 @@ import { useClientService, useRoute, useStore } from 'web-pkg/src/composables' import { useActiveLocation } from '../router' import { isLocationPublicActive, isLocationSpacesActive } from '../../router' import { computed, Ref, unref } from '@vue/composition-api' +import { SHARE_JAIL_ID } from '../../services/folder' interface UploadHelpersResult { inputFilesToUppyFiles(inputFileOptions): UppyResource[] @@ -30,6 +31,7 @@ export function useUploadHelpers(): UploadHelpersResult { const publicLinkPassword = computed((): string => store.getters['Files/publicLinkPassword']) const isPublicLocation = useActiveLocation(isLocationPublicActive, 'files-public-files') const isSpacesProjectLocation = useActiveLocation(isLocationSpacesActive, 'files-spaces-project') + const isSpacesShareLocation = useActiveLocation(isLocationSpacesActive, 'files-spaces-share') const clientService = useClientService() const user = computed((): User => store.getters.user) @@ -43,7 +45,7 @@ export function useUploadHelpers(): UploadHelpersResult { }) const uploadPath = computed((): string => { - const { params } = unref(route) + const { params, query } = unref(route) const { owncloudSdk: client } = clientService if (unref(isPublicLocation)) { @@ -55,6 +57,14 @@ export function useUploadHelpers(): UploadHelpersResult { return client.files.getFileUrl(path) } + if (unref(isSpacesShareLocation)) { + const path = buildWebDavSpacesPath( + [SHARE_JAIL_ID, query.shareId].join('!'), + unref(currentPath) + ) + return client.files.getFileUrl(path) + } + return client.files.getFileUrl(buildWebDavFilesPath(unref(user).id, unref(currentPath))) }) @@ -121,7 +131,7 @@ const inputFilesToUppyFiles = ({ route, uploadPath, currentPath, user }: inputFi return (files: File[]): UppyResource[] => { const uppyFiles: UppyResource[] = [] - const { params } = unref(route) + const { params, query } = unref(route) const currentFolder = unref(currentPath) for (const file of files) { @@ -149,9 +159,15 @@ const inputFilesToUppyFiles = ({ route, uploadPath, currentPath, user }: inputFi } const storageId = params.storageId - const webDavBasePath = storageId - ? buildWebDavSpacesPath(storageId, currentFolder) - : buildWebDavFilesPath(unref(user)?.id, currentFolder) + const shareId = query?.shareId + let webDavBasePath + if (shareId) { + webDavBasePath = buildWebDavSpacesPath([SHARE_JAIL_ID, shareId].join('!'), currentFolder) + } else if (storageId) { + webDavBasePath = buildWebDavSpacesPath(storageId, currentFolder) + } else { + webDavBasePath = buildWebDavFilesPath(unref(user)?.id, currentFolder) + } uppyFiles.push({ source: 'file input', diff --git a/packages/web-app-files/src/helpers/breadcrumbs.ts b/packages/web-app-files/src/helpers/breadcrumbs.ts index 1bf901e2d97..5abd7898a53 100644 --- a/packages/web-app-files/src/helpers/breadcrumbs.ts +++ b/packages/web-app-files/src/helpers/breadcrumbs.ts @@ -1,18 +1,19 @@ import { bus } from 'web-pkg/src/instance' +import { Location } from 'vue-router' export interface BreadcrumbItem { text: string - to?: string + to?: Location allowContextActions?: boolean onClick?: () => void } export const breadcrumbsFromPath = ( - currentPath: string, + currentRoute: Location, resourcePath: string ): BreadcrumbItem[] => { const pathSplit = (p = '') => p.split('/').filter(Boolean) - const current = pathSplit(currentPath) + const current = pathSplit(currentRoute.path) const resource = pathSplit(resourcePath) return resource.map( @@ -20,7 +21,10 @@ export const breadcrumbsFromPath = ( ({ allowContextActions: true, text, - to: '/' + [...current].splice(0, current.length - resource.length + i + 1).join('/') + to: { + path: '/' + [...current].splice(0, current.length - resource.length + i + 1).join('/'), + query: currentRoute.query + } } as BreadcrumbItem) ) } diff --git a/packages/web-app-files/src/helpers/resources.ts b/packages/web-app-files/src/helpers/resources.ts index d0535b3b748..258eae26a2e 100644 --- a/packages/web-app-files/src/helpers/resources.ts +++ b/packages/web-app-files/src/helpers/resources.ts @@ -22,6 +22,7 @@ import { Resource } from './resource' import { User } from './user' +import { SHARE_JAIL_ID } from '../services/folder' export function renameResource(resource, newName, newPath) { let resourcePath = '/' + newPath + newName @@ -267,26 +268,26 @@ export function attachIndicators(resource, sharesTree) { * @param {Array} shares Shares to be transformed into unique resources * @param {Boolean} incomingShares Asserts whether the shares are incoming * @param {Boolean} allowSharePermission Asserts whether the reshare permission is available - * @param {String} server The url of the backend - * @param {String} token The access token of the authenticated user + * @param {Boolean} hasShareJail Asserts whether the share jail is available backend side */ export function aggregateResourceShares( shares, incomingShares = false, allowSharePermission, - server, - token + hasShareJail ): Resource[] { if (incomingShares) { shares = addSharedWithToShares(shares) return orderBy(shares, ['file_target', 'permissions'], ['asc', 'desc']).map((share) => - buildSharedResource(share, incomingShares, allowSharePermission) + buildSharedResource(share, incomingShares, allowSharePermission, hasShareJail) ) } shares.sort((a, b) => a.path.localeCompare(b.path)) const resources = addSharedWithToShares(shares) - return resources.map((share) => buildSharedResource(share, incomingShares, allowSharePermission)) + return resources.map((share) => + buildSharedResource(share, incomingShares, allowSharePermission, hasShareJail) + ) } function addSharedWithToShares(shares) { @@ -342,7 +343,12 @@ function addSharedWithToShares(shares) { return resources } -export function buildSharedResource(share, incomingShares = false, allowSharePermission): Resource { +export function buildSharedResource( + share, + incomingShares = false, + allowSharePermission = true, + hasShareJail = false +): Resource { const isFolder = share.item_type === 'folder' const resource: Resource = { id: share.id, @@ -373,8 +379,15 @@ export function buildSharedResource(share, incomingShares = false, allowSharePer resource.sharedWith = share.sharedWith || [] resource.status = parseInt(share.state) resource.name = path.basename(share.file_target) - resource.path = share.file_target - resource.webDavPath = buildWebDavFilesPath(share.share_with, share.file_target) + if (hasShareJail) { + // 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//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('!'), '/') + } else { + resource.path = share.file_target + resource.webDavPath = buildWebDavFilesPath(share.share_with, share.file_target) + } resource.canDownload = () => share.state === ShareStatus.accepted resource.canShare = () => SharePermissions.share.enabled(share.permissions) resource.canRename = () => SharePermissions.update.enabled(share.permissions) diff --git a/packages/web-app-files/src/helpers/share/triggerShareAction.js b/packages/web-app-files/src/helpers/share/triggerShareAction.js index 5e118732037..f33c44303fc 100644 --- a/packages/web-app-files/src/helpers/share/triggerShareAction.js +++ b/packages/web-app-files/src/helpers/share/triggerShareAction.js @@ -1,7 +1,7 @@ import { buildSharedResource } from '../resources' import { ShareStatus } from './status' -export async function triggerShareAction(resource, status, allowReSharing, $client) { +export async function triggerShareAction(resource, status, hasReSharing, hasShareJail, $client) { const method = _getRequestMethod(status) if (!method) { throw new Error('invalid new share status') @@ -24,7 +24,7 @@ export async function triggerShareAction(resource, status, allowReSharing, $clie response = await response.json() if (response.ocs.data.length > 0) { const share = response.ocs.data[0] - return buildSharedResource(share, true, allowReSharing) + return buildSharedResource(share, true, hasReSharing, hasShareJail) } } diff --git a/packages/web-app-files/src/index.js b/packages/web-app-files/src/index.js index 1409f9579af..e5ce2301e10 100644 --- a/packages/web-app-files/src/index.js +++ b/packages/web-app-files/src/index.js @@ -6,6 +6,7 @@ import PrivateLink from './views/PrivateLink.vue' import PublicFiles from './views/PublicFiles.vue' import PublicLink from './views/PublicLink.vue' import Personal from './views/Personal.vue' +import SharedResource from './views/shares/SharedResource.vue' import SharedWithMe from './views/shares/SharedWithMe.vue' import SharedWithOthers from './views/shares/SharedWithOthers.vue' import SharedViaLink from './views/shares/SharedViaLink.vue' @@ -65,7 +66,8 @@ const navItems = [ icon: 'share-forward', route: { path: `/${appInfo.id}/shares` - } + }, + activeFor: [{ path: `/${appInfo.id}/spaces/shares` }] }, { name: $gettext('Spaces'), @@ -75,7 +77,7 @@ const navItems = [ path: `/${appInfo.id}/spaces/projects` }, enabled(capabilities) { - return capabilities.spaces && capabilities.spaces.enabled === true + return capabilities.spaces && capabilities.spaces.projects === true } }, { @@ -103,6 +105,7 @@ export default { PublicFiles, PublicLink, SearchResults, + SharedResource, SharedViaLink, SharedWithMe, SharedWithOthers, diff --git a/packages/web-app-files/src/mixins/actions/acceptShare.js b/packages/web-app-files/src/mixins/actions/acceptShare.js index 5b7a085f245..35743e94f10 100644 --- a/packages/web-app-files/src/mixins/actions/acceptShare.js +++ b/packages/web-app-files/src/mixins/actions/acceptShare.js @@ -1,17 +1,19 @@ import { triggerShareAction } from '../../helpers/share/triggerShareAction' -import { mapActions, mapMutations } from 'vuex' +import { mapActions, mapGetters, mapMutations } from 'vuex' import PQueue from 'p-queue' import { ShareStatus } from '../../helpers/share' import { isLocationSharesActive } from '../../router' -import { useCapabilityFilesSharingResharing } from 'web-pkg/src/composables' +import get from 'lodash-es/get' export default { computed: { - setup() { - return { - hasResharing: useCapabilityFilesSharingResharing() - } + ...mapGetters(['capabilities']), + $_acceptShare_hasResharing() { + return get(this.capabilities, 'files_sharing.resharing', true) + }, + $_acceptShare_hasShareJail() { + return get(this.capabilities, 'spaces.share_jail', false) }, $_acceptShare_items() { return [ @@ -55,7 +57,8 @@ export default { const share = await triggerShareAction( resource, ShareStatus.accepted, - this.hasResharing, + this.$_acceptShare_hasResharing, + this.$_acceptShare_hasShareJail, this.$client ) if (share) { diff --git a/packages/web-app-files/src/mixins/actions/createQuicklink.js b/packages/web-app-files/src/mixins/actions/createQuicklink.js index 30e5cbefe0d..dc5ec6ca314 100644 --- a/packages/web-app-files/src/mixins/actions/createQuicklink.js +++ b/packages/web-app-files/src/mixins/actions/createQuicklink.js @@ -20,7 +20,6 @@ export default { if (resources[0].status !== ShareStatus.accepted) { return false } - // FIXME: also check via capabilities if resharing is enabled + resharing is allowed on the share } return canShare(resources[0], this.$store) }, diff --git a/packages/web-app-files/src/mixins/actions/declineShare.js b/packages/web-app-files/src/mixins/actions/declineShare.js index 152d3c91bf0..9ee96d3a9ac 100644 --- a/packages/web-app-files/src/mixins/actions/declineShare.js +++ b/packages/web-app-files/src/mixins/actions/declineShare.js @@ -1,15 +1,17 @@ import { triggerShareAction } from '../../helpers/share/triggerShareAction' import { isLocationSharesActive } from '../../router' -import { mapActions, mapMutations } from 'vuex' +import { mapActions, mapGetters, mapMutations } from 'vuex' import PQueue from 'p-queue' import { ShareStatus } from '../../helpers/share' -import { useCapabilityFilesSharingResharing } from 'web-pkg/src/composables' +import get from 'lodash-es/get' export default { - setup() { - return { - hasResharing: useCapabilityFilesSharingResharing() - } + ...mapGetters(['capabilities']), + $_declineShare_hasResharing() { + return get(this.capabilities, 'files_sharing.resharing', true) + }, + $_declineShare_hasShareJail() { + return get(this.capabilities, 'spaces.share_jail', false) }, computed: { $_declineShare_items() { @@ -54,7 +56,8 @@ export default { const share = await triggerShareAction( resource, ShareStatus.declined, - this.hasResharing, + this.$_declineShare_hasResharing, + this.$_declineShare_hasShareJail, this.$client ) if (share) { diff --git a/packages/web-app-files/src/mixins/actions/delete.js b/packages/web-app-files/src/mixins/actions/delete.js index 1810a04bb47..d5cc965f432 100644 --- a/packages/web-app-files/src/mixins/actions/delete.js +++ b/packages/web-app-files/src/mixins/actions/delete.js @@ -18,6 +18,7 @@ export default { if ( !isLocationSpacesActive(this.$router, 'files-spaces-personal-home') && !isLocationSpacesActive(this.$router, 'files-spaces-project') && + !isLocationSpacesActive(this.$router, 'files-spaces-share') && !isLocationPublicActive(this.$router, 'files-public-files') ) { return false diff --git a/packages/web-app-files/src/mixins/actions/downloadArchive.js b/packages/web-app-files/src/mixins/actions/downloadArchive.js index 1daa426b140..800c5719ff4 100644 --- a/packages/web-app-files/src/mixins/actions/downloadArchive.js +++ b/packages/web-app-files/src/mixins/actions/downloadArchive.js @@ -25,6 +25,7 @@ export default { this.$_isFilesAppActive && !isLocationSpacesActive(this.$router, 'files-spaces-personal-home') && !isLocationSpacesActive(this.$router, 'files-spaces-project') && + !isLocationSpacesActive(this.$router, 'files-spaces-share') && !isLocationPublicActive(this.$router, 'files-public-files') && !isLocationCommonActive(this.$router, 'files-common-favorites') ) { diff --git a/packages/web-app-files/src/mixins/actions/downloadFile.js b/packages/web-app-files/src/mixins/actions/downloadFile.js index 24925867dfe..a24438e2021 100644 --- a/packages/web-app-files/src/mixins/actions/downloadFile.js +++ b/packages/web-app-files/src/mixins/actions/downloadFile.js @@ -11,7 +11,7 @@ export default { $_downloadFile_items() { return [ { - name: 'delete-file', + name: 'download-file', icon: 'file-download', handler: this.$_downloadFile_trigger, label: () => { @@ -22,6 +22,7 @@ export default { this.$_isFilesAppActive && !isLocationSpacesActive(this.$router, 'files-spaces-personal-home') && !isLocationSpacesActive(this.$router, 'files-spaces-project') && + !isLocationSpacesActive(this.$router, 'files-spaces-share') && !isLocationPublicActive(this.$router, 'files-public-files') && !isLocationCommonActive(this.$router, 'files-common-favorites') ) { diff --git a/packages/web-app-files/src/mixins/actions/navigate.js b/packages/web-app-files/src/mixins/actions/navigate.js index e1634bd34e4..ebf1ad4e5da 100644 --- a/packages/web-app-files/src/mixins/actions/navigate.js +++ b/packages/web-app-files/src/mixins/actions/navigate.js @@ -1,4 +1,4 @@ -import { mapState } from 'vuex' +import { mapGetters, mapState } from 'vuex' import { isSameResource } from '../../helpers/resource' import { createLocationPublic, @@ -13,6 +13,7 @@ import merge from 'lodash-es/merge' export default { computed: { + ...mapGetters(['capabilities']), ...mapState('Files', ['currentFolder']), $_navigate_items() { return [ @@ -52,9 +53,15 @@ export default { canBeDefault: true, componentType: 'router-link', route: ({ resources }) => { + const shareId = this.getShareId(resources[0]) + const shareName = this.getShareName(resources[0]) return merge({}, this.routeName, { params: { - item: resources[0].path + item: resources[0].path, + ...(shareName && { shareName }) + }, + query: { + ...(shareId && { shareId }) } }) }, @@ -71,7 +78,40 @@ export default { return createLocationPublic('files-spaces-project') } + if ( + isLocationSpacesActive(this.$router, 'files-spaces-share') || + isLocationSharesActive(this.$router, 'files-shares-with-me') + ) { + return createLocationSpaces('files-spaces-share') + } + return createLocationSpaces('files-spaces-personal-home') } + }, + methods: { + getShareId(resource) { + if (this.$route.query?.shareId) { + return this.$route.query.shareId + } + if ( + this.capabilities?.spaces?.share_jail && + isLocationSharesActive(this.$router, 'files-shares-with-me') + ) { + return resource.id + } + return undefined + }, + getShareName(resource) { + if (this.$route.params?.shareName) { + return this.$route.params.shareName + } + if ( + this.capabilities?.spaces?.share_jail && + isLocationSharesActive(this.$router, 'files-shares-with-me') + ) { + return resource.name + } + return undefined + } } } diff --git a/packages/web-app-files/src/mixins/actions/rename.js b/packages/web-app-files/src/mixins/actions/rename.js index 5fcbf15b121..92e7c6fdbae 100644 --- a/packages/web-app-files/src/mixins/actions/rename.js +++ b/packages/web-app-files/src/mixins/actions/rename.js @@ -2,7 +2,7 @@ import { mapActions, mapGetters, mapState } from 'vuex' import { isSameResource, extractNameWithoutExtension } from '../../helpers/resource' import { getParentPaths } from '../../helpers/path' import { buildResource } from '../../helpers/resources' -import { isLocationTrashActive, isLocationSharesActive } from '../../router' +import { isLocationTrashActive, isLocationSharesActive, isLocationSpacesActive } from '../../router' export default { computed: { @@ -35,6 +35,15 @@ export default { if (resources.length !== 1) { return false } + // FIXME: once renaming shares in share_jail has been sorted out backend side we can enable renaming shares again + if ( + this.capabilities?.spaces?.share_jail === true && + (isLocationSharesActive(this.$router, 'files-shares-with-me') || + (isLocationSpacesActive(this.$router, 'files-spaces-share') && + resources[0].path === '/')) + ) { + return false + } const renameDisabled = resources.some((resource) => { return !resource.canRename() @@ -195,7 +204,8 @@ export default { this.$router.push({ params: { item: '/' + newPath + newName || '/' - } + }, + query: this.$route.query }) } }) diff --git a/packages/web-app-files/src/mixins/actions/showShares.js b/packages/web-app-files/src/mixins/actions/showShares.js index 19b58b51982..ce567ea888a 100644 --- a/packages/web-app-files/src/mixins/actions/showShares.js +++ b/packages/web-app-files/src/mixins/actions/showShares.js @@ -33,7 +33,6 @@ export default { if (resources[0].status !== ShareStatus.accepted) { return false } - // FIXME: also check via capabilities if resharing is enabled + resharing is allowed on the share } return canShare(resources[0], this.$store) }, diff --git a/packages/web-app-files/src/mixins/fileActions.js b/packages/web-app-files/src/mixins/fileActions.js index 4ee2db12d59..6daafa561ec 100644 --- a/packages/web-app-files/src/mixins/fileActions.js +++ b/packages/web-app-files/src/mixins/fileActions.js @@ -1,7 +1,7 @@ import get from 'lodash-es/get' import { mapGetters, mapActions, mapState } from 'vuex' -import { isLocationTrashActive } from '../router' +import { isLocationSharesActive, isLocationTrashActive } from '../router' import { routeToContextQuery } from 'web-pkg/src/composables/appDefaults' import AcceptShare from './actions/acceptShare' import Copy from './actions/copy' @@ -15,6 +15,7 @@ import Navigate from './actions/navigate' import Rename from './actions/rename' import Restore from './actions/restore' import kebabCase from 'lodash-es/kebabCase' +import { ShareStatus } from '../helpers/share' const actionsMixins = [ 'navigate', @@ -96,6 +97,13 @@ export default { return false } + if ( + isLocationSharesActive(this.$router, 'files-shares-with-me') && + resources[0].status !== ShareStatus.accepted + ) { + return false + } + if (resources[0].extension && editor.extension) { return resources[0].extension.toLowerCase() === editor.extension.toLowerCase() } diff --git a/packages/web-app-files/src/quickActions.js b/packages/web-app-files/src/quickActions.js index f7c7fb40c5e..2717bf96936 100644 --- a/packages/web-app-files/src/quickActions.js +++ b/packages/web-app-files/src/quickActions.js @@ -9,11 +9,14 @@ export async function openSpaceMembersPanel(ctx) { } export function canShare(item, store) { - return ( - store.state.user.capabilities.files_sharing && - store.state.user.capabilities.files_sharing.api_enabled && - item.canShare() - ) + const { capabilities } = store.state.user + if (!capabilities.files_sharing || !capabilities.files_sharing.api_enabled) { + return false + } + if (item.isReceivedShare() && !capabilities.files_sharing.resharing) { + return false + } + return item.canShare() } export default { diff --git a/packages/web-app-files/src/router/router.ts b/packages/web-app-files/src/router/router.ts index 06b93cde9b9..0b878a12bc2 100644 --- a/packages/web-app-files/src/router/router.ts +++ b/packages/web-app-files/src/router/router.ts @@ -16,6 +16,7 @@ export interface RouteComponents { Personal: ComponentOptions SearchResults: ComponentOptions PublicLink: ComponentOptions + SharedResource: ComponentOptions SharedWithMe: ComponentOptions SharedWithOthers: ComponentOptions SharedViaLink: ComponentOptions diff --git a/packages/web-app-files/src/router/spaces.ts b/packages/web-app-files/src/router/spaces.ts index 481c3b91cc2..ea038667c93 100644 --- a/packages/web-app-files/src/router/spaces.ts +++ b/packages/web-app-files/src/router/spaces.ts @@ -2,7 +2,11 @@ import { Location, RouteConfig } from 'vue-router' import { RouteComponents } from './router' import { createLocation, isLocationActiveDirector, $gettext } from './utils' -type spaceTypes = 'files-spaces-personal-home' | 'files-spaces-project' | 'files-spaces-projects' +type spaceTypes = + | 'files-spaces-personal-home' + | 'files-spaces-project' + | 'files-spaces-projects' + | 'files-spaces-share' export const createLocationSpaces = (name: spaceTypes, location = {}): Location => createLocation( @@ -18,11 +22,13 @@ export const createLocationSpaces = (name: spaceTypes, location = {}): Location export const locationSpacesProject = createLocationSpaces('files-spaces-project') export const locationSpacesProjects = createLocationSpaces('files-spaces-projects') export const locationSpacesPersonalHome = createLocationSpaces('files-spaces-personal-home') +export const locationSpacesShare = createLocationSpaces('files-spaces-share') export const isLocationSpacesActive = isLocationActiveDirector( locationSpacesProject, locationSpacesProjects, - locationSpacesPersonalHome + locationSpacesPersonalHome, + locationSpacesShare ) export const buildRoutes = (components: RouteComponents): RouteConfig[] => [ @@ -53,8 +59,19 @@ export const buildRoutes = (components: RouteComponents): RouteConfig[] => [ name: locationSpacesPersonalHome.name, component: components.Personal, meta: { - title: $gettext('Personal'), - patchCleanPath: true + patchCleanPath: true, + title: $gettext('Personal') + } + }, + { + // FIXME: this is cheating. We rely on shares having a drive alias of `shares/` and hardcode it here until we have dynamic routes with drive aliases. + path: 'shares/:shareName?/:item*', + name: locationSpacesShare.name, + component: components.SharedResource, + meta: { + patchCleanPath: true, + title: $gettext('Files shared with me'), + contextQueryItems: ['shareId'] } } ] diff --git a/packages/web-app-files/src/services/folder.ts b/packages/web-app-files/src/services/folder.ts index 1b85e0cedc3..516d6ad7ccb 100644 --- a/packages/web-app-files/src/services/folder.ts +++ b/packages/web-app-files/src/services/folder.ts @@ -1,22 +1,26 @@ import Router from 'vue-router' import { useTask } from 'vue-concurrency' -import { useRouter, useClientService } from 'web-pkg/src/composables' +import { useRouter, useClientService, useStore } from 'web-pkg/src/composables' import { unref } from '@vue/composition-api' -import { useStore } from '../../../web-pkg/src/composables' import { Store } from 'vuex' import { ClientService } from 'web-pkg/src/services/client' import { + FolderLoaderSpacesProject, + FolderLoaderSpacesShare, FolderLoaderFavorites, - FolderLoaderPersonal, - FolderLoaderProject, + FolderLoaderLegacyPersonal, FolderLoaderPublicFiles, FolderLoaderSharedViaLink, FolderLoaderSharedWithMe, FolderLoaderSharedWithOthers, - FolderLoaderTrashbin + FolderLoaderTrashbin, + FolderLoaderSpacesPersonal } from './folder/' +export * from './folder/util' +export { SHARE_JAIL_ID } from './folder/spaces/loaderShare' + export type FolderLoaderTask = any export type TaskContext = { @@ -26,18 +30,24 @@ export type TaskContext = { } export interface FolderLoader { - isEnabled(router: Router): boolean + isEnabled(store: Store): boolean + isActive(router: Router): boolean getTask(options: TaskContext): FolderLoaderTask } export class FolderService { - private loaders: FolderLoader[] + private readonly loaders: FolderLoader[] constructor() { this.loaders = [ + // legacy loaders + new FolderLoaderLegacyPersonal(), + // spaces loaders + new FolderLoaderSpacesPersonal(), + new FolderLoaderSpacesProject(), + new FolderLoaderSpacesShare(), + // generic loaders new FolderLoaderFavorites(), - new FolderLoaderPersonal(), - new FolderLoaderProject(), new FolderLoaderPublicFiles(), new FolderLoaderSharedViaLink(), new FolderLoaderSharedWithMe(), @@ -53,16 +63,21 @@ export class FolderService { const loaders = this.loaders return useTask(function* (...args) { - const loader = loaders.find((l) => l.isEnabled(unref(router))) + const loader = loaders.find((l) => l.isEnabled(unref(store)) && l.isActive(unref(router))) if (!loader) { - throw new Error('No folder loader found for route') + console.error('No folder loader found for route') + return } const context = { clientService, store, router } - yield loader.getTask(context).perform(...args) + try { + yield loader.getTask(context).perform(...args) + } catch (e) { + console.error(e) + } }) } } diff --git a/packages/web-app-files/src/services/folder/index.ts b/packages/web-app-files/src/services/folder/index.ts index 49d88fe7f48..8e3109723ec 100644 --- a/packages/web-app-files/src/services/folder/index.ts +++ b/packages/web-app-files/src/services/folder/index.ts @@ -1,8 +1,11 @@ +export * from './legacy/loaderPersonal' +export * from './spaces/loaderPersonal' +export * from './spaces/loaderProject' +export * from './spaces/loaderShare' export * from './loaderFavorites' -export * from './loaderPersonal' -export * from './loaderProject' export * from './loaderPublicFiles' export * from './loaderSharedViaLink' export * from './loaderSharedWithMe' export * from './loaderSharedWithOthers' export * from './loaderTrashbin' +export * from './util' diff --git a/packages/web-app-files/src/services/folder/loaderPersonal.ts b/packages/web-app-files/src/services/folder/legacy/loaderPersonal.ts similarity index 63% rename from packages/web-app-files/src/services/folder/loaderPersonal.ts rename to packages/web-app-files/src/services/folder/legacy/loaderPersonal.ts index 437c461d5c9..fdb43bb8dd0 100644 --- a/packages/web-app-files/src/services/folder/loaderPersonal.ts +++ b/packages/web-app-files/src/services/folder/legacy/loaderPersonal.ts @@ -1,20 +1,19 @@ -import { FolderLoader, FolderLoaderTask, TaskContext } from '../folder' +import { FolderLoader, FolderLoaderTask, TaskContext } from '../../folder' import Router from 'vue-router' import { useTask } from 'vue-concurrency' import { DavProperties } from 'web-pkg/src/constants' -import { buildResource, buildWebDavFilesPath } from '../../helpers/resources' -import { isLocationSpacesActive } from '../../router' +import { buildResource, buildWebDavFilesPath } from '../../../helpers/resources' +import { isLocationSpacesActive } from '../../../router' +import { Store } from 'vuex' +import { fetchResources } from '../util' +import get from 'lodash-es/get' -export const fetchResources = async (client, path, properties) => { - try { - return await client.files.list(path, 1, properties) - } catch (error) { - console.error(error) +export class FolderLoaderLegacyPersonal implements FolderLoader { + public isEnabled(store: Store): boolean { + return !get(store, 'getters.capabilities.spaces.share_jail', false) } -} -export class FolderLoaderPersonal implements FolderLoader { - public isEnabled(router: Router): boolean { + public isActive(router: Router): boolean { return isLocationSpacesActive(router, 'files-spaces-personal-home') } @@ -30,7 +29,7 @@ export class FolderLoaderPersonal implements FolderLoader { store.commit('Files/CLEAR_CURRENT_FILES_LIST') let resources = yield fetchResources( - ref.$client, + client, buildWebDavFilesPath(ref.user.id, path || router.currentRoute.params.item || ''), DavProperties.Default ) @@ -43,16 +42,17 @@ export class FolderLoaderPersonal implements FolderLoader { files: resources }) - store.dispatch('Files/loadIndicators', { - client: client, - currentFolder: currentFolder.path - }) + // load indicators + ;(() => { + store.dispatch('Files/loadIndicators', { + client: client, + currentFolder: currentFolder.path + }) + })() - // Load quota - const promiseUser = client.users.getUser(ref.user.id) - // The semicolon is important to separate from the previous statement + // fetch user quota ;(async () => { - const user = await promiseUser + const user = await client.users.getUser(ref.user.id) store.commit('SET_QUOTA', user.quota) })() } catch (error) { diff --git a/packages/web-app-files/src/services/folder/loaderFavorites.ts b/packages/web-app-files/src/services/folder/loaderFavorites.ts index f210927f21f..2f567869811 100644 --- a/packages/web-app-files/src/services/folder/loaderFavorites.ts +++ b/packages/web-app-files/src/services/folder/loaderFavorites.ts @@ -4,9 +4,15 @@ import { useTask } from 'vue-concurrency' import { DavProperties } from 'web-pkg/src/constants' import { buildResource } from '../../helpers/resources' import { isLocationCommonActive } from '../../router' +import { Store } from 'vuex' +import get from 'lodash-es/get' export class FolderLoaderFavorites implements FolderLoader { - public isEnabled(router: Router): boolean { + public isEnabled(store: Store): boolean { + return get(store, 'getters.capabilities.files.favorites', true) + } + + public isActive(router: Router): boolean { return isLocationCommonActive(router, 'files-common-favorites') } diff --git a/packages/web-app-files/src/services/folder/loaderPublicFiles.ts b/packages/web-app-files/src/services/folder/loaderPublicFiles.ts index d83b8f4b713..b8ab9baaa52 100644 --- a/packages/web-app-files/src/services/folder/loaderPublicFiles.ts +++ b/packages/web-app-files/src/services/folder/loaderPublicFiles.ts @@ -14,7 +14,12 @@ import omit from 'lodash-es/omit' import { Store } from 'vuex' export class FolderLoaderPublicFiles implements FolderLoader { - public isEnabled(router: Router): boolean { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public isEnabled(store: Store): boolean { + return true + } + + public isActive(router: Router): boolean { return isLocationPublicActive(router, 'files-public-files') } diff --git a/packages/web-app-files/src/services/folder/loaderSharedViaLink.ts b/packages/web-app-files/src/services/folder/loaderSharedViaLink.ts index 893ef00199d..b4a2f18e80d 100644 --- a/packages/web-app-files/src/services/folder/loaderSharedViaLink.ts +++ b/packages/web-app-files/src/services/folder/loaderSharedViaLink.ts @@ -4,11 +4,20 @@ import { useTask } from 'vue-concurrency' import { isLocationSharesActive } from '../../router' import { ShareTypes } from '../../helpers/share' import { aggregateResourceShares } from '../../helpers/resources' -import { useCapabilityFilesSharingResharing } from 'web-pkg/src/composables' +import { Store } from 'vuex' +import { + useCapabilityFilesSharingResharing, + useCapabilityShareJailEnabled +} from 'web-pkg/src/composables' import { unref } from '@vue/composition-api' export class FolderLoaderSharedViaLink implements FolderLoader { - public isEnabled(router: Router): boolean { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public isEnabled(store: Store): boolean { + return true + } + + public isActive(router: Router): boolean { return isLocationSharesActive(router, 'files-shares-via-link') } @@ -19,6 +28,7 @@ export class FolderLoaderSharedViaLink implements FolderLoader { } = context const hasResharing = useCapabilityFilesSharingResharing(store) + const hasShareJail = useCapabilityShareJailEnabled(store) // eslint-disable-next-line @typescript-eslint/no-unused-vars return useTask(function* (signal1, signal2) { @@ -32,15 +42,11 @@ export class FolderLoaderSharedViaLink implements FolderLoader { resources = resources.map((r) => r.shareInfo) if (resources.length) { - const configuration = store.getters.configuration - const getToken = store.getters.getToken - resources = aggregateResourceShares( resources, false, unref(hasResharing), - configuration.server, - getToken + unref(hasShareJail) ) } diff --git a/packages/web-app-files/src/services/folder/loaderSharedWithMe.ts b/packages/web-app-files/src/services/folder/loaderSharedWithMe.ts index 42d051169fc..87905809696 100644 --- a/packages/web-app-files/src/services/folder/loaderSharedWithMe.ts +++ b/packages/web-app-files/src/services/folder/loaderSharedWithMe.ts @@ -3,27 +3,34 @@ import Router from 'vue-router' import { useTask } from 'vue-concurrency' import { aggregateResourceShares } from '../../helpers/resources' import { isLocationSharesActive } from '../../router' -import { useCapabilityFilesSharingResharing } from 'web-pkg/src/composables' +import { Store } from 'vuex' +import { + useCapabilityFilesSharingResharing, + useCapabilityShareJailEnabled +} from 'web-pkg/src/composables' import { unref } from '@vue/composition-api' export class FolderLoaderSharedWithMe implements FolderLoader { - public isEnabled(router: Router): boolean { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public isEnabled(store: Store): boolean { + return true + } + + public isActive(router: Router): boolean { return isLocationSharesActive(router, 'files-shares-with-me') } public getTask(context: TaskContext): FolderLoaderTask { - const { - store, - clientService: { owncloudSdk: client } - } = context + const { store, clientService } = context const hasResharing = useCapabilityFilesSharingResharing(store) + const hasShareJail = useCapabilityShareJailEnabled(store) // eslint-disable-next-line @typescript-eslint/no-unused-vars return useTask(function* (signal1, signal2) { store.commit('Files/CLEAR_CURRENT_FILES_LIST') - let resources = yield client.shares.getShares('', { + let resources = yield clientService.owncloudSdk.shares.getShares('', { state: 'all', include_tags: false, shared_with_me: true @@ -32,15 +39,11 @@ export class FolderLoaderSharedWithMe implements FolderLoader { resources = resources.map((r) => r.shareInfo) if (resources.length) { - const configuration = store.getters.configuration - const getToken = store.getters.getToken - resources = aggregateResourceShares( resources, true, unref(hasResharing), - configuration.server, - getToken + unref(hasShareJail) ) } diff --git a/packages/web-app-files/src/services/folder/loaderSharedWithOthers.ts b/packages/web-app-files/src/services/folder/loaderSharedWithOthers.ts index 7267a2aaee4..ba68ea8ccc3 100644 --- a/packages/web-app-files/src/services/folder/loaderSharedWithOthers.ts +++ b/packages/web-app-files/src/services/folder/loaderSharedWithOthers.ts @@ -3,12 +3,21 @@ import Router from 'vue-router' import { useTask } from 'vue-concurrency' import { isLocationSharesActive } from '../../router' import { aggregateResourceShares } from '../../helpers/resources' +import { Store } from 'vuex' import { ShareTypes } from '../../helpers/share' -import { useCapabilityFilesSharingResharing } from 'web-pkg/src/composables' +import { + useCapabilityFilesSharingResharing, + useCapabilityShareJailEnabled +} from 'web-pkg/src/composables' import { unref } from '@vue/composition-api' export class FolderLoaderSharedWithOthers implements FolderLoader { - public isEnabled(router: Router): boolean { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public isEnabled(store: Store): boolean { + return true + } + + public isActive(router: Router): boolean { return isLocationSharesActive(router, 'files-shares-with-others') } @@ -19,6 +28,7 @@ export class FolderLoaderSharedWithOthers implements FolderLoader { } = context const hasResharing = useCapabilityFilesSharingResharing(store) + const hasShareJail = useCapabilityShareJailEnabled(store) // eslint-disable-next-line @typescript-eslint/no-unused-vars return useTask(function* (signal1, signal2) { @@ -37,15 +47,11 @@ export class FolderLoaderSharedWithOthers implements FolderLoader { resources = resources.map((r) => r.shareInfo) if (resources.length) { - const configuration = store.getters.configuration - const getToken = store.getters.getToken - resources = aggregateResourceShares( resources, false, unref(hasResharing), - configuration.server, - getToken + unref(hasShareJail) ) } diff --git a/packages/web-app-files/src/services/folder/loaderTrashbin.ts b/packages/web-app-files/src/services/folder/loaderTrashbin.ts index 1a0ca1001c1..2bcac86be6b 100644 --- a/packages/web-app-files/src/services/folder/loaderTrashbin.ts +++ b/packages/web-app-files/src/services/folder/loaderTrashbin.ts @@ -9,9 +9,15 @@ import { buildWebDavFilesTrashPath, buildWebDavSpacesTrashPath } from '../../helpers/resources' +import { Store } from 'vuex' export class FolderLoaderTrashbin implements FolderLoader { - public isEnabled(router: Router): boolean { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public isEnabled(store: Store): boolean { + return true + } + + public isActive(router: Router): boolean { return ( isLocationTrashActive(router, 'files-trash-personal') || isLocationTrashActive(router, 'files-trash-spaces-project') diff --git a/packages/web-app-files/src/services/folder/spaces/loaderPersonal.ts b/packages/web-app-files/src/services/folder/spaces/loaderPersonal.ts new file mode 100644 index 00000000000..32fde450654 --- /dev/null +++ b/packages/web-app-files/src/services/folder/spaces/loaderPersonal.ts @@ -0,0 +1,78 @@ +import { FolderLoader, FolderLoaderTask, TaskContext } from '../../folder' +import Router from 'vue-router' +import { useTask } from 'vue-concurrency' +import { DavProperties } from 'web-pkg/src/constants' +import { buildResource, buildWebDavSpacesPath } from '../../../helpers/resources' +import { isLocationSpacesActive } from '../../../router' +import { Store } from 'vuex' +import { fetchResources } from '../util' +import get from 'lodash-es/get' + +export class FolderLoaderSpacesPersonal implements FolderLoader { + public isEnabled(store: Store): boolean { + return get(store, 'getters.capabilities.spaces.share_jail', false) + } + + public isActive(router: Router): boolean { + return isLocationSpacesActive(router, 'files-spaces-personal-home') + } + + public getTask(context: TaskContext): FolderLoaderTask { + const { store, router, clientService } = context + + const graphClient = clientService.graphAuthenticated( + store.getters.configuration.server, + store.getters.getToken + ) + + return useTask(function* (signal1, signal2, ref, sameRoute, path = null) { + try { + store.commit('Files/CLEAR_CURRENT_FILES_LIST') + + const userResponse = yield graphClient.users.getMe() + if (!userResponse.data) { + throw new Error('graph.user.getMe() has no data') + } + + let resources = yield fetchResources( + clientService.owncloudSdk, + buildWebDavSpacesPath( + userResponse.data.id, + path || router.currentRoute.params.item || '' + ), + DavProperties.Default + ) + resources = resources.map(buildResource) + + const currentFolder = resources.shift() + + store.commit('Files/LOAD_FILES', { + currentFolder, + files: resources + }) + + // load indicators + ;(() => { + store.dispatch('Files/loadIndicators', { + client: clientService.owncloudSdk, + currentFolder: currentFolder.path + }) + })() + + // fetch user quota + ;(async () => { + const user = await clientService.owncloudSdk.users.getUser(ref.user.id) + store.commit('SET_QUOTA', user.quota) + })() + } catch (error) { + store.commit('Files/SET_CURRENT_FOLDER', null) + console.error(error) + } + + ref.refreshFileListHeaderPosition() + + ref.accessibleBreadcrumb_focusAndAnnounceBreadcrumb(sameRoute) + ref.scrollToResourceFromRoute() + }).restartable() + } +} diff --git a/packages/web-app-files/src/services/folder/loaderProject.ts b/packages/web-app-files/src/services/folder/spaces/loaderProject.ts similarity index 83% rename from packages/web-app-files/src/services/folder/loaderProject.ts rename to packages/web-app-files/src/services/folder/spaces/loaderProject.ts index 8dd33ec2d52..1792c2e7f81 100644 --- a/packages/web-app-files/src/services/folder/loaderProject.ts +++ b/packages/web-app-files/src/services/folder/spaces/loaderProject.ts @@ -1,13 +1,19 @@ -import { FolderLoader, FolderLoaderTask, TaskContext } from '../folder' +import { FolderLoader, FolderLoaderTask, TaskContext } from '../../folder' import Router from 'vue-router' import { useTask } from 'vue-concurrency' -import { isLocationSpacesActive } from '../../router' +import { isLocationSpacesActive } from '../../../router' import { clientService } from 'web-pkg/src/services' -import { buildResource, buildSpace, buildWebDavSpacesPath } from '../../helpers/resources' +import { buildResource, buildSpace, buildWebDavSpacesPath } from '../../../helpers/resources' import { DavProperties } from 'web-pkg/src/constants' +import { Store } from 'vuex' +import get from 'lodash-es/get' -export class FolderLoaderProject implements FolderLoader { - public isEnabled(router: Router): boolean { +export class FolderLoaderSpacesProject implements FolderLoader { + public isEnabled(store: Store): boolean { + return get(store, 'getters.capabilities.spaces.projects', false) + } + + public isActive(router: Router): boolean { return isLocationSpacesActive(router, 'files-spaces-project') } diff --git a/packages/web-app-files/src/services/folder/spaces/loaderShare.ts b/packages/web-app-files/src/services/folder/spaces/loaderShare.ts new file mode 100644 index 00000000000..5acf92a6a6b --- /dev/null +++ b/packages/web-app-files/src/services/folder/spaces/loaderShare.ts @@ -0,0 +1,42 @@ +import { FolderLoader, FolderLoaderTask, TaskContext } from '../../folder' +import Router from 'vue-router' +import { useTask } from 'vue-concurrency' +import { isLocationSpacesActive } from '../../../router' +import { buildResource, buildWebDavSpacesPath } from '../../../helpers/resources' +import { Store } from 'vuex' +import get from 'lodash-es/get' + +export const SHARE_JAIL_ID = 'a0ca6a90-a365-4782-871e-d44447bbc668' + +export class FolderLoaderSpacesShare implements FolderLoader { + public isEnabled(store: Store): boolean { + return get(store, 'getters.capabilities.spaces.share_jail', false) + } + + public isActive(router: Router): boolean { + return isLocationSpacesActive(router, 'files-spaces-share') + } + + public getTask(context: TaskContext): FolderLoaderTask { + const { store, router, clientService } = context + + return useTask(function* (signal1, signal2, ref, shareId, path = null) { + store.commit('Files/CLEAR_CURRENT_FILES_LIST') + + const webDavResponse = yield clientService.owncloudSdk.files.list( + buildWebDavSpacesPath( + [SHARE_JAIL_ID, shareId].join('!'), + path || router.currentRoute.params.item || '' + ) + ) + + const resources = webDavResponse.map(buildResource) + const currentFolder = resources.shift() + + store.commit('Files/LOAD_FILES', { + currentFolder, + files: resources + }) + }) + } +} diff --git a/packages/web-app-files/src/services/folder/util.ts b/packages/web-app-files/src/services/folder/util.ts new file mode 100644 index 00000000000..18b93dffd59 --- /dev/null +++ b/packages/web-app-files/src/services/folder/util.ts @@ -0,0 +1,7 @@ +export const fetchResources = async (client, path, properties) => { + try { + return await client.files.list(path, 1, properties) + } catch (error) { + console.error(error) + } +} diff --git a/packages/web-app-files/src/views/LocationPicker.vue b/packages/web-app-files/src/views/LocationPicker.vue index eba32cba2de..2d626d488cc 100644 --- a/packages/web-app-files/src/views/LocationPicker.vue +++ b/packages/web-app-files/src/views/LocationPicker.vue @@ -98,7 +98,9 @@ import { DavProperties } from 'web-pkg/src/constants' import { createLocationPublic, createLocationSpaces } from '../router' import { buildWebDavFilesPath, buildWebDavSpacesPath } from '../helpers/resources' import { useResourcesViewDefaults } from '../composables' -import { useCapabilitySpacesEnabled } from 'web-pkg/src/composables' +import { useCapabilityShareJailEnabled } from 'web-pkg/src/composables' +import { unref } from '@vue/composition-api' +import { clientService } from 'web-pkg/src/services' export default { metaInfo() { @@ -118,6 +120,7 @@ export default { mixins: [MixinsGeneral, MixinFilesListFilter], setup() { + const hasShareJail = useCapabilityShareJailEnabled() const loadResourcesTask = useTask(function* (signal, ref, target) { ref.CLEAR_CURRENT_FILES_LIST() @@ -128,7 +131,7 @@ export default { const { context } = ref.$route.params let resources - + let webDavPath switch (context) { case 'public': resources = yield ref.$client.publicFiles.list( @@ -145,11 +148,21 @@ export default { ) break default: - resources = yield ref.$client.files.list( - buildWebDavFilesPath(ref.user.id, target), - 1, - DavProperties.Default - ) + if (unref(hasShareJail)) { + const graphClient = clientService.graphAuthenticated( + ref.$store.getters.configuration.server, + ref.$store.getters.getToken + ) + const userResponse = yield graphClient.users.getMe() + if (!userResponse.data) { + throw new Error('graph.user.getMe() has no data') + } + + webDavPath = buildWebDavSpacesPath(userResponse.data.id, target) + } else { + webDavPath = buildWebDavFilesPath(ref.user.id, target) + } + resources = yield ref.$client.files.list(webDavPath, 1, DavProperties.Default) } ref.loadFiles({ currentFolder: resources[0], files: resources.slice(1) }) @@ -162,7 +175,7 @@ export default { return { ...useResourcesViewDefaults({ loadResourcesTask }), - hasSpaces: useCapabilitySpacesEnabled() + hasShareJail } }, @@ -182,7 +195,7 @@ export default { ...mapState(['user']), title() { - const personalRouteTitle = this.hasSpaces + const personalRouteTitle = this.hasShareJail ? this.$gettext('Personal') : this.$gettext('All files') const target = @@ -235,7 +248,7 @@ export default { breadcrumbs.push(this.createBreadcrumbNode(i + 1, pathSegments[i], itemPath)) } } else { - const personalRouteTitle = this.hasSpaces + const personalRouteTitle = this.hasShareJail ? this.$gettext('Personal') : this.$gettext('All files') breadcrumbs.push(this.createBreadcrumbNode(0, personalRouteTitle, '/')) diff --git a/packages/web-app-files/src/views/Personal.vue b/packages/web-app-files/src/views/Personal.vue index f5dcbeff136..43540e26ed4 100644 --- a/packages/web-app-files/src/views/Personal.vue +++ b/packages/web-app-files/src/views/Personal.vue @@ -100,10 +100,10 @@ import { basename, join } from 'path' import PQueue from 'p-queue' import { createLocationSpaces } from '../router' import { useResourcesViewDefaults } from '../composables' -import { fetchResources } from '../services/folder/loaderPersonal' +import { fetchResources } from '../services/folder' import { defineComponent } from '@vue/composition-api' import { Resource } from '../helpers/resource' -import { useCapabilitySpacesEnabled } from 'web-pkg/src/composables' +import { useCapabilityShareJailEnabled } from 'web-pkg/src/composables' const visibilityObserver = new VisibilityObserver() @@ -132,7 +132,7 @@ export default defineComponent({ return { ...useResourcesViewDefaults(), resourceTargetLocation: createLocationSpaces('files-spaces-personal-home'), - hasSpaces: useCapabilitySpacesEnabled() + hasShareJail: useCapabilityShareJailEnabled() } }, @@ -153,12 +153,12 @@ export default defineComponent({ }, breadcrumbs() { - const personalRouteName = this.hasSpaces + const personalRouteName = this.hasShareJail ? this.$gettext('Personal') : this.$gettext('All files') return concatBreadcrumbs( - { text: personalRouteName, to: '/' }, - ...breadcrumbsFromPath(this.$route.path, this.$route.params.item) + { text: personalRouteName, to: { path: '/' } }, + ...breadcrumbsFromPath(this.$route, this.$route.params.item) ) }, diff --git a/packages/web-app-files/src/views/PublicFiles.vue b/packages/web-app-files/src/views/PublicFiles.vue index 900bdda4593..b758118e8ae 100644 --- a/packages/web-app-files/src/views/PublicFiles.vue +++ b/packages/web-app-files/src/views/PublicFiles.vue @@ -123,7 +123,7 @@ export default defineComponent({ ...mapState('Files/sidebar', { sidebarClosed: 'closed' }), breadcrumbs() { - const breadcrumbs = breadcrumbsFromPath(this.$route.path, this.$route.params.item) + const breadcrumbs = breadcrumbsFromPath(this.$route, this.$route.params.item) const rootRoute = breadcrumbs.shift() return concatBreadcrumbs( diff --git a/packages/web-app-files/src/views/Trashbin.vue b/packages/web-app-files/src/views/Trashbin.vue index 42a1ac8c39f..67f8a002e7d 100644 --- a/packages/web-app-files/src/views/Trashbin.vue +++ b/packages/web-app-files/src/views/Trashbin.vue @@ -4,7 +4,7 @@ diff --git a/packages/web-app-files/src/views/shares/SharedWithMe.vue b/packages/web-app-files/src/views/shares/SharedWithMe.vue index d9a7eb573ea..bc3d871d32d 100644 --- a/packages/web-app-files/src/views/shares/SharedWithMe.vue +++ b/packages/web-app-files/src/views/shares/SharedWithMe.vue @@ -113,6 +113,8 @@ :resources="sharesItems" :are-resources-clickable="showsAcceptedShares" :target-route="resourceTargetLocation" + :target-route-param-mapping="resourceTargetParamMapping" + :target-route-query-mapping="resourceTargetQueryMapping" :header-position="fileListHeaderY" :sort-by="sharesSortBy" :sort-dir="sharesSortDir" @@ -173,7 +175,7 @@ import MixinMountSideBar from '../../mixins/sidebar/mountSideBar' import { VisibilityObserver } from 'web-pkg/src/observer' import { ImageDimension, ImageType } from '../../constants' import { useSort, useResourcesViewDefaults } from '../../composables' -import { useRouteQuery } from 'web-pkg/src/composables' +import { useCapabilityShareJailEnabled, useRouteQuery } from 'web-pkg/src/composables' import debounce from 'lodash-es/debounce' import AppLoadingSpinner from 'web-pkg/src/components/AppLoadingSpinner.vue' @@ -214,6 +216,19 @@ export default defineComponent({ any[] >() + const hasShareJail = useCapabilityShareJailEnabled() + const resourceTargetLocation = computed(() => + createLocationSpaces( + unref(hasShareJail) ? 'files-spaces-share' : 'files-spaces-personal-home' + ) + ) + const resourceTargetParamMapping = computed(() => + unref(hasShareJail) ? { name: 'shareName', path: 'item' } : undefined + ) + const resourceTargetQueryMapping = computed(() => + unref(hasShareJail) ? { id: 'shareId' } : undefined + ) + const viewMode = computed(() => parseInt(String(unref(useRouteQuery('view-mode', ShareStatus.accepted.toString())))) ) @@ -267,7 +282,9 @@ export default defineComponent({ sharesItems, displayedFields, - resourceTargetLocation: createLocationSpaces('files-spaces-personal-home') + resourceTargetLocation, + resourceTargetParamMapping, + resourceTargetQueryMapping } }, diff --git a/packages/web-app-files/src/views/spaces/Project.vue b/packages/web-app-files/src/views/spaces/Project.vue index 6b1841e001c..46dc0326011 100644 --- a/packages/web-app-files/src/views/spaces/Project.vue +++ b/packages/web-app-files/src/views/spaces/Project.vue @@ -199,13 +199,13 @@ export default defineComponent({ return concatBreadcrumbs( { text: this.$gettext('Spaces'), - to: '/files/spaces/projects' + to: { path: '/files/spaces/projects' } }, { text: this.space?.name, - to: `/files/spaces/projects/${this.$route.params.storageId}` + to: { path: `/files/spaces/projects/${this.$route.params.storageId}` } }, - ...breadcrumbsFromPath(this.$route.path, this.$route.params.item) + ...breadcrumbsFromPath(this.$route, this.$route.params.item) ) }, diff --git a/packages/web-app-files/tests/unit/components/Search/Preview.spec.js b/packages/web-app-files/tests/unit/components/Search/Preview.spec.js index f64322a31fa..150b8b35c75 100644 --- a/packages/web-app-files/tests/unit/components/Search/Preview.spec.js +++ b/packages/web-app-files/tests/unit/components/Search/Preview.spec.js @@ -45,7 +45,7 @@ describe('Preview component', () => { it('should equal "All files and folders" if spaces capability is not present', () => { const wrapper = getWrapper({ resourceTargetLocation: null, - hasSpaces: false + hasShareJail: false }) expect(wrapper.vm.defaultParentFolderName).toEqual('All files and folders') }) @@ -84,7 +84,7 @@ function getWrapper({ params: {} }, spaces = [], - hasSpaces = true + hasShareJail = true } = {}) { return shallowMount(Preview, { localVue, @@ -118,7 +118,7 @@ function getWrapper({ setup: () => { return { resourceTargetLocation, - hasSpaces + hasShareJail } } }) diff --git a/packages/web-app-files/tests/unit/helpers/breadcrumbs.spec.js b/packages/web-app-files/tests/unit/helpers/breadcrumbs.spec.js index ee08e7f73e8..e1e9285a2de 100644 --- a/packages/web-app-files/tests/unit/helpers/breadcrumbs.spec.js +++ b/packages/web-app-files/tests/unit/helpers/breadcrumbs.spec.js @@ -2,15 +2,15 @@ import { breadcrumbsFromPath, concatBreadcrumbs } from '../../../src/helpers/bre describe('builds an array of breadcrumbitems', () => { it('from a path', () => { - const breadCrumbs = breadcrumbsFromPath('/files/spaces/personal/home/test', '/test') + const breadCrumbs = breadcrumbsFromPath({ path: '/files/spaces/personal/home/test' }, '/test') expect(breadCrumbs).toEqual([ - { allowContextActions: true, text: 'test', to: '/files/spaces/personal/home/test' } + { allowContextActions: true, text: 'test', to: { path: '/files/spaces/personal/home/test' } } ]) }) it('from an array of breadcrumbitems', () => { const initialBreadCrumbs = [{ text: 'Foo' }, { text: 'Bar' }] - const breadCrumbsFromPath = breadcrumbsFromPath('/app/foo/bar?all=500', '/bar') + const breadCrumbsFromPath = breadcrumbsFromPath({ path: '/app/foo/bar?all=500' }, '/bar') const result = concatBreadcrumbs(...initialBreadCrumbs, breadCrumbsFromPath) expect(result[0]).toMatchObject({ text: 'Foo' }) expect(result[1]).toMatchObject({ text: 'Bar' }) diff --git a/packages/web-app-files/tests/unit/helpers/share/triggerShareAction.spec.js b/packages/web-app-files/tests/unit/helpers/share/triggerShareAction.spec.js index f07e882d21e..c6f84c6e76e 100644 --- a/packages/web-app-files/tests/unit/helpers/share/triggerShareAction.spec.js +++ b/packages/web-app-files/tests/unit/helpers/share/triggerShareAction.spec.js @@ -17,7 +17,7 @@ describe('method triggerShareAction', () => { it('throws error if invalid share status given', async () => { const statusText = 'invalid new share status' - await expect(triggerShareAction(null, ShareStatus.pending, true, null)).rejects.toThrow( + await expect(triggerShareAction(null, ShareStatus.pending, true, false, null)).rejects.toThrow( statusText ) }) @@ -26,7 +26,7 @@ describe('method triggerShareAction', () => { const statusText = 'status is not 200' fetch.mockResponse(new Error(), { status: 404, statusText }) await expect( - triggerShareAction({ share: { id: 1 } }, ShareStatus.accepted, true, $client) + triggerShareAction({ share: { id: 1 } }, ShareStatus.accepted, true, false, $client) ).rejects.toThrow(statusText) }) @@ -35,7 +35,7 @@ describe('method triggerShareAction', () => { headers: { 'content-length': 0 } }) await expect( - triggerShareAction({ share: { id: 1 } }, ShareStatus.accepted, true, $client) + triggerShareAction({ share: { id: 1 } }, ShareStatus.accepted, true, false, $client) ).resolves.toBeNull() }) @@ -44,7 +44,7 @@ describe('method triggerShareAction', () => { headers: { 'content-length': 1 } }) await expect( - triggerShareAction({ share: { id: 1 } }, ShareStatus.accepted, true, $client) + triggerShareAction({ share: { id: 1 } }, ShareStatus.accepted, true, false, $client) ).resolves.toMatchObject({ id: 1 }) }) }) diff --git a/packages/web-app-files/tests/unit/views/__snapshots__/PublicFiles.spec.ts.snap b/packages/web-app-files/tests/unit/views/__snapshots__/PublicFiles.spec.ts.snap index 36651884f2e..ac80694fad1 100644 --- a/packages/web-app-files/tests/unit/views/__snapshots__/PublicFiles.spec.ts.snap +++ b/packages/web-app-files/tests/unit/views/__snapshots__/PublicFiles.spec.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PublicFiles view when the view is not loading anymore when length of the paginated resources is greater than zero should load the resource table with the correct props 1`] = ``; +exports[`PublicFiles view when the view is not loading anymore when length of the paginated resources is greater than zero should load the resource table with the correct props 1`] = ``; diff --git a/packages/web-app-files/tests/unit/views/spaces/Project.spec.js b/packages/web-app-files/tests/unit/views/spaces/Project.spec.js index 65bbccd837c..b91666aee11 100644 --- a/packages/web-app-files/tests/unit/views/spaces/Project.spec.js +++ b/packages/web-app-files/tests/unit/views/spaces/Project.spec.js @@ -264,6 +264,11 @@ function getMountedWrapper(spaceResources = [], spaceItem = null, imageContent = } } }), + capabilities: () => ({ + spaces: { + projects: true + } + }), user: () => ({ id: 'marie' }), diff --git a/packages/web-app-files/tests/unit/views/views.setup.js b/packages/web-app-files/tests/unit/views/views.setup.js index f35909d421a..892d9cbd66c 100644 --- a/packages/web-app-files/tests/unit/views/views.setup.js +++ b/packages/web-app-files/tests/unit/views/views.setup.js @@ -73,6 +73,10 @@ export const routes = [ path: '/files/spaces/project/', name: 'files-spaces-project' }, + { + path: '/files/spaces/share/', + name: 'files-spaces-share' + }, { path: '/files/public/files/', name: 'files-public-files' diff --git a/packages/web-pkg/src/composables/capability/useCapability.ts b/packages/web-pkg/src/composables/capability/useCapability.ts index 56cd451b8dd..ff786fab3d6 100644 --- a/packages/web-pkg/src/composables/capability/useCapability.ts +++ b/packages/web-pkg/src/composables/capability/useCapability.ts @@ -30,6 +30,11 @@ export const useCapabilityFilesSharingResharing = createCapabilityComposable( ) export const useCapabilitySpacesEnabled = createCapabilityComposable('spaces.enabled', false) +export const useCapabilityProjectSpacesEnabled = createCapabilityComposable( + 'spaces.projects', + false +) +export const useCapabilityShareJailEnabled = createCapabilityComposable('spaces.share_jail', false) export const useCapabilityFilesTusSupportHttpMethodOverride = createCapabilityComposable( 'files.tus_support.http_method_override', false diff --git a/packages/web-pkg/src/composables/router/index.ts b/packages/web-pkg/src/composables/router/index.ts index 16c63aa370d..f6c1176ac92 100644 --- a/packages/web-pkg/src/composables/router/index.ts +++ b/packages/web-pkg/src/composables/router/index.ts @@ -2,6 +2,7 @@ export * from './types' export * from './useActiveApp' export * from './useRoute' export * from './useRouteName' +export * from './useRouteParam' export * from './useRouteQuery' export * from './useRouteQueryPersisted' export * from './useRouter' diff --git a/packages/web-pkg/src/composables/router/useRouteParam.ts b/packages/web-pkg/src/composables/router/useRouteParam.ts new file mode 100644 index 00000000000..21a5f8a2875 --- /dev/null +++ b/packages/web-pkg/src/composables/router/useRouteParam.ts @@ -0,0 +1,30 @@ +import { customRef, Ref } from '@vue/composition-api' +import { useRouter } from './useRouter' +import { ParamValue } from './types' + +export const useRouteParam = (name: string, defaultValue?: ParamValue): Ref => { + const router = useRouter() + + return customRef((track, trigger) => { + router.afterEach((to, from) => to.params[name] !== from.params[name] && trigger()) + + return { + get() { + track() + return router.currentRoute.params[name] || defaultValue + }, + async set(v) { + if (router.currentRoute.params[name] === v) { + return + } + await router.replace({ + params: { + ...router.currentRoute.params, + [name]: v + } + }) + trigger() + } + } + }) +} diff --git a/packages/web-runtime/src/components/UploadInfo.vue b/packages/web-runtime/src/components/UploadInfo.vue index e4d730ecddd..7fb94fa2bb6 100644 --- a/packages/web-runtime/src/components/UploadInfo.vue +++ b/packages/web-runtime/src/components/UploadInfo.vue @@ -55,13 +55,13 @@ import '@uppy/core/dist/style.css' import '@uppy/status-bar/dist/style.css' import path from 'path' -import { useCapabilitySpacesEnabled } from 'web-pkg/src/composables' +import { useCapabilityShareJailEnabled } from 'web-pkg/src/composables' import { mapGetters } from 'vuex' export default { setup() { return { - hasSpaces: useCapabilitySpacesEnabled() + hasShareJail: useCapabilityShareJailEnabled() } }, data: () => ({ @@ -148,7 +148,7 @@ export default { if (file.targetRoute?.name === 'files-spaces-project') { return file.targetRoute.params.name } - return this.hasSpaces ? this.$gettext('Personal') : this.$gettext('All files and folders') + return this.hasShareJail ? this.$gettext('Personal') : this.$gettext('All files and folders') }, createFolderLink(path, storageId, targetRoute) { if (!targetRoute) { diff --git a/packages/web-runtime/src/layouts/Application.vue b/packages/web-runtime/src/layouts/Application.vue index 548844f9ad3..ab7ce98a5a4 100644 --- a/packages/web-runtime/src/layouts/Application.vue +++ b/packages/web-runtime/src/layouts/Application.vue @@ -100,14 +100,26 @@ export default defineComponent({ const { href: currentHref } = this.$router.resolve(this.$route) return items.map((item) => { - const { href: comparativeHref } = this.$router.resolve(item.route) - + const active = [item.route, ...(item.activeFor || [])] + .filter(Boolean) + .reduce((result, currentItem) => { + if (result) { + return true + } + try { + const comparativeHref = this.$router.resolve(currentItem).href + return currentHref.startsWith(comparativeHref) + } catch (e) { + console.error(e) + return false + } + }, false) const name = typeof item.name === 'function' ? item.name(this.capabilities) : item.name return { ...item, name: this.$gettext(name), - active: currentHref.startsWith(comparativeHref) + active } }) }, diff --git a/tests/acceptance/expected-failures-with-ocis-server-ocis-storage.md b/tests/acceptance/expected-failures-with-ocis-server-ocis-storage.md index 946962fe850..be5c51ae046 100644 --- a/tests/acceptance/expected-failures-with-ocis-server-ocis-storage.md +++ b/tests/acceptance/expected-failures-with-ocis-server-ocis-storage.md @@ -76,12 +76,13 @@ Other free text and markdown formatting can be used elsewhere in the document if - [webUISharingPermissionsUsers/sharePermissionsUsers.feature:196](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPermissionsUsers/sharePermissionsUsers.feature#L196) - [webUISharingPermissionsUsers/sharePermissionsUsers.feature:209](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPermissionsUsers/sharePermissionsUsers.feature#L209) - [webUISharingPermissionsUsers/sharePermissionsUsers.feature:223](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPermissionsUsers/sharePermissionsUsers.feature#L223) +- [webUIResharing2/reshareUsers.feature:41](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing2/reshareUsers.feature#L41) +- [webUIResharing2/reshareUsers.feature:69](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing2/reshareUsers.feature#L69) +- [webUIResharing2/reshareUsers.feature:70](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing2/reshareUsers.feature#L70) - [webUIResharing2/reshareUsers.feature:95](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing2/reshareUsers.feature#L95) - [webUIResharing2/reshareUsers.feature:96](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing2/reshareUsers.feature#L96) - [webUIResharing2/reshareUsers.feature:130](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing2/reshareUsers.feature#L130) - [webUIResharing2/reshareUsers.feature:131](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing2/reshareUsers.feature#L131) -- [webUIResharing2/reshareUsers.feature:69](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing2/reshareUsers.feature#L69) -- [webUIResharing2/reshareUsers.feature:70](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing2/reshareUsers.feature#L70) - [webUIResharing1/reshareUsers.feature:18](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing1/reshareUsers.feature#L18) - [webUIResharing1/reshareUsers.feature:46](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing1/reshareUsers.feature#L46) - [webUIResharing1/reshareUsers.feature:74](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIResharing1/reshareUsers.feature#L74) @@ -474,3 +475,11 @@ Other free text and markdown formatting can be used elsewhere in the document if - [webUIFilesDetails/fileDetails.feature:42](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIFilesDetails/fileDetails.feature#42) - [webUIFilesDetails/fileDetails.feature:57](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIFilesDetails/fileDetails.feature#57) - [webUIRenameFiles/renameFiles.feature:246](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIRenameFiles/renameFiles.feature#246) + +### [Copy/move not possible from and into shares in oCIS](https://github.com/owncloud/web/issues/6892) +- [webUIFilesCopy/copy.feature:89](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIFilesCopy/copy.feature#L89) +- [webUIFilesCopy/copy.feature:101](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIFilesCopy/copy.feature#L101) + +### [No share indicators inside share jail (needs concept / PM decision)](https://github.com/owncloud/web/issues/6894) +- [webUISharingInternalGroupsSharingIndicator/shareWithGroups.feature:42](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingInternalGroupsSharingIndicator/shareWithGroups.feature#L42) +- [webUISharingInternalUsersSharingIndicator/shareWithUsers.feature:80](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingInternalUsersSharingIndicator/shareWithUsers.feature#L80) diff --git a/tests/acceptance/features/webUIFilesActionMenu/versions.feature b/tests/acceptance/features/webUIFilesActionMenu/versions.feature index 0887a089830..c371fbcb640 100644 --- a/tests/acceptance/features/webUIFilesActionMenu/versions.feature +++ b/tests/acceptance/features/webUIFilesActionMenu/versions.feature @@ -73,7 +73,7 @@ Feature: Versions of a file And the user browses to display the "versions" details of file "lorem-file.txt" Then the versions list should contain 0 entries - @issue-ocis-2319 + @issue-ocis-2319 @skipOnOCIS Scenario: change the file content of a received shared file Given the setting "shareapi_auto_accept_share" of app "core" has been set to "no" in the server And the administrator has set the default folder for received shares to "Shares" in the server diff --git a/tests/acceptance/features/webUIFilesCopy/copy.feature b/tests/acceptance/features/webUIFilesCopy/copy.feature index 27766e4bbd5..fb335234dfb 100644 --- a/tests/acceptance/features/webUIFilesCopy/copy.feature +++ b/tests/acceptance/features/webUIFilesCopy/copy.feature @@ -85,7 +85,7 @@ Feature: copy files and folders And as "Alice" file "simple-folder/simple-empty-folder/data.zip" should exist in the server And as "Alice" file "simple-folder/data.zip" should exist in the server - + @issue-6892 Scenario: copy a file into another folder with no change permission Given user "Alice" has created file "lorem.txt" in the server And user "Brian" has been created with default attributes and without skeleton files in the server @@ -97,7 +97,7 @@ Feature: copy files and folders Then as "Alice" file "Shares/simple-folder/lorem.txt" should not exist in the server - @issue-ocis-1328 + @issue-6892 @issue-ocis-1328 Scenario: copy a folder into another folder with no change permission Given user "Alice" has created folder "simple-empty-folder" in the server And user "Brian" has been created with default attributes and without skeleton files in the server diff --git a/tests/acceptance/features/webUIOperationsWithFolderShares/accessToSharesFolder.feature b/tests/acceptance/features/webUIOperationsWithFolderShares/accessToSharesFolder.feature index 733c72fc12b..90436baec51 100644 --- a/tests/acceptance/features/webUIOperationsWithFolderShares/accessToSharesFolder.feature +++ b/tests/acceptance/features/webUIOperationsWithFolderShares/accessToSharesFolder.feature @@ -17,12 +17,12 @@ Feature: Upload into a folder Shares When user "Alice" logs in using the webUI Then folder "Shares" should not be listed on the webUI - @issue-ocis-2322 @notToImplementOnOC10 - Scenario: the Shares folder should not be listed even without any share - When user "Alice" logs in using the webUI - Then folder "Shares" should not be listed on the webUI + + + + @notToImplementOnOCIS Scenario: the Shares folder exists after accepting the first shared file Given user "Brian" has created file "lorem.txt" in the server And user "Brian" has shared file "lorem.txt" with user "Alice" with "all" permissions in the server @@ -30,23 +30,23 @@ Feature: Upload into a folder Shares When user "Alice" logs in using the webUI Then folder "Shares" should be listed on the webUI - @issue-ocis-2322 @notToImplementOnOC10 - Scenario: try to upload a file or a folder into a folder Shares with all permissions in oCIS - Given user "Brian" has created file "lorem.txt" in the server - And user "Brian" has shared file "lorem.txt" with user "Alice" with "all" permissions in the server - And user "Alice" has accepted the share "lorem.txt" offered by user "Brian" in the server - And user "Alice" has logged in using the webUI - When the user opens folder "Shares" using the webUI - Then it should not be possible to create files using the webUI - @issue-ocis-2322 @notToImplementOnOC10 - Scenario: try to upload a file or a folder into a folder Shares with read permissions in oCIS - Given user "Brian" has created file "lorem.txt" in the server - And user "Brian" has shared file "lorem.txt" with user "Alice" with "read" permissions in the server - And user "Alice" has accepted the share "lorem.txt" offered by user "Brian" in the server - And user "Alice" has logged in using the webUI - When the user opens folder "Shares" using the webUI - Then it should not be possible to create files using the webUI + + + + + + + + + + + + + + + + @issue-ocis-2322 @notToImplementOnOCIS Scenario: upload of a file into a folder Shares in oc10 @@ -90,15 +90,15 @@ Feature: Upload into a folder Shares When the user moves folder "NewFolder" into folder "Shares" using the webUI Then folder "NewFolder" should be listed on the webUI - @issue-ocis-2322 @notToImplementOnOC10 - Scenario: try to move a file or a folder into a folder Shares in oCIS - Given user "Brian" has created file "lorem.txt" in the server - And user "Brian" has shared file "lorem.txt" with user "Alice" with "read" permissions in the server - And user "Alice" has accepted the share "lorem.txt" offered by user "Brian" in the server - And user "Alice" has created folder "NewFolder" in the server - And user "Alice" has logged in using the webUI - When the user tries to move folder "NewFolder" into folder "Shares" using the webUI - Then the move here folder button should be disabled + + + + + + + + + @issue-ocis-2322 @notToImplementOnOCIS Scenario: the user can delete files that they wrote into the folder Shares diff --git a/tests/acceptance/features/webUIRenameFiles/renameFiles.feature b/tests/acceptance/features/webUIRenameFiles/renameFiles.feature index 86c70ede28c..800ad6c4f6f 100644 --- a/tests/acceptance/features/webUIRenameFiles/renameFiles.feature +++ b/tests/acceptance/features/webUIRenameFiles/renameFiles.feature @@ -105,8 +105,8 @@ Feature: rename files Then file "loremz.dat" should be listed on the webUI And file "loremy.tad" should be listed on the webUI - - # these are invalid file names on all implementations + # these are invalid file names on oc10 + @notToImplementOnOCIS Scenario Outline: Try to rename a file using forbidden characters When the user tries to rename file "data.zip" to "" using the webUI Then the error message with header 'Failed to rename "data.zip" to ""' should be displayed on the webUI @@ -116,14 +116,14 @@ Feature: rename files | filename | | lorem\txt | | \\.txt | + | .htaccess | + + + + + + - @notToImplementOnOCIS - # .htaccess is an invalid file name on oC10 - Scenario: Try to rename a file to .htaccess on oC10 - When the user tries to rename file "data.zip" to ".htaccess" using the webUI - Then the error message with header 'Failed to rename "data.zip" to ".htaccess"' should be displayed on the webUI - And file "data.zip" should be listed on the webUI - And file "" should not be listed on the webUI Scenario Outline: Rename a file/folder using forward slash in its name diff --git a/tests/acceptance/features/webUIRenameFolders/renameFolders.feature b/tests/acceptance/features/webUIRenameFolders/renameFolders.feature index dcf7541dfc6..6e544091dc5 100644 --- a/tests/acceptance/features/webUIRenameFolders/renameFolders.feature +++ b/tests/acceptance/features/webUIRenameFolders/renameFolders.feature @@ -96,7 +96,8 @@ Feature: rename folders | a normal folder | | another normal folder | - # these are invalid file names on all implementations + # these are invalid file names on oc10 + @notToImplementOnOCIS Scenario Outline: Rename a folder using forbidden characters When the user tries to rename folder to using the webUI Then the error message with header '' should be displayed on the webUI @@ -105,13 +106,12 @@ Feature: rename folders | from_name | to_name | alert_message | | "simple-folder" | "simple\folder" | Failed to rename "simple-folder" to "simple\folder" | | "simple-folder" | "\\simple-folder" | Failed to rename "simple-folder" to "\\simple-folder" | + | "simple-folder" | ".htaccess" | Failed to rename "simple-folder" to ".htaccess" | + + + + - @notToImplementOnOCIS - # .htaccess is an invalid folder name on oC10 - Scenario: Try to rename a folder to .htaccess on oC10 - When the user tries to rename folder "simple-folder" to ".htaccess" using the webUI - Then the error message with header 'Failed to rename "simple-folder" to ".htaccess"' should be displayed on the webUI - And folder "simple-folder" should be listed on the webUI Scenario: Rename a folder putting a name of a file which already exists diff --git a/tests/acceptance/features/webUIResharing1/reshareUsers.feature b/tests/acceptance/features/webUIResharing1/reshareUsers.feature index 54347494e4f..a9c9f8cdc41 100644 --- a/tests/acceptance/features/webUIResharing1/reshareUsers.feature +++ b/tests/acceptance/features/webUIResharing1/reshareUsers.feature @@ -86,24 +86,24 @@ Feature: Resharing shared files with different permissions | item_type | folder | | permissions | read, share | - @skipOnOC10 - #after fixing the issue delete this scenario and use the one above by deleting the @skipOnOCIS tag there - Scenario: share a folder with another user with share permissions and reshare without share permissions to different user, and check if user is displayed for the receiver ( ocis bug demonstration) - Given user "Brian" has shared folder "simple-folder" with user "Alice" with "read, share" permissions in the server - And user "Alice" has accepted the share "simple-folder" offered by user "Brian" in the server - And user "Alice" has logged in using the webUI - And the user opens folder "Shares" using the webUI - When the user shares folder "simple-folder" with user "Carol King" as "Viewer" with permissions "," using the webUI - And user "Carol" accepts the share "simple-folder" offered by user "Alice" using the sharing API in the server - And user "Carol" should have received a share with these details in the server: - | field | value | - | uid_owner | Alice | - | share_with | Carol | - | file_target | /Shares/simple-folder | - | item_type | folder | - | permissions | read | + + + + + + + + + + + + + + + + @skipOnOCIS Scenario: share a folder without share permissions and check if another user can reshare Given user "Brian" has shared folder "simple-folder" with user "Alice" with "read" permissions in the server And user "Alice" has accepted the share "Shares/simple-folder" offered by user "Brian" in the server @@ -111,7 +111,7 @@ Feature: Resharing shared files with different permissions And the user opens folder "Shares" using the webUI Then the user should not be able to share resource "simple-folder" using the webUI - + @skipOnOCIS Scenario: share a file without share permissions and check if another user can reshare Given user "Brian" has created file "lorem.txt" in the server And user "Brian" has shared file "lorem.txt" with user "Alice" with "read" permissions in the server @@ -120,7 +120,7 @@ Feature: Resharing shared files with different permissions And the user opens folder "Shares" using the webUI Then the user should not be able to share resource "lorem.txt" using the webUI - + @skipOnOCIS Scenario: share a received folder without share permissions and check if another user can reshare Given user "Brian" has shared folder "simple-folder" with user "Alice" with "all" permissions in the server And user "Alice" has accepted the share "Shares/simple-folder" offered by user "Brian" in the server @@ -130,7 +130,7 @@ Feature: Resharing shared files with different permissions And the user opens folder "Shares" using the webUI Then the user should not be able to share resource "simple-folder" using the webUI - + @skipOnOCIS Scenario: share a received file without share permissions and check if another user can reshare Given user "Brian" has created file "lorem.txt" in the server And user "Brian" has shared file "lorem.txt" with user "Alice" with "all" permissions in the server @@ -194,7 +194,7 @@ Feature: Resharing shared files with different permissions And as "Carol" folder "Shares/simple-folder" should not exist in the server And user "Carol" should not have received any shares in the server - + @skipOnOCIS Scenario: Reshare a file and folder from shared with me page Given user "Brian" has created file "lorem.txt" in the server And user "Brian" has shared folder "simple-folder" with user "Alice" in the server diff --git a/tests/acceptance/features/webUISharingAcceptShares/acceptShares.feature b/tests/acceptance/features/webUISharingAcceptShares/acceptShares.feature index b774d631273..3728beab33e 100644 --- a/tests/acceptance/features/webUISharingAcceptShares/acceptShares.feature +++ b/tests/acceptance/features/webUISharingAcceptShares/acceptShares.feature @@ -123,7 +123,7 @@ Feature: accept/decline shares coming from internal users Then file "toshare.txt" should not be listed on the webUI And file "anotherfile.txt" should not be listed on the webUI - @ocisSmokeTest + @ocisSmokeTest @skipOnOCIS Scenario: accept an offered (pending) share Given user "Alice" has created file "toshare.txt" in the server And user "Alice" has created file "anotherfile.txt" in the server @@ -141,7 +141,7 @@ Feature: accept/decline shares coming from internal users And file "anotherfile.txt" should not be listed on the webUI - + @skipOnOCIS Scenario: accept a previously declined share Given user "Alice" has created file "lorem.txt" in the server And user "Alice" has uploaded file "testavatar.jpg" to "testimage.jpg" in the server @@ -208,7 +208,7 @@ Feature: accept/decline shares coming from internal users And the user browses to the shared-with-me page in declined shares view Then file "lorem.txt" shared by "Alice Hansen" should be in "Declined" state on the webUI - @ocis-issue-714 @issue-5532 + @ocis-issue-714 @issue-5532 @skipOnOCIS Scenario: the deleted shared file is restored back to the personal file list when accepted from the shared with me file list Given user "Alice" has created file "lorem.txt" in the server And user "Alice" has shared file "lorem.txt" with user "Brian" in the server @@ -241,7 +241,7 @@ Feature: accept/decline shares coming from internal users And as "Brian" folder "from_Alice" should exist inside folder "/Shares/simple-folder" in the server And as "Brian" folder "from_Carol" should exist inside folder "/Shares/simple-folder (2)" in the server - @issue-ocis-1950 + @issue-ocis-1950 @skipOnOCIS Scenario: accept a share that you received as user and as group member Given these groups have been created in the server: | groupname | diff --git a/tests/acceptance/features/webUISharingInternalGroups/shareWithGroups.feature b/tests/acceptance/features/webUISharingInternalGroups/shareWithGroups.feature index a6d1ff5fb61..66fa9cb3824 100644 --- a/tests/acceptance/features/webUISharingInternalGroups/shareWithGroups.feature +++ b/tests/acceptance/features/webUISharingInternalGroups/shareWithGroups.feature @@ -96,7 +96,7 @@ Feature: Sharing files and folders with internal groups # check that the original file owner can still see the file And as "Carol" the content of "new-lorem.txt" in the server should be the same as the content of local file "new-lorem.txt" - @issue-ocis-1943 + @issue-ocis-1943 @skipOnOCIS Scenario: share a folder with an internal group and a member uploads, overwrites and deletes files Given user "Carol" has created folder "simple-folder" in the server And user "Carol" has created file "simple-folder/lorem.txt" in the server @@ -134,7 +134,7 @@ Feature: Sharing files and folders with internal groups And as "Carol" the content of "new-simple-folder/new-lorem.txt" in the server should be the same as the content of local file "new-lorem.txt" And file "data.zip" should not be listed on the webUI - @issue-4102 + @issue-4102 @skipOnOCIS Scenario: share a folder with an internal group and a member unshares the folder Given user "Carol" has created folder "simple-folder" in the server And user "Carol" has uploaded file with content "lorem content" to "simple-folder/lorem.txt" in the server diff --git a/tests/acceptance/features/webUISharingInternalGroupsEdgeCases/shareWithGroupsEdgeCases.feature b/tests/acceptance/features/webUISharingInternalGroupsEdgeCases/shareWithGroupsEdgeCases.feature index 55166145582..9ab3ec77b59 100644 --- a/tests/acceptance/features/webUISharingInternalGroupsEdgeCases/shareWithGroupsEdgeCases.feature +++ b/tests/acceptance/features/webUISharingInternalGroupsEdgeCases/shareWithGroupsEdgeCases.feature @@ -14,7 +14,7 @@ Feature: Sharing files and folders with internal groups | Carol | @issue-5216 - Scenario Outline: sharing files and folder with an internal problematic group name + Scenario Outline: sharing files and folder with an internal problematic group name Given these groups have been created in the server: | groupname | | | @@ -28,9 +28,9 @@ Feature: Sharing files and folders with internal groups And user "Alice" accepts the share "Shares/testimage.jpg" offered by user "Carol" using the sharing API in the server Then group "" should be listed as "Viewer" in the collaborators list for folder "simple-folder" on the webUI And group "" should be listed as "Viewer" in the collaborators list for file "testimage.jpg" on the webUI - When the user re-logs in as "Alice" using the webUI - And the user opens folder "Shares" using the webUI - Then folder "simple-folder" should be listed on the webUI + # When the user re-logs in as "Alice" using the webUI + # And the user opens folder "Shares" using the webUI + # Then folder "simple-folder" should be listed on the webUI # When the user opens the share dialog for file "simple-folder" using the webUI # Then user "Carol King" should be listed as "Owner" in the collaborators list on the webUI # And file "testimage.jpg" should be listed on the webUI diff --git a/tests/acceptance/features/webUISharingInternalGroupsSharingIndicator/shareWithGroups.feature b/tests/acceptance/features/webUISharingInternalGroupsSharingIndicator/shareWithGroups.feature index 33d208855bd..5be09989f67 100644 --- a/tests/acceptance/features/webUISharingInternalGroupsSharingIndicator/shareWithGroups.feature +++ b/tests/acceptance/features/webUISharingInternalGroupsSharingIndicator/shareWithGroups.feature @@ -38,7 +38,7 @@ Feature: Sharing files and folders with internal groups | fileName | expectedIndicators | | inside.txt | user-indirect | - @issue-2060 + @issue-2060 @issue-6894 Scenario: sharing indicator of items inside a re-shared folder Given user "Alice" has created folder "simple-folder" in the server And user "Alice" has created folder "simple-folder/simple-empty-folder" in the server diff --git a/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature b/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature index 5b78d0e3f7c..6bb08ba8005 100644 --- a/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature +++ b/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature @@ -73,7 +73,7 @@ Feature: Sharing files and folders with internal users # check that the original file owner can still see the file And as "Brian" the content of "new-lorem.txt" in the server should be the same as the content of local file "new-lorem.txt" - @disablePreviews + @disablePreviews @skipOnOCIS Scenario: share a folder with another internal user who uploads, overwrites and deletes files Given user "Brian" has created file "simple-folder/lorem.txt" in the server And user "Brian" has created file "simple-folder/data.zip" in the server @@ -104,7 +104,7 @@ Feature: Sharing files and folders with internal users And as "Brian" the content of "simple-folder/new-lorem.txt" in the server should be the same as the content of local file "new-lorem.txt" But file "data.zip" should not be listed on the webUI - @issue-product-270 @disablePreviews + @issue-product-270 @disablePreviews @skipOnOCIS Scenario: share a folder with another internal user who unshares the folder Given user "Brian" has uploaded file with content "text file" to "simple-folder/lorem.txt" in the server And user "Brian" has logged in using the webUI @@ -122,7 +122,7 @@ Feature: Sharing files and folders with internal users Then folder "new-simple-folder" should be listed on the webUI And the content of file "new-simple-folder/lorem.txt" for user "Brian" should be "text file" in the server - @issue-product-270 @disablePreviews + @issue-product-270 @disablePreviews @skipOnOCIS Scenario: share a folder with another internal user and prohibit deleting Given user "Brian" has created file "simple-folder/lorem.txt" in the server And user "Brian" has logged in using the webUI @@ -191,7 +191,7 @@ Feature: Sharing files and folders with internal users Then the shared-via path in the details dialog should be "/simple-folder" # Share permission is not available in oCIS webUI so when setting all permissions, it is displayed as "Custom permissions" there - @issue-2897 @issue-ocis-2260 @disablePreviews + @issue-2897 @issue-ocis-2260 @disablePreviews @skipOnOCIS Scenario: sharing details of items inside a re-shared folder ("via" info) Given user "Alice" has created folder "simple-folder" in the server And user "Alice" has created folder "simple-folder/simple-empty-folder" in the server @@ -276,43 +276,43 @@ Feature: Sharing files and folders with internal users | Editor | Editor | read,update,create,delete,share | read,update,share | | Custom permissions | Custom permissions | read | read | - @skipOnOC10 @ocisSmokeTest @disablePreviews - #after fixing the issue delete this scenario and use the one above by deleting the @skipOnOCIS tag there - Scenario Outline: Share files/folders with special characters in their name - Given user "Brian" has created folder "Sample,Folder,With,Comma" in the server - And user "Brian" has created file "sample,1.txt" in the server - And user "Brian" has logged in using the webUI - When the user shares folder "Sample,Folder,With,Comma" with user "Alice Hansen" as "" using the webUI - And user "Alice" accepts the share "Shares/Sample,Folder,With,Comma" offered by user "Brian" using the sharing API in the server - And the user shares file "sample,1.txt" with user "Alice Hansen" as "" using the webUI - And user "Alice" accepts the share "Shares/sample,1.txt" offered by user "Brian" using the sharing API in the server - Then user "Alice Hansen" should be listed as "" in the collaborators list for folder "Sample,Folder,With,Comma" on the webUI - And user "Alice Hansen" should be listed as "" in the collaborators list for file "sample,1.txt" on the webUI - And user "Alice" should have received a share with these details in the server: - | field | value | - | uid_owner | Brian | - | share_with | Alice | - | file_target | /Shares/Sample,Folder,With,Comma | - | item_type | folder | - | permissions | | - And user "Alice" should have received a share with these details in the server: - | field | value | - | uid_owner | Brian | - | share_with | Alice | - | file_target | /Shares/sample,1.txt | - | item_type | file | - | permissions | | - When the user re-logs in as "Alice" using the webUI - And the user opens folder "Shares" using the webUI - Then these files should be listed on the webUI - | files | - | Sample,Folder,With,Comma | - | sample,1.txt | - Examples: - | set-role | expected-role | permissions-folder | permissions-file | - | Viewer | Viewer | read | read | - | Editor | Editor | read,update,create,delete | read,update | - | Custom permissions | Viewer | read | read | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @issue-4192 @disablePreviews diff --git a/tests/acceptance/features/webUISharingInternalUsersSharingIndicator/shareWithUsers.feature b/tests/acceptance/features/webUISharingInternalUsersSharingIndicator/shareWithUsers.feature index d07e462dc8f..c152cf11497 100644 --- a/tests/acceptance/features/webUISharingInternalUsersSharingIndicator/shareWithUsers.feature +++ b/tests/acceptance/features/webUISharingInternalUsersSharingIndicator/shareWithUsers.feature @@ -76,7 +76,7 @@ Feature: Sharing files and folders with internal users | new-folder | user-indirect | | lorem.txt | user-indirect | - @issue-4167 + @issue-4167 @issue-6894 Scenario: sharing indicator of items inside a re-shared folder Given user "Carol" has been created with default attributes and without skeleton files in the server And user "Alice" has created folder "/simple-folder/simple-empty-folder" in the server diff --git a/tests/acceptance/features/webUISharingPermissionsUsers/sharePermissionsUsers.feature b/tests/acceptance/features/webUISharingPermissionsUsers/sharePermissionsUsers.feature index cbd49a465e9..20e3162283e 100644 --- a/tests/acceptance/features/webUISharingPermissionsUsers/sharePermissionsUsers.feature +++ b/tests/acceptance/features/webUISharingPermissionsUsers/sharePermissionsUsers.feature @@ -237,23 +237,3 @@ Feature: Sharing files and folders with internal users with different permission | file_target | /Shares/simple-empty-folder | | item_type | folder | | permissions | all | - - @skipOnOC10 @issue-ocis-1231 - #after fixing the issue delete this scenario and use the one above by deleting the @skipOnOCIS tag there - Scenario: User is allowed to update permissions of a reshared sub-folder within the permissions that the user has received (ocis bug demonstration) - Given user "Carol" has been created with default attributes and without skeleton files in the server - And user "Brian" has shared folder "simple-folder" with user "Alice" with "all" permissions in the server - And user "Alice" has accepted the share "Shares/simple-folder" offered by user "Brian" in the server - And user "Alice" has shared folder "/Shares/simple-folder" with user "Carol" with "share, delete" permissions in the server - And user "Alice" has logged in using the webUI - When the user opens folder "Shares" using the webUI - And the user opens folder "simple-folder" using the webUI - And the user shares folder "simple-empty-folder" with user "Carol King" as "Custom permissions" with permissions "delete, create, update" using the webUI - And user "Carol" accepts the share "simple-empty-folder" offered by user "Alice" using the sharing API in the server - Then user "Carol" should have received a share with these details in the server: - | field | value | - | uid_owner | Alice | - | share_with | Carol | - | file_target | /Shares/simple-empty-folder | - | item_type | folder | - | permissions | read, delete, create, update | diff --git a/tests/e2e/cucumber/features/integrations/share.feature b/tests/e2e/cucumber/features/integrations/share.oc10.feature similarity index 100% rename from tests/e2e/cucumber/features/integrations/share.feature rename to tests/e2e/cucumber/features/integrations/share.oc10.feature diff --git a/tests/e2e/cucumber/features/integrations/share.ocis.feature b/tests/e2e/cucumber/features/integrations/share.ocis.feature new file mode 100644 index 00000000000..b869d1030c7 --- /dev/null +++ b/tests/e2e/cucumber/features/integrations/share.ocis.feature @@ -0,0 +1,57 @@ +Feature: share + + Background: + And "Admin" disables share auto accepting + + Scenario: folder + Given "Admin" creates following users + | id | + | Alice | + | Brian | + When "Alice" logs in + And "Alice" opens the "files" app + And "Alice" navigates to the personal space page + And "Alice" creates the following resources + | resource | type | + | folder_to_shared | folder | + And "Alice" uploads the following resource + | resource | to | + | lorem.txt | folder_to_shared | + #Then "Alice" should see the following resource + # | folder_to_shared/lorem.txt | + When "Alice" shares the following resource using the sidebar panel + | resource | user | role | + | folder_to_shared | Brian | editor | + And "Brian" logs in + And "Brian" opens the "files" app + And "Brian" navigates to the shared with me page + And "Brian" accepts the following share + | name | + | folder_to_shared | + And "Brian" renames the following resource + | resource | as | + | folder_to_shared/lorem.txt | lorem_new.txt | + And "Brian" uploads the following resource + | resource | to | + | simple.pdf | folder_to_shared | + When "Alice" opens the "files" app + #Then "Alice" should see the following resources + # | folder_to_shared/lorem_new.txt | + # | folder_to_shared/simple.pdf | + And "Alice" uploads the following resource + | resource | to | create_version | + | PARENT/simple.pdf | folder_to_shared | true | + #Then "Alice" should see that the resource "folder_to_shared/simple.pdf" has 1 version + When "Brian" restores following resources + | resource | to | version | + | simple.pdf | folder_to_shared | 1 | + #Then "Brian" should see that the version of resource "simple.pdf" has been restored + When "Alice" deletes the following resources + | resource | + | folder_to_shared/lorem_new.txt | + | folder_to_shared | + And "Alice" logs out + And "Brian" opens the "files" app + #Then "Brian" should not see the following resource + # | Shares/folder_to_shared | + And "Brian" logs out diff --git a/tests/e2e/cucumber/features/integrations/spaces/project.ocis.feature b/tests/e2e/cucumber/features/integrations/spaces/project.ocis.feature index 810df266c6a..4d3e605a713 100644 --- a/tests/e2e/cucumber/features/integrations/spaces/project.ocis.feature +++ b/tests/e2e/cucumber/features/integrations/spaces/project.ocis.feature @@ -90,16 +90,12 @@ Feature: spaces.personal And "Brian" accepts the following share | name | | folder_to_shared | - And "Brian" navigates to the personal space page And "Brian" renames the following resource | resource | as | - | Shares/folder_to_shared/lorem.txt | lorem_new.txt | + | folder_to_shared/lorem.txt | lorem_new.txt | And "Brian" uploads the following resource | resource | to | - | simple.pdf | Shares/folder_to_shared | - And "Brian" copies the following resource - | resource | to | - | Shares/folder_to_shared | Personal | + | simple.pdf | folder_to_shared | And "Alice" navigates to the projects space page And "Alice" navigates to the project space "team.1" And "Alice" uploads the following resource @@ -107,7 +103,7 @@ Feature: spaces.personal | PARENT/simple.pdf | folder_to_shared | true | When "Brian" restores following resources | resource | to | version | - | simple.pdf | Shares/folder_to_shared | 1 | + | simple.pdf | folder_to_shared | 1 | When "Alice" deletes the following resources | resource | | folder_to_shared/lorem_new.txt | diff --git a/tests/e2e/cucumber/features/journeys/kindergarten.feature b/tests/e2e/cucumber/features/journeys/kindergarten.oc10.feature similarity index 100% rename from tests/e2e/cucumber/features/journeys/kindergarten.feature rename to tests/e2e/cucumber/features/journeys/kindergarten.oc10.feature diff --git a/tests/e2e/cucumber/features/journeys/kindergarten.ocis.feature b/tests/e2e/cucumber/features/journeys/kindergarten.ocis.feature new file mode 100644 index 00000000000..f88068a6940 --- /dev/null +++ b/tests/e2e/cucumber/features/journeys/kindergarten.ocis.feature @@ -0,0 +1,87 @@ +Feature: Kindergarten can use web to organize a day + + As a kindergarten operator named Alice + I want to manage all file related operations by using ownCloud WEB + So that i'm sure all parents are informed and have the latest information in a easy and secure way + + Background: + Given "Admin" creates following users + | id | + | Alice | + | Brian | + | Carol | + And "Admin" disables share auto accepting + + Scenario: Alice can share this weeks meal plan with all parents + When "Alice" logs in + And "Alice" opens the "files" app + And "Alice" navigates to the personal space page + And "Alice" creates the following resources + | resource | type | + | groups/Kindergarten Koalas/meal plan | folder | + | groups/Pre-Schools Pirates/meal plan | folder | + | groups/Teddy Bear Daycare/meal plan | folder | + And "Alice" uploads the following resources + | resource | to | + | PARENT/parent.txt | groups/Kindergarten Koalas/meal plan | + | lorem.txt | groups/Kindergarten Koalas/meal plan | + | lorem-big.txt | groups/Kindergarten Koalas/meal plan | + | data.zip | groups/Pre-Schools Pirates/meal plan | + | lorem.txt | groups/Pre-Schools Pirates/meal plan | + | lorem-big.txt | groups/Pre-Schools Pirates/meal plan | + | data.zip | groups/Teddy Bear Daycare/meal plan | + | lorem.txt | groups/Teddy Bear Daycare/meal plan | + | lorem-big.txt | groups/Teddy Bear Daycare/meal plan | + # Implementation of sharing with different roles is currently broken + # since we switched to bulk creating of shares with a single dropdown + And "Alice" shares the following resources using the sidebar panel + | resource | user | role | + | groups/Pre-Schools Pirates/meal plan | Brian | editor | + | groups/Pre-Schools Pirates/meal plan | Carol | viewer | + # Then what do we check for to be confident that the above things done by Alice have worked? + When "Brian" logs in + And "Brian" opens the "files" app + And "Brian" navigates to the shared with me page + And "Brian" accepts the following share + | name | + | meal plan | + And "Brian" downloads the following resources using the sidebar panel + | resource | from | + | data.zip | meal plan | + # Then what do we check for to be confident that the above things done by Brian have worked? + # Then the downloaded zip should contain... ? + When "Carol" logs in + And "Carol" opens the "files" app + And "Carol" navigates to the shared with me page + And "Carol" accepts the following shares + | name | + | meal plan | + And "Carol" downloads the following resources using the sidebar panel + | resource | from | + | data.zip | meal plan | + | lorem.txt | meal plan | + | lorem-big.txt | meal plan | + # Then what do we check for to be confident that the above things done by Carol have worked? + # Then the downloaded files should have content "abc..." + And "Carol" logs out + When "Brian" downloads the following resources using the sidebar panel + | resource | from | + | lorem.txt | meal plan | + | lorem-big.txt | meal plan | + # Then what do we check for to be confident that the above things done by Brian have worked? + # Then the downloaded files should have content "abc..." + And "Brian" logs out + And "Alice" downloads the following resources using the sidebar panel + | resource | from | + | parent.txt | groups/Kindergarten Koalas/meal plan | + | lorem.txt | groups/Kindergarten Koalas/meal plan | + | lorem-big.txt | groups/Kindergarten Koalas/meal plan | + | data.zip | groups/Pre-Schools Pirates/meal plan | + | lorem.txt | groups/Pre-Schools Pirates/meal plan | + | lorem-big.txt | groups/Pre-Schools Pirates/meal plan | + | data.zip | groups/Teddy Bear Daycare/meal plan | + | lorem.txt | groups/Teddy Bear Daycare/meal plan | + | lorem-big.txt | groups/Teddy Bear Daycare/meal plan | + # Then what do we check for to be confident that the above things done by Alice have worked? + # Then the downloaded files should have content "abc..." + And "Alice" logs out diff --git a/tests/e2e/support/objects/app-files/resource/actions.ts b/tests/e2e/support/objects/app-files/resource/actions.ts index 3d16c1ad9e8..3551b4bb3f9 100644 --- a/tests/e2e/support/objects/app-files/resource/actions.ts +++ b/tests/e2e/support/objects/app-files/resource/actions.ts @@ -19,9 +19,14 @@ export const clickResource = async ({ }): Promise => { const paths = path.split('/') for (const name of paths) { + const resource = await page.locator(`[data-test-resource-name="${name}"]`) + const itemId = await resource.locator('//ancestor::tr').getAttribute('data-item-id') + await Promise.all([ - page.locator(`[data-test-resource-name="${name}"]`).click(), - page.waitForResponse((resp) => resp.url().endsWith(encodeURIComponent(name))) + resource.click(), + page.waitForResponse( + (resp) => resp.url().endsWith(encodeURIComponent(name)) || resp.url().endsWith(itemId) + ) ]) } }