Skip to content

Commit 6f4b272

Browse files
jefferytoDanRibbensJarrodMFlesch
authored
fix(ui): ensure up-to-date upload preview thumbnails in admin panel (#15029)
### What? Ensure the preview thumbnails in list view table cells and edit view upload fields are up-to-date with the actual uploaded/edited images. ### Why? A thumbnail for an uploaded image may be shown in several places: * List view of the upload collection * List view and edit view of other collections that have an upload field linking to the upload collection After editing an image, the thumbnail of the unedited version may still be shown in these places due to browser caching. ### How? This sets the `imageCacheTag` property for the `Thumbnail` component to force the component to load the latest thumbnail image. Fixes #15027 --------- Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com> Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
1 parent 9aeb843 commit 6f4b272

6 files changed

Lines changed: 54 additions & 24 deletions

File tree

packages/ui/src/elements/Table/DefaultCell/fields/File/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export const FileCell: React.FC<FileCellProps> = ({
5353
filename,
5454
}}
5555
fileSrc={fileSrc}
56+
imageCacheTag={collectionConfig?.upload?.cacheTags && rowData?.updatedAt}
5657
size="small"
5758
uploadConfig={collectionConfig?.upload}
5859
/>

packages/ui/src/fields/Upload/HasMany/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ export function UploadComponentHasMany(props: Props) {
152152
showCollectionSlug={showCollectionSlug}
153153
src={src}
154154
thumbnailSrc={thumbnailSrc}
155+
updatedAt={value.updatedAt}
155156
withMeta={false}
156157
x={value?.width as number}
157158
y={value?.height as number}

packages/ui/src/fields/Upload/HasOne/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export function UploadComponentHasOne(props: Props) {
8686
showCollectionSlug={showCollectionSlug}
8787
src={src}
8888
thumbnailSrc={thumbnailSrc}
89+
updatedAt={value.updatedAt}
8990
x={value?.width as number}
9091
y={value?.height as number}
9192
/>

packages/ui/src/fields/Upload/RelationshipContent/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Props = {
3434
readonly showCollectionSlug?: boolean
3535
readonly src: string
3636
readonly thumbnailSrc: string
37+
readonly updatedAt?: string
3738
readonly withMeta?: boolean
3839
readonly x?: number
3940
readonly y?: number
@@ -55,6 +56,7 @@ export function RelationshipContent(props: Props) {
5556
showCollectionSlug = false,
5657
src,
5758
thumbnailSrc,
59+
updatedAt,
5860
withMeta = true,
5961
x,
6062
y,
@@ -106,6 +108,7 @@ export function RelationshipContent(props: Props) {
106108
className={`${baseClass}__thumbnail`}
107109
filename={filename}
108110
fileSrc={thumbnailSrc}
111+
imageCacheTag={collectionConfig?.upload?.cacheTags && updatedAt}
109112
size="small"
110113
/>
111114
)}

test/fields/collections/Upload/e2e.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ describe('Upload', () => {
175175

176176
await expect(
177177
page.locator('.field-type.upload .upload-relationship-details img'),
178-
).toHaveAttribute('src', '/api/uploads/file/payload-1.png')
178+
).toHaveAttribute('src', /\/api\/uploads\/file\/payload-1\.png(\?.*)?$/)
179179
await saveDocAndAssert(page)
180180
})
181181

@@ -214,7 +214,7 @@ describe('Upload', () => {
214214
).toContainText('payload-1.png')
215215
await expect(
216216
page.locator('.field-type.upload .upload-relationship-details img'),
217-
).toHaveAttribute('src', '/api/uploads/file/payload-1.png')
217+
).toHaveAttribute('src', /\/api\/uploads\/file\/payload-1\.png(\?.*)?$/)
218218
await saveDocAndAssert(page)
219219
})
220220

test/uploads/e2e.spec.ts

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ import { startMockCorsServer } from './startMockCorsServer.js'
5959
const filename = fileURLToPath(import.meta.url)
6060
const dirname = path.dirname(filename)
6161

62+
/**
63+
* Regex matcher for date cache tags.
64+
*
65+
* @example it will match `?2022-01-01T00%3A00%3A00.000Z` (`?2022-01-01T00:00:00.000Z` encoded)
66+
*/
67+
const cacheTagPattern = /\?\d{4}-\d{2}-\d{2}T\d{2}%3A\d{2}%3A\d{2}\.\d{3}Z/
68+
6269
const { afterAll, beforeAll, beforeEach, describe } = test
6370

6471
let payload: PayloadTestSDK<Config>
@@ -640,7 +647,7 @@ describe('Uploads', () => {
640647
const genericUploadImage = page.locator('tr.row-1 .thumbnail img')
641648
await expect(genericUploadImage).toHaveAttribute(
642649
'src',
643-
'https://raw.githubusercontent.com/payloadcms/website/refs/heads/main/public/images/universal-truth.jpg',
650+
/^https:\/\/raw\.githubusercontent\.com\/payloadcms\/website\/refs\/heads\/main\/public\/images\/universal-truth\.jpg(\?.*)?$/,
644651
)
645652
})
646653

@@ -671,17 +678,14 @@ describe('Uploads', () => {
671678
await page.goto(mediaWithoutCacheTagsSlugURL.edit(imageDoc!.id))
672679

673680
const genericUploadImage = page.locator('.file-details .thumbnail img')
681+
await expect(genericUploadImage).not.toHaveAttribute('src', cacheTagPattern)
682+
})
674683

675-
const src = await genericUploadImage.getAttribute('src')
676-
677-
/**
678-
* Regex matcher for date cache tags.
679-
*
680-
* @example it will match `?2022-01-01T00%3A00%3A00.000Z` (`?2022-01-01T00:00:00.000Z` encoded)
681-
*/
682-
const cacheTagPattern = /\?\d{4}-\d{2}-\d{2}T\d{2}%3A\d{2}%3A\d{2}\.\d{3}Z/
684+
test('should render adminThumbnail without the additional cache tag in upload collection list', async () => {
685+
await page.goto(mediaWithoutCacheTagsSlugURL.list)
683686

684-
expect(src).not.toMatch(cacheTagPattern)
687+
const genericUploadImage = page.locator('tr.row-1 .thumbnail img')
688+
await expect(genericUploadImage).not.toHaveAttribute('src', cacheTagPattern)
685689
})
686690

687691
test('should render adminThumbnail with the cache tag by default', async () => {
@@ -701,17 +705,37 @@ describe('Uploads', () => {
701705
await page.goto(adminThumbnailFunctionURL.edit(imageDoc!.id))
702706

703707
const genericUploadImage = page.locator('.file-details .thumbnail img')
708+
await expect(genericUploadImage).toHaveAttribute('src', cacheTagPattern)
709+
})
704710

705-
const src = await genericUploadImage.getAttribute('src')
711+
test('should render adminThumbnail with the cache tag in upload collection list by default', async () => {
712+
await page.goto(adminThumbnailFunctionURL.list)
713+
714+
const genericUploadImage = page.locator('tr.row-1 .thumbnail img')
715+
await expect(genericUploadImage).toHaveAttribute('src', cacheTagPattern)
716+
})
717+
718+
test('should render adminThumbnail with the cache tag in relation list by default', async () => {
719+
await page.goto(relationPreviewURL.list)
720+
721+
const relationPreview1 = page.locator('.cell-imageWithPreview1 img')
722+
await expect(relationPreview1).toHaveAttribute('src', cacheTagPattern)
723+
})
724+
725+
test('should render adminThumbnail with the cache tag in upload field by default', async () => {
726+
const relationPreviewDoc = (
727+
await payload.find({
728+
collection: relationPreviewSlug,
729+
depth: 0,
730+
limit: 1,
731+
pagination: false,
732+
})
733+
).docs[0]
706734

707-
/**
708-
* Regex matcher for date cache tags.
709-
*
710-
* @example it will match `?2022-01-01T00%3A00%3A00.000Z` (`?2022-01-01T00:00:00.000Z` encoded)
711-
*/
712-
const cacheTagPattern = /\?\d{4}-\d{2}-\d{2}T\d{2}%3A\d{2}%3A\d{2}\.\d{3}Z/
735+
await page.goto(relationPreviewURL.edit(relationPreviewDoc!.id))
713736

714-
expect(src).toMatch(cacheTagPattern)
737+
const relationPreview1 = page.locator('#field-imageWithPreview1 .thumbnail img')
738+
await expect(relationPreview1).toHaveAttribute('src', cacheTagPattern)
715739
})
716740

717741
test('should render adminThumbnail when using a specific size', async () => {
@@ -1863,7 +1887,7 @@ describe('Uploads', () => {
18631887
const thumbnail = page.locator('#field-withAdminThumbnail div.thumbnail > img')
18641888
await expect(thumbnail).toHaveAttribute(
18651889
'src',
1866-
'https://raw.githubusercontent.com/payloadcms/website/refs/heads/main/public/images/universal-truth.jpg',
1890+
/^https:\/\/raw\.githubusercontent\.com\/payloadcms\/website\/refs\/heads\/main\/public\/images\/universal-truth\.jpg(\?.*)?$/,
18671891
)
18681892
})
18691893

@@ -1875,7 +1899,7 @@ describe('Uploads', () => {
18751899
const thumbnail = page.locator('#field-withinRange div.thumbnail > img')
18761900
await expect(thumbnail).toHaveAttribute(
18771901
'src',
1878-
/\/api\/enlarge\/file\/test-image-180x50\.jpg$/,
1902+
/\/api\/enlarge\/file\/test-image-180x50\.jpg(\?.*)?$/,
18791903
)
18801904
})
18811905

@@ -1887,7 +1911,7 @@ describe('Uploads', () => {
18871911
const thumbnail = page.locator('#field-nextSmallestOutOfRange div.thumbnail > img')
18881912
await expect(thumbnail).toHaveAttribute(
18891913
'src',
1890-
/\/api\/focal-only\/file\/test-image-400x300\.jpg$/,
1914+
/\/api\/focal-only\/file\/test-image-400x300\.jpg(\?.*)?$/,
18911915
)
18921916
})
18931917

@@ -1897,7 +1921,7 @@ describe('Uploads', () => {
18971921
await page.setInputFiles('input[type="file"]', path.join(dirname, 'small.png'))
18981922
await page.locator('dialog button#action-save').click()
18991923
const thumbnail = page.locator('#field-original div.thumbnail > img')
1900-
await expect(thumbnail).toHaveAttribute('src', /\/api\/focal-only\/file\/small\.png$/)
1924+
await expect(thumbnail).toHaveAttribute('src', /\/api\/focal-only\/file\/small\.png(\?.*)?$/)
19011925
})
19021926
})
19031927

0 commit comments

Comments
 (0)