Skip to content

Commit 8562648

Browse files
committed
fix(sharings): hide received shares from My Drive listing
1 parent 6677402 commit 8562648

3 files changed

Lines changed: 148 additions & 4 deletions

File tree

src/modules/views/Drive/DriveFolderView.jsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import FolderViewBreadcrumb from '@/modules/views/Folder/FolderViewBreadcrumb'
4747
import FolderViewHeader from '@/modules/views/Folder/FolderViewHeader'
4848
import { useFabOnMobile } from '@/modules/views/Folder/hooks/useFabOnMobile'
4949
import { useFolderViewBase } from '@/modules/views/Folder/hooks/useFolderViewBase'
50+
import { filterOutReceivedShares } from '@/modules/views/Folder/syncHelpers'
5051
import FolderViewBodyVz from '@/modules/views/Folder/virtualized/FolderViewBody'
5152
import { useResumeUploadFromFlagship } from '@/modules/views/Upload/useResumeFromFlagship'
5253

@@ -71,9 +72,21 @@ const DriveFolderView = () => {
7172
const [sortOrder, setSortOrder, isSettingsLoaded] =
7273
useFolderSort(currentFolderId)
7374

74-
const { allResults, isInError, isLoading, isPending } = useDriveQueries(
75-
currentFolderId,
76-
sortOrder
75+
const {
76+
allResults: rawResults,
77+
isInError,
78+
isLoading,
79+
isPending
80+
} = useDriveQueries(currentFolderId, sortOrder)
81+
// Received shares are tagged with an io.cozy.sharings reference by the stack
82+
// and may be materialised among the recipient's own files; they belong in the
83+
// Sharings section, not My Drive. Wait until the sharing context is fully
84+
// loaded so isOwner can tell a received share from the user's own folders;
85+
// filtering earlier sees an incomplete picture and would show-then-hide them.
86+
const allResults = useMemo(
87+
() =>
88+
allLoaded ? filterOutReceivedShares(rawResults, isOwner) : rawResults,
89+
[allLoaded, rawResults, isOwner]
7790
)
7891
const [foldersResult, filesResult] = allResults
7992
const canWriteToCurrentFolder = hasWriteAccess(currentFolderId)

src/modules/views/Folder/syncHelpers.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,43 @@ export const computeSyncingFakeFile = ({
135135
return updatedSyncingFakeFile
136136
}
137137

138+
/**
139+
* Whether the file is the root of a sharing the current instance received
140+
* (i.e. is referenced by an io.cozy.sharings the user does not own).
141+
*
142+
* The stack tags both the owner's shared folder and the recipient's copy with
143+
* an io.cozy.sharings reference, so ownership is what tells them apart: the
144+
* recipient must not see the received share among their own files (it belongs
145+
* in the Sharings section), while the owner keeps their folder in place.
146+
* @param {object} file - An io.cozy.files doc
147+
* @param {function} isOwner - cozy-sharing predicate, true when the instance owns the sharing of the given file id
148+
* @returns {bool}
149+
*/
150+
export const isReceivedShare = (file, isOwner) => {
151+
const fileReferences = get(file, 'relationships.referenced_by.data') || []
152+
const isSharingReferenced = fileReferences.some(
153+
reference => reference.type === 'io.cozy.sharings'
154+
)
155+
if (!isSharingReferenced) return false
156+
157+
return !isOwner(file._id ?? file.id ?? '')
158+
}
159+
160+
/**
161+
* Drops received shares from folder/file query results so they don't surface
162+
* among the user's own files. Returns new result objects with a filtered
163+
* `data` (and matching `count`), preserving the rest of each query result.
164+
* @param {array} queryResults - cozy-client query results (folders, files)
165+
* @param {function} isOwner - cozy-sharing ownership predicate
166+
* @returns {array} query results without received shares
167+
*/
168+
export const filterOutReceivedShares = (queryResults, isOwner) =>
169+
queryResults.map(result => {
170+
if (!result?.data) return result
171+
const data = result.data.filter(file => !isReceivedShare(file, isOwner))
172+
return { ...result, data, count: data.length }
173+
})
174+
138175
/**
139176
* Whether the file is referenced by a share in the sharing context
140177
* @param {object} file - An io.cozy.files doc

src/modules/views/Folder/syncHelpers.spec.js

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import {
33
createSyncingFakeFile,
44
computeSyncingFakeFile,
55
checkSyncingFakeFileObsolescence,
6-
isReferencedByShareInSharingContext
6+
isReferencedByShareInSharingContext,
7+
isReceivedShare,
8+
filterOutReceivedShares
79
} from './syncHelpers'
810

911
const queryResults = [
@@ -201,6 +203,98 @@ describe('syncHelpers', () => {
201203
})
202204
})
203205

206+
describe('isReceivedShare', () => {
207+
const sharingRoot = {
208+
_id: 'shared-folder-id',
209+
relationships: {
210+
referenced_by: {
211+
data: [{ id: 'sharing-id', type: 'io.cozy.sharings' }]
212+
}
213+
}
214+
}
215+
const ownFile = {
216+
_id: 'own-file-id',
217+
relationships: {
218+
referenced_by: {
219+
data: [{ id: 'album-id', type: 'io.cozy.photos.albums' }]
220+
}
221+
}
222+
}
223+
224+
it('returns true when referenced by a sharing the user does not own', () => {
225+
expect(isReceivedShare(sharingRoot, () => false)).toBe(true)
226+
})
227+
228+
it('returns false for the owner of the sharing', () => {
229+
expect(isReceivedShare(sharingRoot, () => true)).toBe(false)
230+
})
231+
232+
it('returns false when not referenced by any sharing', () => {
233+
expect(isReceivedShare(ownFile, () => false)).toBe(false)
234+
})
235+
236+
it('returns false when there is no referenced_by relationship', () => {
237+
expect(isReceivedShare({ _id: 'x' }, () => false)).toBe(false)
238+
})
239+
240+
it('returns false when referenced_by.data is null', () => {
241+
const file = {
242+
_id: 'x',
243+
relationships: { referenced_by: { data: null } }
244+
}
245+
expect(isReceivedShare(file, () => false)).toBe(false)
246+
})
247+
})
248+
249+
describe('filterOutReceivedShares', () => {
250+
const isOwner = id => id === 'own-shared-folder'
251+
const results = [
252+
{
253+
data: [
254+
{ _id: 'own-folder' },
255+
{
256+
_id: 'own-shared-folder',
257+
relationships: {
258+
referenced_by: {
259+
data: [{ id: 's1', type: 'io.cozy.sharings' }]
260+
}
261+
}
262+
},
263+
{
264+
_id: 'received-share',
265+
relationships: {
266+
referenced_by: {
267+
data: [{ id: 's2', type: 'io.cozy.sharings' }]
268+
}
269+
}
270+
}
271+
],
272+
hasMore: true,
273+
fetchMore: jest.fn()
274+
}
275+
]
276+
277+
it('drops received shares but keeps own and own-shared files', () => {
278+
const [filtered] = filterOutReceivedShares(results, isOwner)
279+
expect(filtered.data.map(f => f._id)).toEqual([
280+
'own-folder',
281+
'own-shared-folder'
282+
])
283+
expect(filtered.count).toBe(2)
284+
})
285+
286+
it('preserves the other result properties', () => {
287+
const [filtered] = filterOutReceivedShares(results, isOwner)
288+
expect(filtered.hasMore).toBe(true)
289+
expect(filtered.fetchMore).toBe(results[0].fetchMore)
290+
})
291+
292+
it('passes through results without data untouched', () => {
293+
const noData = [{ fetchStatus: 'loading' }]
294+
expect(filterOutReceivedShares(noData, isOwner)).toEqual(noData)
295+
})
296+
})
297+
204298
describe('isReferencedByShareInSharingContext', () => {
205299
it('should return true or false if the file is referenced or not by a share in sharing context', () => {
206300
const referencedFile = {

0 commit comments

Comments
 (0)