mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 20:05:06 +03:00
feat(storage): drive version history off the real endpoints
The preview panel now addresses versions by the object's full path, which is what the list, move and delete endpoints take, rather than by the leaf name the explorer renders. `VersionHistory` keeps `objectName` for copy and takes `path` separately. Expiry countdowns read the bucket's stored lifecycle policy instead of an empty placeholder, so a row's fate reflects the policy that governs it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
This commit is contained in:
1 parent
0cd08c6b1a
commit
0b8ef6dad2
2 files changed
+35
-18
No files matched your search
@@ -24,6 +24,7 @@ import {
|
||||
} from 'ui'
|
||||
import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
|
||||
import { fromLifecycleRules } from '../BucketVersioningFields.lifecycle'
|
||||
import { URL_EXPIRY_DURATION } from '../Storage.constants'
|
||||
import { StorageItem } from '../Storage.types'
|
||||
import { getBucketVersioningState } from '../StorageVersioning.constants'
|
||||
@@ -36,6 +37,7 @@ import { VersionCompareWidget } from './VersionCompareWidget'
|
||||
import { VersionHistory } from './VersionHistory'
|
||||
import { useIsStorageVersioningEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
||||
import { bucketLifecycleQueryOptions } from '@/data/storage/bucket-lifecycle-query'
|
||||
import { useObjectPurgeMutation } from '@/data/storage/versioning/object-purge-mutation'
|
||||
import { useObjectVersionRestoreMutation } from '@/data/storage/versioning/object-version-restore-mutation'
|
||||
import {
|
||||
@@ -52,12 +54,6 @@ const PREVIEW_SIZE_LIMIT = 10 * 1024 * 1024 // 10MB
|
||||
|
||||
const PANEL_WIDTH = 450
|
||||
|
||||
/**
|
||||
* TODO(storage-versioning): read the bucket's stored policy once the Storage API
|
||||
* returns it. No condition is set until then, so no row shows an expiry.
|
||||
*/
|
||||
const EMPTY_LIFECYCLE_POLICY: LifecyclePolicy = { expiryDays: null, maxVersions: null }
|
||||
|
||||
const PreviewFile = ({ item }: { item: StorageItem }) => {
|
||||
const { projectRef, selectedBucket, openedFolders } = useStorageExplorerStateSnapshot()
|
||||
const folderPath = getPathAlongOpenedFolders({ openedFolders, selectedBucket }, false)
|
||||
@@ -338,6 +334,7 @@ export const PreviewPane = () => {
|
||||
projectRef,
|
||||
selectedBucket,
|
||||
selectedFilePreview: file,
|
||||
openedFolders,
|
||||
setSelectedItemsToDelete,
|
||||
setSelectedFileCustomExpiry,
|
||||
downloadFile,
|
||||
@@ -359,14 +356,29 @@ export const PreviewPane = () => {
|
||||
|
||||
const versioningState = getBucketVersioningState(selectedBucket)
|
||||
|
||||
// The version endpoints address an object by its full path in the bucket, not
|
||||
// by the leaf name the explorer renders.
|
||||
const folderPath = getPathAlongOpenedFolders({ openedFolders, selectedBucket }, false)
|
||||
const filePath = file ? [folderPath, file.name].filter(Boolean).join('/') : undefined
|
||||
|
||||
const { data: lifecycle } = useQuery({
|
||||
...bucketLifecycleQueryOptions({ projectRef, bucketId: selectedBucket?.id }),
|
||||
enabled: isStorageVersioningEnabled && !!projectRef && !!selectedBucket?.id,
|
||||
})
|
||||
const storedPolicy = fromLifecycleRules(lifecycle)
|
||||
const lifecyclePolicy: LifecyclePolicy = {
|
||||
expiryDays: storedPolicy.versionExpiryDays,
|
||||
maxVersions: storedPolicy.maxNoncurrentVersions,
|
||||
}
|
||||
|
||||
const { data: versions } = useQuery({
|
||||
...objectVersionsQueryOptions({
|
||||
projectRef,
|
||||
bucketId: selectedBucket?.id,
|
||||
objectName: file?.name,
|
||||
lifecyclePolicy: EMPTY_LIFECYCLE_POLICY,
|
||||
path: filePath,
|
||||
lifecyclePolicy,
|
||||
}),
|
||||
enabled: isStorageVersioningEnabled && !!projectRef && !!selectedBucket?.id && !!file?.name,
|
||||
enabled: isStorageVersioningEnabled && !!projectRef && !!selectedBucket?.id && !!filePath,
|
||||
})
|
||||
|
||||
const { mutate: restoreVersion, isPending: isRestoring } = useObjectVersionRestoreMutation({
|
||||
@@ -395,18 +407,18 @@ export const PreviewPane = () => {
|
||||
const isComparing = previewedVersion !== undefined && !previewedVersion.isCurrent
|
||||
|
||||
const handleRestore = () => {
|
||||
if (!projectRef || !selectedBucket?.id || !previewedVersion) return
|
||||
if (!projectRef || !selectedBucket?.id || !previewedVersion || !filePath) return
|
||||
restoreVersion({
|
||||
projectRef,
|
||||
bucketId: selectedBucket.id,
|
||||
objectName: file.name,
|
||||
path: filePath,
|
||||
versionId: previewedVersion.versionId,
|
||||
})
|
||||
}
|
||||
|
||||
const handlePurge = () => {
|
||||
if (!projectRef || !selectedBucket?.id) return
|
||||
purgeObject({ projectRef, bucketId: selectedBucket.id, objectName: file.name })
|
||||
if (!projectRef || !selectedBucket?.id || !filePath) return
|
||||
purgeObject({ projectRef, bucketId: selectedBucket.id, path: filePath })
|
||||
}
|
||||
|
||||
// The compare widget replaces the top of the panel, so scroll up to show it.
|
||||
@@ -469,9 +481,10 @@ export const PreviewPane = () => {
|
||||
projectRef={projectRef}
|
||||
bucketId={selectedBucket?.id}
|
||||
objectName={file.name}
|
||||
path={filePath ?? file.name}
|
||||
versioningState={versioningState}
|
||||
lifecyclePolicy={EMPTY_LIFECYCLE_POLICY}
|
||||
expirationMode="and"
|
||||
lifecyclePolicy={lifecyclePolicy}
|
||||
expirationMode={storedPolicy.expirationMode}
|
||||
mimeType={mimeType}
|
||||
previewedVersionId={previewedVersion?.versionId}
|
||||
onPreview={handlePreviewVersion}
|
||||
|
||||
@@ -127,7 +127,10 @@ const VersionActionsMenu = ({
|
||||
interface VersionHistoryProps {
|
||||
projectRef?: string
|
||||
bucketId?: string
|
||||
/** Leaf name, for copy. */
|
||||
objectName: string
|
||||
/** Full path within the bucket, which is what the version endpoints address. */
|
||||
path: string
|
||||
versioningState: BucketVersioningState
|
||||
lifecyclePolicy: LifecyclePolicy
|
||||
expirationMode: ExpirationMode
|
||||
@@ -142,6 +145,7 @@ export const VersionHistory = ({
|
||||
projectRef,
|
||||
bucketId,
|
||||
objectName,
|
||||
path,
|
||||
versioningState,
|
||||
lifecyclePolicy,
|
||||
expirationMode,
|
||||
@@ -157,7 +161,7 @@ export const VersionHistory = ({
|
||||
isError,
|
||||
error,
|
||||
isSuccess,
|
||||
} = useQuery(objectVersionsQueryOptions({ projectRef, bucketId, objectName, lifecyclePolicy }))
|
||||
} = useQuery(objectVersionsQueryOptions({ projectRef, bucketId, path, lifecyclePolicy }))
|
||||
|
||||
const [versionToDelete, setVersionToDelete] = useState<ObjectVersion>()
|
||||
|
||||
@@ -197,7 +201,7 @@ export const VersionHistory = ({
|
||||
|
||||
const handleRestore = (version: ObjectVersion) => {
|
||||
if (!projectRef || !bucketId) return
|
||||
restoreVersion({ projectRef, bucketId, objectName, versionId: version.versionId })
|
||||
restoreVersion({ projectRef, bucketId, path, versionId: version.versionId })
|
||||
}
|
||||
|
||||
if (isPending) return <GenericSkeletonLoader />
|
||||
@@ -325,7 +329,7 @@ export const VersionHistory = ({
|
||||
deleteVersion({
|
||||
projectRef,
|
||||
bucketId,
|
||||
objectName,
|
||||
path,
|
||||
versionId: versionToDelete.versionId,
|
||||
})
|
||||
}}
|
||||
|
||||
Reference in new issue
Block a user