mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 10:55:06 +03:00
fix(storage): refresh the row after restoring a version
Restoring a noncurrent version invalidated the version list but left the explorer row showing the old size, type and modified date, since the live listing is the explorer's own state rather than a React Query cache. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
This commit is contained in:
3 files changed
+95
-2
No files matched your search
@@ -232,6 +232,7 @@ export const PreviewPane = () => {
|
||||
openedFolders,
|
||||
setSelectedItemsToDelete,
|
||||
setItemToPurge,
|
||||
refetchAllOpenedFolders,
|
||||
setSelectedFileCustomExpiry,
|
||||
downloadFile,
|
||||
} = useStorageExplorerStateSnapshot()
|
||||
@@ -276,9 +277,10 @@ export const PreviewPane = () => {
|
||||
})
|
||||
|
||||
const { mutate: restoreVersion, isPending: isRestoring } = useObjectVersionRestoreMutation({
|
||||
onSuccess: () => {
|
||||
onSuccess: async () => {
|
||||
toast.success('Version restored as the current version')
|
||||
setPreviewedVersion(undefined)
|
||||
await refetchAllOpenedFolders()
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { VersionHistory } from '@/components/interfaces/Storage/StorageExplorer/VersionHistory'
|
||||
import { customRender as render } from '@/tests/lib/custom-render'
|
||||
import { addAPIMock } from '@/tests/lib/msw'
|
||||
|
||||
const refetchAllOpenedFolders = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
vi.mock('@/state/storage-explorer', () => ({
|
||||
useStorageExplorerStateSnapshot: () => ({ refetchAllOpenedFolders }),
|
||||
}))
|
||||
// The versions query is platform-only, and `IS_PLATFORM` is false under vitest.
|
||||
vi.mock('@/lib/constants', async (importOriginal) => {
|
||||
const actual = await importOriginal<Record<string, unknown>>()
|
||||
return { ...actual, IS_PLATFORM: true }
|
||||
})
|
||||
|
||||
const VERSIONS = [
|
||||
{
|
||||
id: 'obj-1',
|
||||
name: 'photo.png',
|
||||
version: 'v-current',
|
||||
created_at: '2024-01-02T00:00:00Z',
|
||||
updated_at: '2024-01-02T00:00:00Z',
|
||||
last_accessed_at: null,
|
||||
archived_at: null,
|
||||
is_delete_marker: false,
|
||||
metadata: { size: 200, mimetype: 'image/png' },
|
||||
},
|
||||
{
|
||||
id: 'obj-1',
|
||||
name: 'photo.png',
|
||||
version: 'v-older',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
last_accessed_at: null,
|
||||
// Anything with an `archived_at` is a noncurrent version, which is what can be restored.
|
||||
archived_at: '2024-01-02T00:00:00Z',
|
||||
is_delete_marker: false,
|
||||
metadata: { size: 100, mimetype: 'image/png' },
|
||||
},
|
||||
]
|
||||
|
||||
const renderHistory = () =>
|
||||
render(
|
||||
<VersionHistory
|
||||
projectRef="abcdef"
|
||||
bucketId="my-bucket"
|
||||
objectName="photo.png"
|
||||
path="images/photo.png"
|
||||
versioningState="enabled"
|
||||
lifecyclePolicy={{ expiryDays: null, maxVersions: null }}
|
||||
expirationMode="and"
|
||||
clearPreview={vi.fn()}
|
||||
onEditBucket={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
describe('VersionHistory', () => {
|
||||
it('refreshes the listing after a restore, so the row stops showing the old size', async () => {
|
||||
addAPIMock({
|
||||
method: 'post',
|
||||
path: '/platform/storage/:ref/buckets/:id/objects/list',
|
||||
response: () => Response.json(VERSIONS),
|
||||
})
|
||||
addAPIMock({
|
||||
method: 'post',
|
||||
path: '/platform/storage/:ref/buckets/:id/objects/move',
|
||||
response: () => Response.json({ message: 'ok' }),
|
||||
})
|
||||
|
||||
renderHistory()
|
||||
|
||||
await userEvent.click(await screen.findByRole('button', { name: /Actions for version/ }))
|
||||
await userEvent.click(await screen.findByText('Restore as current'))
|
||||
|
||||
// A restore changes the object's size, type and modified date, and the row
|
||||
// showing them lives in the explorer's state rather than a React Query cache.
|
||||
await waitFor(() => expect(refetchAllOpenedFolders).toHaveBeenCalled())
|
||||
})
|
||||
})
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
type ObjectVersion,
|
||||
} from '@/data/storage/versioning/object-versions-query'
|
||||
import { formatBytes } from '@/lib/helpers'
|
||||
import { useStorageExplorerStateSnapshot } from '@/state/storage-explorer'
|
||||
|
||||
/** Version IDs are long opaque strings; show enough to tell two rows apart. */
|
||||
export const shortVersion = (versionId: string) => `${versionId.slice(0, 6)}…${versionId.slice(-2)}`
|
||||
@@ -161,10 +162,17 @@ export const VersionHistory = ({
|
||||
isSuccess,
|
||||
} = useQuery(objectVersionsQueryOptions({ projectRef, bucketId, path, lifecyclePolicy }))
|
||||
|
||||
const { refetchAllOpenedFolders } = useStorageExplorerStateSnapshot()
|
||||
|
||||
const [versionToDelete, setVersionToDelete] = useState<ObjectVersion>()
|
||||
|
||||
const { mutate: restoreVersion, isPending: isRestoring } = useObjectVersionRestoreMutation({
|
||||
onSuccess: () => toast.success('Version restored as the current version'),
|
||||
onSuccess: async () => {
|
||||
toast.success('Version restored as the current version')
|
||||
// The object's size, type and modified date all change with it, and the row
|
||||
// showing them is the explorer's own state.
|
||||
await refetchAllOpenedFolders()
|
||||
},
|
||||
})
|
||||
|
||||
const { mutate: deleteVersion, isPending: isDeleting } = useObjectVersionDeleteMutation({
|
||||
|
||||
Reference in new issue
Block a user