diff --git a/apps/files/src/components/Collaborators/Collaborator.vue b/apps/files/src/components/Collaborators/Collaborator.vue index 91c3347a08a..481efdb5ea5 100644 --- a/apps/files/src/components/Collaborators/Collaborator.vue +++ b/apps/files/src/components/Collaborators/Collaborator.vue @@ -18,7 +18,7 @@ - + @@ -43,7 +43,7 @@ - + @@ -59,7 +59,16 @@ import Mixins from '../../mixins/collaborators' export default { name: 'Collaborator', - props: ['collaborator'], + props: { + collaborator: { + type: Object, + required: true + }, + modifiable: { + type: Boolean, + default: false + } + }, mixins: [ Mixins ], @@ -71,11 +80,11 @@ export default { computed: { ...mapGetters(['user']), - $_isIndirectIncomingShare () { + $_isIndirectShare () { // it is assumed that the "incoming" attribute only exists // on shares coming from this.sharesTree which are all indirect // and not related to the current folder - return this.collaborator.incoming + return this.collaborator.incoming || this.collaborator.outgoing }, $_reshareInformation () { @@ -91,7 +100,7 @@ export default { }, $_getViaLabel () { - if (!this.$_isIndirectIncomingShare) { + if (!this.$_isIndirectShare) { return null } const translated = this.$gettext('Via %{folderName}') diff --git a/apps/files/src/components/FileSharingSidebar.vue b/apps/files/src/components/FileSharingSidebar.vue index af7b6362db2..fcb32615fa4 100644 --- a/apps/files/src/components/FileSharingSidebar.vue +++ b/apps/files/src/components/FileSharingSidebar.vue @@ -14,18 +14,42 @@ key="no-reshare-permissions-message" v-text="noResharePermsMessage" /> - -
  • - -
  • -
    -
    No collaborators
    +
    +
    + Owner +
    + +
    +
    +
    + Shares +
    + +
  • + +
  • +
    +
    +
    +
    + Via Parent +
    + +
    +
    No collaborators
    @@ -106,59 +130,27 @@ export default { $_sharesLoading () { return this.sharesLoading && this.incomingSharesLoading }, - $_allIncomingShares () { - const allShares = [...this.incomingShares] - const parentPaths = getParentPaths(this.highlightedFile.path, true) - if (parentPaths.length === 0) { - return [] - } - // remove root entry - parentPaths.pop() - - parentPaths.forEach((parentPath) => { - const shares = this.sharesTree[parentPath] - if (shares) { - shares.forEach((share) => { - if (share.incoming) { - allShares.push(share) - } - }) - } - }) - - return allShares + $_noCollaborators () { + return this.$_ownerAndResharers.length === 0 && + this.$_directOutgoingShares.length === 0 && + this.$_indirectOutgoingShares.length === 0 }, - $_ocCollaborators () { - const shares = this.shares - .filter(collaborator => this.$_ocCollaborators_isUser(collaborator) || this.$_ocCollaborators_isGroup(collaborator)) - .sort((c1, c2) => { - const name1 = c1.displayName.toLowerCase().trim() - const name2 = c2.displayName.toLowerCase().trim() - // sorting priority 1: display name (lower case, ascending), 2: share type (groups first), 3: id (ascending) - if (name1 === name2) { - if (this.$_ocCollaborators_isGroup(c1) === this.$_ocCollaborators_isGroup(c2)) { - return parseInt(c1.info.id, 10) < parseInt(c2.info.id, 10) ? -1 : 1 - } else { - return this.$_ocCollaborators_isGroup(c1) ? -1 : 1 - } - } else { - return textUtils.naturalSortCompare(name1, name2) - } - }) - .map(collaborator => { - collaborator.key = 'collaborator-' + collaborator.info.id - collaborator.modifiable = true - return collaborator - }) + $_ownerAndResharers () { + const ownerArray = this.$_shareOwnerAsCollaborator ? [this.$_shareOwnerAsCollaborator] : [] + return [ + ...ownerArray, + ...this.$_resharersAsCollaborators + ] + }, + $_shareOwnerAsCollaborator () { if (!this.$_allIncomingShares.length) { - return shares + return null } - const resharers = new Map() const firstShare = this.$_allIncomingShares[0] - const owner = { + return { ...firstShare, name: firstShare.info.uid_file_owner, displayName: firstShare.info.displayname_file_owner, @@ -169,9 +161,13 @@ export default { share_type: '' + shareTypes.user, // most code requires string.. share_with_additional_info: firstShare.info.additional_info_file_owner || [] }, - role: this.ownerRole, - modifiable: false + role: this.ownerRole } + }, + + $_resharersAsCollaborators () { + const resharers = new Map() + const owner = this.$_shareOwnerAsCollaborator this.$_allIncomingShares.forEach(share => { if (share.info.uid_owner !== owner.name) { @@ -186,17 +182,83 @@ export default { // set to user share for displaying user share_type: '' + shareTypes.user, // most code requires string.. share_with_additional_info: share.info.additional_info_owner || [] - }, - modifiable: false + } }) } }) - Array.prototype.unshift.apply(shares, Array.from(resharers.values())) - shares.unshift(owner) + // make them unique then sort + return Array.from(resharers.values()).sort(this.$_collaboratorsComparator.bind(this)) + }, + + /** + * Returns all incoming shares, direct and indirect + * + * @return {Array.} list of incoming shares + */ + $_allIncomingShares () { + // direct incoming shares + const allShares = [...this.incomingShares] + + // indirect incoming shares + const parentPaths = getParentPaths(this.highlightedFile.path, true) + if (parentPaths.length === 0) { + return [] + } + + // remove root entry + parentPaths.pop() - return shares + parentPaths.forEach((parentPath) => { + const shares = this.sharesTree[parentPath] + if (shares) { + shares.forEach((share) => { + if (share.incoming) { + allShares.push(share) + } + }) + } + }) + + return allShares + }, + + $_directOutgoingShares () { + // direct outgoing shares + return this.shares + .filter(this.$_isCollaboratorShare.bind(this)) + .sort(this.$_collaboratorsComparator.bind(this)) + .map(collaborator => { + collaborator.key = 'collaborator-' + collaborator.info.id + return collaborator + }) + }, + + $_indirectOutgoingShares () { + const allShares = [] + const parentPaths = getParentPaths(this.highlightedFile.path, true) + if (parentPaths.length === 0) { + return [] + } + + // remove root entry + parentPaths.pop() + + parentPaths.forEach((parentPath) => { + const shares = this.sharesTree[parentPath] + if (shares) { + shares.forEach((share) => { + if (share.outgoing && this.$_isCollaboratorShare(share)) { + share.key = 'indirect-collaborator-' + share.info.id + allShares.push(share) + } + }) + } + }) + + return allShares }, + $_ocCollaborators_canShare () { return this.highlightedFile.canShare() }, @@ -213,6 +275,23 @@ export default { 'loadIncomingShares', 'incomingSharesClearState' ]), + $_isCollaboratorShare (collaborator) { + return this.$_ocCollaborators_isUser(collaborator) || this.$_ocCollaborators_isGroup(collaborator) + }, + $_collaboratorsComparator (c1, c2) { + const name1 = c1.displayName.toLowerCase().trim() + const name2 = c2.displayName.toLowerCase().trim() + // sorting priority 1: display name (lower case, ascending), 2: share type (groups first), 3: id (ascending) + if (name1 === name2) { + if (this.$_ocCollaborators_isGroup(c1) === this.$_ocCollaborators_isGroup(c2)) { + return parseInt(c1.info.id, 10) < parseInt(c2.info.id, 10) ? -1 : 1 + } else { + return this.$_ocCollaborators_isGroup(c1) ? -1 : 1 + } + } else { + return textUtils.naturalSortCompare(name1, name2) + } + }, $_ocCollaborators_editShare (share) { this.currentShare = share this.visiblePanel = 'editCollaborator' diff --git a/changelog/unreleased/2897 b/changelog/unreleased/2897 new file mode 100644 index 00000000000..cd823c16bdb --- /dev/null +++ b/changelog/unreleased/2897 @@ -0,0 +1,8 @@ +Enhancement: Show indirect outgoing shares in collaborators list + +Whenever outgoing shares exist on any parent resource from the currently viewed resource, +the collaborators panel will now show these outgoing shares with a link to jump to the matching parent +resource. + +https://github.com/owncloud/phoenix/issues/2897 +https://github.com/owncloud/phoenix/pull/2929 diff --git a/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature b/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature index 183ad350f43..5e691a09522 100644 --- a/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature +++ b/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature @@ -422,7 +422,7 @@ Feature: Sharing files and folders with internal users | fileName | expectedIndicators | | sub-folder | user-indirect | - @skip @yetToImplement @issue-2897 + @issue-2897 Scenario: sharing details of items inside a shared folder Given user "user3" has been created with default attributes And user "user1" has uploaded file with content "test" to "/simple-folder/lorem.txt" @@ -434,26 +434,26 @@ Feature: Sharing files and folders with internal users When the user opens the share dialog for file "lorem.txt" using the webUI Then user "User Two" should be listed as "Editor" via "simple-folder" in the collaborators list on the webUI - @skip @yetToImplement @issue-2897 + @issue-2897 Scenario: sharing details of items inside a re-shared folder Given user "user3" has been created with default attributes And user "user1" has uploaded file with content "test" to "/simple-folder/lorem.txt" And user "user1" has shared folder "simple-folder" with user "user2" - And user "user2" has shared folder "simple-folder" with user "user3" + And user "user2" has shared folder "simple-folder (2)" with user "user3" And user "user2" has logged in using the webUI And the user opens folder "simple-folder (2)" using the webUI When the user opens the share dialog for folder "simple-empty-folder" using the webUI - Then user "User Three" should be listed as "Editor" via "simple-folder" in the collaborators list on the webUI + Then user "User Three" should be listed as "Editor" via "simple-folder (2)" in the collaborators list on the webUI When the user opens the share dialog for file "lorem.txt" using the webUI - Then user "User Three" should be listed as "Editor" via "simple-folder" in the collaborators list on the webUI + Then user "User Three" should be listed as "Editor" via "simple-folder (2)" in the collaborators list on the webUI - @skip @yetToImplement @issue-2897 + @issue-2897 Scenario: sharing details of items inside a shared folder shared with multiple users Given user "user3" has been created with default attributes And user "user1" has created folder "/simple-folder/sub-folder" And user "user1" has uploaded file with content "test" to "/simple-folder/sub-folder/lorem.txt" And user "user1" has shared folder "simple-folder" with user "user2" - And user "user1" has shared folder "/simple-folder/sub-folder" with user "user3" + And user "user1" has shared folder "simple-folder/sub-folder" with user "user3" And user "user1" has logged in using the webUI And the user opens folder "simple-folder/sub-folder" directly on the webUI When the user opens the share dialog for file "lorem.txt" using the webUI