feat: image variants cache panel
This commit is contained in:
1 parent
951fef18ba
commit
d768668014
53 files changed
+5809
-46
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { initializeLocale } from '@/i18n'
|
||||
import type { ActivityItem } from '@/lib/activity'
|
||||
import { detailLine, sentenceText } from '../activitySentence'
|
||||
|
||||
function purgeItem(overrides: Partial<ActivityItem>): ActivityItem {
|
||||
return {
|
||||
id: 1,
|
||||
created_at: '2026-10-03T12:00:00Z',
|
||||
action: 'media.variant_purge_all',
|
||||
category: 'media',
|
||||
actor: { type: 'user', id: 3, name: 'Marta Alves' },
|
||||
target: null,
|
||||
outcome: 'success',
|
||||
failure_reason: null,
|
||||
security: false,
|
||||
source: 'web',
|
||||
ip: null,
|
||||
user_agent: null,
|
||||
params: {},
|
||||
changes: [],
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
const hero = { type: 'image_variant', id: 'hero', name: 'hero', exists: true }
|
||||
const wall = { type: 'image', id: '1', name: 'north-wall.jpg', exists: true }
|
||||
|
||||
const rows: [string, Partial<ActivityItem>, string, string | null][] = [
|
||||
[
|
||||
'media.variant_purge_all success',
|
||||
{ params: { purged_files: 1284, purged_bytes: 222298112 } },
|
||||
'Purged all variants',
|
||||
'Removed 1,284 files (212 MB)',
|
||||
],
|
||||
['media.variant_purge_all failed', { outcome: 'failed' }, 'Failed to purge all variants', null],
|
||||
[
|
||||
'media.variant_purge_unused success',
|
||||
{
|
||||
action: 'media.variant_purge_unused',
|
||||
params: { purged_files: 312, purged_bytes: 42991616, older_than_days: 30 },
|
||||
},
|
||||
'Purged unused variants',
|
||||
'Removed 312 files (41 MB) not requested for 30 days',
|
||||
],
|
||||
[
|
||||
'media.variant_purge_unused failed',
|
||||
{ action: 'media.variant_purge_unused', outcome: 'failed' },
|
||||
'Failed to purge unused variants',
|
||||
null,
|
||||
],
|
||||
[
|
||||
'media.variant_purge success',
|
||||
{
|
||||
action: 'media.variant_purge',
|
||||
target: hero,
|
||||
params: { purged_files: 244, purged_bytes: 88080384 },
|
||||
},
|
||||
'Purged the cache of variant “hero”',
|
||||
'Removed 244 files (84 MB)',
|
||||
],
|
||||
[
|
||||
'media.variant_purge failed',
|
||||
{ action: 'media.variant_purge', target: hero, outcome: 'failed' },
|
||||
'Failed to purge the cache of variant “hero”',
|
||||
null,
|
||||
],
|
||||
[
|
||||
'media.variant_purge_image success',
|
||||
{
|
||||
action: 'media.variant_purge_image',
|
||||
target: wall,
|
||||
params: { purged_files: 6, purged_bytes: 6082765 },
|
||||
},
|
||||
'Purged the variants of north-wall.jpg',
|
||||
'Removed 6 files (5.8 MB)',
|
||||
],
|
||||
]
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
initializeLocale()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear()
|
||||
initializeLocale()
|
||||
})
|
||||
|
||||
describe('The Activity screen words each cache purge scope', () => {
|
||||
it.each(rows)('%s', (_, overrides, sentence, detail) => {
|
||||
const subject = purgeItem(overrides)
|
||||
|
||||
expect(sentenceText(subject)).toBe(sentence)
|
||||
expect(detailLine(subject)).toBe(detail)
|
||||
})
|
||||
|
||||
it('A partial purge adds the failed count to the detail line', () => {
|
||||
const subject = purgeItem({
|
||||
params: { purged_files: 640, purged_bytes: 111149056, failed_files: 644 },
|
||||
})
|
||||
|
||||
expect(detailLine(subject)).toBe('Removed 640 files (106 MB) · 644 failed')
|
||||
})
|
||||
|
||||
it('The singular reads 1 file', () => {
|
||||
const subject = purgeItem({ params: { purged_files: 1, purged_bytes: 2048 } })
|
||||
|
||||
expect(detailLine(subject)).toBe('Removed 1 file (2 KB)')
|
||||
})
|
||||
})
|
||||
@@ -231,6 +231,9 @@ describe('The catalogue is complete in both languages', () => {
|
||||
'media.merge',
|
||||
'media.purge',
|
||||
'media.variant_generate_all',
|
||||
'media.variant_purge',
|
||||
'media.variant_purge_all',
|
||||
'media.variant_purge_unused',
|
||||
'media.variant_purge_image',
|
||||
'system.media_purge',
|
||||
'user.register',
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { MessageKey } from '@/i18n/messages'
|
||||
import type { ActivityItem } from '@/lib/activity'
|
||||
import { sizeLabel } from '@/lib/activityRetention'
|
||||
import { formatSize } from '@/lib/mediaFormat'
|
||||
import { formatVariantSize } from '@/lib/variants/variantFormat'
|
||||
import { causeText } from '@/lib/variants/variantCauses'
|
||||
|
||||
export interface SentenceSegment {
|
||||
@@ -62,6 +63,18 @@ const CATALOGUE: Record<string, Template> = {
|
||||
success: 'Generated all variants of {0}',
|
||||
failed: 'Failed to generate the variants of {0}',
|
||||
},
|
||||
'media.variant_purge': {
|
||||
success: 'Purged the cache of variant “{0}”',
|
||||
failed: 'Failed to purge the cache of variant “{0}”',
|
||||
},
|
||||
'media.variant_purge_all': {
|
||||
success: 'Purged all variants',
|
||||
failed: 'Failed to purge all variants',
|
||||
},
|
||||
'media.variant_purge_unused': {
|
||||
success: 'Purged unused variants',
|
||||
failed: 'Failed to purge unused variants',
|
||||
},
|
||||
'media.variant_purge_image': {
|
||||
success: 'Purged the variants of {0}',
|
||||
failed: 'Failed to purge the variants of {0}',
|
||||
@@ -262,7 +275,42 @@ function purgeImageDetail(params: Record<string, unknown>) {
|
||||
: t('Removed {0} files ({1})', [formatNumber(files), size])
|
||||
}
|
||||
|
||||
const DEFAULT_UNUSED_DAYS = 30
|
||||
const CACHE_PURGE_ACTIONS = [
|
||||
'media.variant_purge',
|
||||
'media.variant_purge_all',
|
||||
'media.variant_purge_unused',
|
||||
]
|
||||
|
||||
function removedFiles(files: number, size: string, days: number | null) {
|
||||
if (days === null) {
|
||||
return files === 1
|
||||
? t('Removed 1 file ({0})', [size])
|
||||
: t('Removed {0} files ({1})', [formatNumber(files), size])
|
||||
}
|
||||
return files === 1
|
||||
? t('Removed 1 file ({0}) not requested for {1} days', [size, days])
|
||||
: t('Removed {0} files ({1}) not requested for {2} days', [formatNumber(files), size, days])
|
||||
}
|
||||
|
||||
function cachePurgeDetail(item: ActivityItem) {
|
||||
if (item.outcome === 'failed') return null
|
||||
const params = item.params
|
||||
const unused = item.action === 'media.variant_purge_unused'
|
||||
const days = unused ? count(params.older_than_days) || DEFAULT_UNUSED_DAYS : null
|
||||
const removed = removedFiles(
|
||||
count(params.purged_files),
|
||||
formatVariantSize(count(params.purged_bytes)),
|
||||
days,
|
||||
)
|
||||
const failed = count(params.failed_files)
|
||||
return failed > 0 ? `${removed} · ${t('{0} failed', [formatNumber(failed)])}` : removed
|
||||
}
|
||||
|
||||
export function detailLine(item: ActivityItem): string | null {
|
||||
if (CACHE_PURGE_ACTIONS.includes(item.action)) {
|
||||
return cachePurgeDetail(item)
|
||||
}
|
||||
if (item.action === 'media.variant_generate_all') {
|
||||
return generateAllDetail(item.params)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { formatNumber, t } from '@/i18n'
|
||||
import { formatVariantSize } from '@/lib/variants/variantFormat'
|
||||
|
||||
const PERCENT = 100
|
||||
|
||||
export function cacheSize(bytes: number) {
|
||||
return bytes === 0 ? t('{0} MB', [0]) : formatVariantSize(bytes)
|
||||
}
|
||||
|
||||
export function filesCount(count: number) {
|
||||
return count === 1 ? t('1 file') : t('{0} files', [formatNumber(count)])
|
||||
}
|
||||
|
||||
export function filesAndSize(count: number, bytes: number) {
|
||||
return count === 1
|
||||
? t('1 file · {0}', [formatVariantSize(bytes)])
|
||||
: t('{0} files · {1}', [formatNumber(count), formatVariantSize(bytes)])
|
||||
}
|
||||
|
||||
export function variantsCount(count: number) {
|
||||
return count === 1 ? t('1 variant') : t('{0} variants', [formatNumber(count)])
|
||||
}
|
||||
|
||||
export function barPercent(bytes: number, largest: number) {
|
||||
return largest === 0 ? 0 : Math.round((bytes / largest) * PERCENT)
|
||||
}
|
||||
Reference in new issue
Block a user