Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: private link error messages and resolving hidden shares #10321

Merged
merged 4 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog/unreleased/bugfix-private-link-error-messages
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Bugfix: Private link error messages

Private links not showing proper error messages has been fixed.

https://github.com/owncloud/web/pull/10321
https://github.com/owncloud/web/issues/10315
1 change: 1 addition & 0 deletions changelog/unreleased/enhancement-show-hide-shares
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Furthermore, accepting and rejecting shares has been renamed to "enable sync"/"d
https://github.com/owncloud/web/issues/9531
https://github.com/owncloud/web/pull/9718
https://github.com/owncloud/web/pull/10097
https://github.com/owncloud/web/pull/10321
141 changes: 69 additions & 72 deletions packages/web-runtime/src/pages/resolvePrivateLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,15 @@
</template>
<template v-else-if="errorMessage">
<div class="oc-card-header oc-link-resolve-error-title">
<h2 v-if="isUnacceptedShareError">
{{ resource.name }}
</h2>
<h2 v-else key="private-link-error">
<h2 key="private-link-error">
<span v-text="$gettext('An error occurred while resolving the private link')" />
</h2>
</div>
<div class="oc-card-body oc-link-resolve-error-message">
<p class="oc-text-xlarge">{{ errorMessage }}</p>
<p
v-if="isUnacceptedShareError"
v-text="$gettext('Note: You can reload this page after you accept the share.')"
v-text="$gettext('You can reload this page after you have enabled syncing the share.')"
/>
</div>
</template>
Expand All @@ -53,7 +50,8 @@ import {
useRouteQuery,
useConfigurationManager,
createLocationSpaces,
createLocationShares
createLocationShares,
useClientService
} from '@ownclouders/web-pkg'
import { unref, defineComponent, computed, onMounted, ref, Ref } from 'vue'
// import { createLocationSpaces } from 'web-app-files/src/router'
Expand All @@ -71,6 +69,8 @@ export default defineComponent({
const id = useRouteParam('fileId')
const configurationManager = useConfigurationManager()
const { $gettext } = useGettext()
const clientService = useClientService()

const resource: Ref<Resource> = ref()
const sharedParentResource: Ref<Resource> = ref()
const isUnacceptedShareError = ref(false)
Expand All @@ -90,60 +90,74 @@ export default defineComponent({
})

const resolvePrivateLinkTask = useTask(function* (signal, id) {
if (
[
`${SHARE_JAIL_ID}$${SHARE_JAIL_ID}!${SHARE_JAIL_ID}`,
`${SHARE_JAIL_ID}$${SHARE_JAIL_ID}`
].includes(id)
) {
return router.push(createLocationShares('files-shares-with-me'))
}

let result: Awaited<ReturnType<typeof getResourceContext>>
try {
if (
[
`${SHARE_JAIL_ID}$${SHARE_JAIL_ID}!${SHARE_JAIL_ID}`,
`${SHARE_JAIL_ID}$${SHARE_JAIL_ID}`
].includes(id)
) {
return router.push(createLocationShares('files-shares-with-me'))
}
result = yield getResourceContext(id)
} catch (e) {
// error means the resurce is an unaccepted/unsynced share
isUnacceptedShareError.value = true
throw Error(e)
}

const result = yield getResourceContext(id)
const { space, resource } = result
let { path } = result
const { space, resource } = result
let { path } = result

let resourceIsNestedInShare = false
if (isShareSpaceResource(space)) {
sharedParentResource.value = resource
resourceIsNestedInShare = path !== '/'
}
if (!path) {
// empty path means the user has no access to the resource or it doesn't exist
throw new Error('The file or folder does not exist')
}

let fileId: string
let targetLocation: RouteLocationNamedRaw
if (unref(resource).type === 'folder') {
fileId = unref(resource).fileId
targetLocation = createLocationSpaces('files-spaces-generic')
} else {
fileId = unref(resource).parentFolderId
path = dirname(path)
targetLocation =
space.driveType === 'share' && !resourceIsNestedInShare
? createLocationShares('files-shares-with-me')
: createLocationSpaces('files-spaces-generic')
let resourceIsNestedInShare = false
let isHiddenShare = false
if (isShareSpaceResource(space)) {
sharedParentResource.value = resource
resourceIsNestedInShare = path !== '/'
if (!resourceIsNestedInShare && space.shareId) {
const { shareInfo } = yield clientService.owncloudSdk.shares.getShare(space.shareId)
isHiddenShare = shareInfo.hidden === 'true'
}
}

const { params, query } = createFileRouteOptions(space, { fileId, path })
const openWithDefault =
configurationManager.options.openLinksWithDefaultApp &&
unref(openWithDefaultApp) !== 'false' &&
!unref(details)

targetLocation.params = params
targetLocation.query = {
...query,
scrollTo:
targetLocation.name === 'files-shares-with-me' ? space.shareId : unref(resource).fileId,
...(unref(details) && { details: unref(details) }),
...(openWithDefault && { openWithDefaultApp: 'true' })
}
let fileId: string
let targetLocation: RouteLocationNamedRaw
if (unref(resource).type === 'folder') {
fileId = unref(resource).fileId
targetLocation = createLocationSpaces('files-spaces-generic')
} else {
fileId = unref(resource).parentFolderId
path = dirname(path)
targetLocation =
space.driveType === 'share' && !resourceIsNestedInShare
? createLocationShares('files-shares-with-me')
: createLocationSpaces('files-spaces-generic')
}

router.push(targetLocation)
} catch (e) {
isUnacceptedShareError.value = true
throw Error(e)
const { params, query } = createFileRouteOptions(space, { fileId, path })
const openWithDefault =
configurationManager.options.openLinksWithDefaultApp &&
unref(openWithDefaultApp) !== 'false' &&
!unref(details)

targetLocation.params = params
targetLocation.query = {
...query,
scrollTo:
targetLocation.name === 'files-shares-with-me' ? space.shareId : unref(resource).fileId,
...(unref(details) && { details: unref(details) }),
...(isHiddenShare && { 'q_share-visibility': 'hidden' }),
...(openWithDefault && { openWithDefaultApp: 'true' })
}

router.push(targetLocation)
})

const loading = computed(() => {
Expand All @@ -160,30 +174,13 @@ export default defineComponent({

const errorMessage = computed(() => {
if (unref(isUnacceptedShareError)) {
if (
!unref(sharedParentResource) ||
unref(resource).name === unref(sharedParentResource).name
) {
if (unref(resource).type === 'file') {
return $gettext(
'This file has been shared with you. Accept it in "Shares" > "Shared with me" to view it.'
)
}
if (!unref(sharedParentResource)) {
return $gettext(
'This folder has been shared with you. Accept it in "Shares" > "Shared with me" to view it.'
)
}

if (unref(resource).type === 'file') {
return $gettext(
'This file has been shared with you via "%{parentShareName}". Accept the share "%{parentShareName}" in "Shares" > "Shared with me" to view it.',
{
parentShareName: unref(sharedParentResource).name
}
'This file or folder has been shared with you. Enable the sync in "Shares" > "Shared with me" to view it.'
)
} else {
return $gettext(
'This folder has been shared with you via "%{parentShareName}". Accept the share "%{parentShareName}" in "Shares" > "Shared with me" to view it.',
'This file or folder has been shared with you via "%{parentShareName}". Enable the sync for the share "%{parentShareName}" in "Shares" > "Shared with me" to view it.',
{
parentShareName: unref(sharedParentResource).name
}
Expand Down
49 changes: 43 additions & 6 deletions packages/web-runtime/tests/unit/pages/resolvePrivateLink.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('resolvePrivateLink', () => {
const driveAliasAndItem = 'personal/home'
const space = mock<SpaceResource>({ getDriveAliasAndItem: () => driveAliasAndItem })
const resource = mock<Resource>({ fileId })
const { wrapper, mocks } = getWrapper({ space, resource, fileId })
const { wrapper, mocks } = getWrapper({ space, resource, fileId, path: '/' })
await wrapper.vm.resolvePrivateLinkTask.last
expect(mocks.$router.push).toHaveBeenCalledWith(
expect.objectContaining({
Expand Down Expand Up @@ -74,25 +74,57 @@ describe('resolvePrivateLink', () => {
expect.objectContaining({ name: 'files-shares-with-me' })
)
})
it('adds the hidden share param for hidden shares', async () => {
const fileId = '1'
const driveAliasAndItem = 'shares/someShare'
const space = mock<SpaceResource>({
driveType: 'share',
getDriveAliasAndItem: () => driveAliasAndItem
})
const resource = mock<Resource>({ fileId, type: 'file' })
const { wrapper, mocks } = getWrapper({
space,
resource,
fileId,
path: '/',
hiddenShare: true
})
await wrapper.vm.resolvePrivateLinkTask.last
expect(mocks.$router.push).toHaveBeenCalledWith(
expect.objectContaining({
query: expect.objectContaining({ 'q_share-visibility': 'hidden' })
})
)
})
})
it('passes the details query param if given via query', async () => {
const details = 'sharing'
const { wrapper, mocks } = getWrapper({ details })
const { wrapper, mocks } = getWrapper({ details, path: '/' })
await wrapper.vm.resolvePrivateLinkTask.last
expect(mocks.$router.push).toHaveBeenCalledWith(
expect.objectContaining({ query: expect.objectContaining({ details }) })
)
})
it('throws an error if the path is empty', async () => {
const { wrapper } = getWrapper()
try {
await wrapper.vm.resolvePrivateLinkTask.last
} catch (e) {}

expect(wrapper.find('.oc-link-resolve-error-message p').text()).toEqual(
'The file or folder does not exist'
)
})
describe('openWithDefaultApp', () => {
it('correctly passes the openWithDefaultApp param if enabled and given via query', async () => {
const { wrapper, mocks } = getWrapper()
const { wrapper, mocks } = getWrapper({ path: '/' })
await wrapper.vm.resolvePrivateLinkTask.last
expect(mocks.$router.push).toHaveBeenCalledWith(
expect.objectContaining({ query: expect.objectContaining({ openWithDefaultApp: 'true' }) })
)
})
it('does not pass the openWithDefaultApp param when details param is given', async () => {
const { wrapper, mocks } = getWrapper({ details: 'sharing' })
const { wrapper, mocks } = getWrapper({ details: 'sharing', path: '/' })
await wrapper.vm.resolvePrivateLinkTask.last
expect(mocks.$router.push).toHaveBeenCalledWith(
expect.objectContaining({
Expand All @@ -101,7 +133,7 @@ describe('resolvePrivateLink', () => {
)
})
it('does not pass the openWithDefaultApp param when disabled via config', async () => {
const { wrapper, mocks } = getWrapper({ openLinksWithDefaultApp: false })
const { wrapper, mocks } = getWrapper({ openLinksWithDefaultApp: false, path: '/' })
await wrapper.vm.resolvePrivateLinkTask.last
expect(mocks.$router.push).toHaveBeenCalledWith(
expect.objectContaining({
Expand All @@ -110,7 +142,7 @@ describe('resolvePrivateLink', () => {
)
})
it('does not pass the openWithDefaultApp param when not requested via query', async () => {
const { wrapper, mocks } = getWrapper({ openWithDefaultAppQuery: 'false' })
const { wrapper, mocks } = getWrapper({ openWithDefaultAppQuery: 'false', path: '/' })
await wrapper.vm.resolvePrivateLinkTask.last
expect(mocks.$router.push).toHaveBeenCalledWith(
expect.objectContaining({
Expand All @@ -127,6 +159,7 @@ function getWrapper({
path = '',
fileId = '',
details = '',
hiddenShare = false,
openWithDefaultAppQuery = 'true',
openLinksWithDefaultApp = true
} = {}) {
Expand Down Expand Up @@ -155,6 +188,10 @@ function getWrapper({
)

const mocks = { ...defaultComponentMocks() }
mocks.$clientService.owncloudSdk.shares.getShare.mockResolvedValue({
shareInfo: { hidden: hiddenShare ? 'true' : 'false' }
})

const storeOptions = defaultStoreMockOptions
const store = createStore(storeOptions)

Expand Down