Skip to content

Commit 3f6f8aa

Browse files
awaseemivasilov
andauthored
Fix: Use storage API for downloading files in the Dashboard (supabase#40592)
* added ability to download files from storage api directly * updated to rechange size * updated test methods * updated test suite * updated tests * updated to remove loops * removed AI slop * Updated to use single API key in mem * Fix the memoization of the getOrRefreshTempApiKey function. * updated imports * updated imports * updated to use import meta --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
1 parent b12d68f commit 3f6f8aa

14 files changed

Lines changed: 764 additions & 229 deletions

File tree

apps/studio/components/interfaces/Storage/FilesBuckets/BucketTable.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export const BucketTableRow = ({
102102
return (
103103
<BucketTableRow
104104
key={bucket.id}
105+
data-bucket-id={bucket.id}
105106
className="relative cursor-pointer h-16 group inset-focus"
106107
onClick={(event) => handleBucketNavigation(bucket.id, event)}
107108
onKeyDown={(event) => {

apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
import { PermissionAction } from '@supabase/shared-types/out/constants'
22
import { Download, Move, Trash2, X } from 'lucide-react'
33

4-
import { useParams } from 'common'
54
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
65
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
76
import { useStorageExplorerStateSnapshot } from 'state/storage-explorer'
87
import { Button } from 'ui'
9-
import { downloadFile } from './StorageExplorer.utils'
108

119
export const FileExplorerHeaderSelection = () => {
12-
const { ref: projectRef, bucketId } = useParams()
1310
const { can: canUpdateFiles } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
1411

1512
const {
1613
selectedItems,
14+
downloadFile,
1715
downloadSelectedFiles,
1816
clearSelectedItems,
1917
setSelectedItemsToDelete,
@@ -37,7 +35,7 @@ export const FileExplorerHeaderSelection = () => {
3735
type="primary"
3836
onClick={async () => {
3937
if (selectedItems.length === 1) {
40-
await downloadFile({ projectRef, bucketId, file: selectedItems[0] })
38+
await downloadFile(selectedItems[0])
4139
} else {
4240
await downloadSelectedFiles(selectedItems)
4341
}

apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerRow.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import {
4848
} from '../Storage.constants'
4949
import { StorageItemWithColumn, type StorageItem } from '../Storage.types'
5050
import { FileExplorerRowEditing } from './FileExplorerRowEditing'
51-
import { copyPathToFolder, downloadFile } from './StorageExplorer.utils'
51+
import { copyPathToFolder } from './StorageExplorer.utils'
5252
import { useCopyUrl } from './useCopyUrl'
5353

5454
export const RowIcon = ({
@@ -115,7 +115,7 @@ export const FileExplorerRow = ({
115115
selectedItems = [],
116116
style,
117117
}: FileExplorerRowProps) => {
118-
const { ref: projectRef, bucketId } = useParams()
118+
const { bucketId } = useParams()
119119

120120
const {
121121
selectedBucket,
@@ -128,6 +128,7 @@ export const FileExplorerRow = ({
128128
setSelectedFileCustomExpiry,
129129
setSelectedItems,
130130
setSelectedItemsToDelete,
131+
downloadFile,
131132
setSelectedItemToRename,
132133
setSelectedItemsToMove,
133134
openFolder,
@@ -254,13 +255,7 @@ export const FileExplorerRow = ({
254255
{
255256
name: 'Download',
256257
icon: <Download size={14} strokeWidth={1} />,
257-
onClick: async () => {
258-
await downloadFile({
259-
projectRef,
260-
bucketId,
261-
file: itemWithColumnIndex,
262-
})
263-
},
258+
onClick: () => downloadFile(itemWithColumnIndex),
264259
},
265260
{ name: 'Separator', icon: undefined, onClick: undefined },
266261
]

apps/studio/components/interfaces/Storage/StorageExplorer/ItemContextMenu.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@ import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
88
import { useStorageExplorerStateSnapshot } from 'state/storage-explorer'
99
import { URL_EXPIRY_DURATION } from '../Storage.constants'
1010
import { StorageItemWithColumn } from '../Storage.types'
11-
import { downloadFile } from './StorageExplorer.utils'
1211
import { useCopyUrl } from './useCopyUrl'
1312

1413
interface ItemContextMenuProps {
1514
id: string
1615
}
1716

1817
export const ItemContextMenu = ({ id = '' }: ItemContextMenuProps) => {
19-
const { ref: projectRef, bucketId } = useParams()
2018
const snap = useStorageExplorerStateSnapshot()
2119
const { setSelectedFileCustomExpiry } = snap
2220

@@ -25,6 +23,7 @@ export const ItemContextMenu = ({ id = '' }: ItemContextMenuProps) => {
2523
setSelectedItemsToDelete,
2624
setSelectedItemToRename,
2725
setSelectedItemsToMove,
26+
downloadFile,
2827
} = useStorageExplorerStateSnapshot()
2928
const { onCopyUrl } = useCopyUrl()
3029
const isPublic = selectedBucket.public
@@ -41,7 +40,7 @@ export const ItemContextMenu = ({ id = '' }: ItemContextMenuProps) => {
4140
case 'move':
4241
return setSelectedItemsToMove([item])
4342
case 'download':
44-
return await downloadFile({ projectRef, bucketId, file: item })
43+
return await downloadFile(item)
4544
default:
4645
break
4746
}

apps/studio/components/interfaces/Storage/StorageExplorer/PreviewPane.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
} from 'ui'
2020
import { URL_EXPIRY_DURATION } from '../Storage.constants'
2121
import { StorageItem } from '../Storage.types'
22-
import { downloadFile } from './StorageExplorer.utils'
2322
import { useCopyUrl } from './useCopyUrl'
2423
import { useFetchFileUrlQuery } from './useFetchFileUrlQuery'
2524

@@ -116,14 +115,13 @@ const PreviewFile = ({ item }: { item: StorageItem }) => {
116115
}
117116

118117
export const PreviewPane = () => {
119-
const { ref: projectRef, bucketId } = useParams()
120-
121118
const {
122119
selectedBucket,
123120
selectedFilePreview: file,
124121
setSelectedItemsToDelete,
125122
setSelectedFilePreview,
126123
setSelectedFileCustomExpiry,
124+
downloadFile,
127125
} = useStorageExplorerStateSnapshot()
128126
const { onCopyUrl } = useCopyUrl()
129127

@@ -204,7 +202,7 @@ export const PreviewPane = () => {
204202
type="default"
205203
icon={<Download />}
206204
disabled={file.isCorrupted}
207-
onClick={async () => await downloadFile({ projectRef, bucketId, file })}
205+
onClick={() => downloadFile(file)}
208206
>
209207
Download
210208
</Button>

apps/studio/components/interfaces/Storage/StorageExplorer/StorageExplorer.utils.tsx

Lines changed: 6 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { toast } from 'sonner'
22

3-
import { downloadBucketObject } from 'data/storage/bucket-object-download-mutation'
43
import { StorageObject } from 'data/storage/bucket-objects-list-mutation'
5-
import { SONNER_DEFAULT_DURATION, copyToClipboard } from 'ui'
4+
import { copyToClipboard } from 'ui'
65
import { STORAGE_ROW_STATUS, STORAGE_ROW_TYPES } from '../Storage.constants'
7-
import { StorageItem, StorageItemMetadata, StorageItemWithColumn } from '../Storage.types'
6+
import { StorageItem, StorageItemMetadata } from '../Storage.types'
87

98
type UploadProgress = {
109
percentage: number
@@ -17,7 +16,10 @@ type UploadProgress = {
1716
const CORRUPTED_THRESHOLD_MS = 15 * 60 * 1000 // 15 minutes
1817
export const EMPTY_FOLDER_PLACEHOLDER_FILE_NAME = '.emptyFolderPlaceholder'
1918

20-
export const copyPathToFolder = (openedFolders: StorageItem[], item: StorageItemWithColumn) => {
19+
export const copyPathToFolder = (
20+
openedFolders: StorageItem[],
21+
item: StorageItem & { columnIndex: number }
22+
) => {
2123
const folders = openedFolders.slice(0, item.columnIndex).map((folder) => folder.name)
2224
const path = folders.length > 0 ? `${folders.join('/')}/${item.name}` : item.name
2325
copyToClipboard(path)
@@ -38,67 +40,6 @@ export const formatTime = (seconds: number) => {
3840
return `${seconds}s`
3941
}
4042

41-
export const downloadFile = async ({
42-
projectRef,
43-
bucketId,
44-
file,
45-
showToast = true,
46-
returnBlob = false,
47-
}: {
48-
projectRef?: string
49-
bucketId?: string
50-
file: StorageItemWithColumn
51-
showToast?: boolean
52-
returnBlob?: boolean
53-
}) => {
54-
if (!projectRef) return toast.error('Failed to download: Project ref is required')
55-
if (!bucketId) return toast.error('Failed to download: Bucket ID is required')
56-
if (!file.path) return toast.error('Failed to download: Unable to find path to file')
57-
58-
const fileName: string = file.name
59-
const fileMimeType = file?.metadata?.mimetype ?? undefined
60-
61-
const toastId = showToast ? toast.loading(`Retrieving ${fileName}...`) : undefined
62-
63-
try {
64-
const data = await downloadBucketObject({
65-
projectRef,
66-
bucketId,
67-
path: file.path,
68-
})
69-
70-
const blob = await data.blob()
71-
const newBlob = new Blob([blob], { type: fileMimeType })
72-
if (returnBlob) return { name: fileName, blob: newBlob }
73-
74-
const blobUrl = window.URL.createObjectURL(newBlob)
75-
const link = document.createElement('a')
76-
link.href = blobUrl
77-
link.setAttribute('download', `${fileName}`)
78-
document.body.appendChild(link)
79-
link.click()
80-
link.parentNode?.removeChild(link)
81-
window.URL.revokeObjectURL(blob)
82-
if (toastId) {
83-
toast.success(`Downloading ${fileName}`, {
84-
id: toastId,
85-
closeButton: true,
86-
duration: SONNER_DEFAULT_DURATION,
87-
})
88-
}
89-
return true
90-
} catch (err) {
91-
if (toastId) {
92-
toast.error(`Failed to download ${fileName}`, {
93-
id: toastId,
94-
closeButton: true,
95-
duration: SONNER_DEFAULT_DURATION,
96-
})
97-
}
98-
return false
99-
}
100-
}
101-
10243
export const calculateTotalRemainingTime = (progresses: UploadProgress[]) => {
10344
let totalRemainingTime = 0
10445
let totalRemainingBytes = 0

0 commit comments

Comments
 (0)