Refactor Media Manager log to use streaming and card UI
- **UI Overhaul**: Moved the activity log to a dedicated, expandable card at the bottom of the Media Manager modal.
- **Styling**: Updated the log card to match the application's light theme using CSS variables (`--surface`, `--text`).
- **Log Streaming**: Replaced batch logging with real-time streaming via `CacheService` and `pollJobLogs`.
- **Session Resumption**: Implemented logic to resume log polling for active jobs upon page reload.
- **Fixes**:
- Exposed `pollJobLogs` in `global.ts` to fix "Script function not found" error.
- Updated `mediaHandlers.test.ts` with `CacheService` mocks and new signatures.
- Removed legacy auto-hide/toggle logic for the log.
This commit is contained in:
@ -48,6 +48,15 @@ describe("MediaService Robust Sync", () => {
|
||||
removeFile: (f) => {}
|
||||
})
|
||||
} as any
|
||||
|
||||
// Mock CacheService for log streaming
|
||||
global.CacheService = {
|
||||
getDocumentCache: () => ({
|
||||
get: (key) => null,
|
||||
put: (k, v, t) => {},
|
||||
remove: (k) => {}
|
||||
})
|
||||
} as any
|
||||
})
|
||||
|
||||
test("Strict Matching: Only matches via property, ignores filename", () => {
|
||||
|
||||
@ -24,11 +24,38 @@ export class MediaService {
|
||||
|
||||
|
||||
|
||||
private logToCache(jobId: string, message: string) {
|
||||
if (!jobId) return;
|
||||
try {
|
||||
const cache = CacheService.getDocumentCache();
|
||||
const key = `job_logs_${jobId}`;
|
||||
const existing = cache.get(key);
|
||||
let logs = existing ? JSON.parse(existing) : [];
|
||||
logs.push(message);
|
||||
// Expire in 10 minutes (plenty for a save operation)
|
||||
cache.put(key, JSON.stringify(logs), 600);
|
||||
} catch (e) {
|
||||
console.warn("Retrying log to cache failed slightly", e);
|
||||
}
|
||||
}
|
||||
|
||||
getDiagnostics(sku: string, shopifyProductId: string) {
|
||||
const results = {
|
||||
drive: { status: 'pending', fileCount: 0, folderId: null, folderUrl: null, error: null },
|
||||
shopify: { status: 'pending', mediaCount: 0, id: shopifyProductId, adminUrl: null, error: null },
|
||||
matching: { status: 'pending', error: null }
|
||||
matching: { status: 'pending', error: null },
|
||||
activeJobId: null
|
||||
}
|
||||
|
||||
// Check for Active Job
|
||||
try {
|
||||
const cache = CacheService.getDocumentCache();
|
||||
const activeJobId = cache.get(`active_job_${sku}`);
|
||||
if (activeJobId) {
|
||||
results.activeJobId = activeJobId;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to check active job", e);
|
||||
}
|
||||
|
||||
// 1. Unsafe Drive Check
|
||||
@ -348,10 +375,24 @@ export class MediaService {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
processMediaChanges(sku: string, finalState: any[], shopifyProductId: string): string[] {
|
||||
processMediaChanges(sku: string, finalState: any[], shopifyProductId: string, jobId: string | null = null): string[] {
|
||||
const logs: string[] = []
|
||||
logs.push(`Starting processing for SKU ${sku}`)
|
||||
console.log(`MediaService: Processing changes for SKU ${sku}`)
|
||||
|
||||
// Helper to log to both return array and cache
|
||||
const log = (msg: string) => {
|
||||
logs.push(msg);
|
||||
console.log(msg);
|
||||
if (jobId) this.logToCache(jobId, msg);
|
||||
}
|
||||
|
||||
log(`Starting processing for SKU ${sku}`)
|
||||
|
||||
// Register Job
|
||||
if (jobId) {
|
||||
try {
|
||||
CacheService.getDocumentCache().put(`active_job_${sku}`, jobId, 600);
|
||||
} catch(e) { console.warn("Failed to register active job", e); }
|
||||
}
|
||||
|
||||
// 0. Service Availability Check & Local Capture (Fixing 'undefined' context issues)
|
||||
const shopifySvc = this.shopifyMediaService
|
||||
@ -366,15 +407,14 @@ export class MediaService {
|
||||
|
||||
// 2. Process Deletions (Orphans not in final state are removed from Shopify)
|
||||
const toDelete = currentState.filter(c => !finalIds.has(c.id))
|
||||
if (toDelete.length === 0) logs.push("No deletions found.")
|
||||
if (toDelete.length === 0) log("No deletions found.")
|
||||
|
||||
toDelete.forEach(item => {
|
||||
const msg = `Deleting item: ${item.filename}`
|
||||
logs.push(msg)
|
||||
console.log(msg)
|
||||
log(msg)
|
||||
if (item.shopifyId) {
|
||||
shopifySvc.productDeleteMedia(shopifyProductId, item.shopifyId)
|
||||
logs.push(`- Deleted from Shopify (${item.shopifyId})`)
|
||||
log(`- Deleted from Shopify (${item.shopifyId})`)
|
||||
}
|
||||
if (item.driveId) {
|
||||
// Check for Associated Sidecar Thumbs (Request #2)
|
||||
@ -389,14 +429,14 @@ export class MediaService {
|
||||
const props = driveSvc.getFileProperties(item.driveId);
|
||||
if (props && props['custom_thumbnail_id']) {
|
||||
driveSvc.trashFile(props['custom_thumbnail_id']);
|
||||
logs.push(`- Trashed associated Sidecar Thumbnail (${props['custom_thumbnail_id']})`);
|
||||
log(`- Trashed associated Sidecar Thumbnail (${props['custom_thumbnail_id']})`);
|
||||
}
|
||||
} catch (ignore) {
|
||||
// If file already gone or other error
|
||||
}
|
||||
|
||||
driveSvc.trashFile(item.driveId)
|
||||
logs.push(`- Trashed in Drive (${item.driveId})`)
|
||||
log(`- Trashed in Drive (${item.driveId})`)
|
||||
}
|
||||
})
|
||||
|
||||
@ -406,8 +446,7 @@ export class MediaService {
|
||||
finalState.forEach(item => {
|
||||
if (item.source === 'shopify_only' && item.shopifyId) {
|
||||
const msg = `Adopting Orphan: ${item.filename}`
|
||||
logs.push(msg)
|
||||
console.log(msg)
|
||||
log(msg)
|
||||
|
||||
try {
|
||||
// Download
|
||||
@ -433,9 +472,9 @@ export class MediaService {
|
||||
// Update item refs for subsequent steps
|
||||
item.driveId = file.getId()
|
||||
item.source = 'synced'
|
||||
logs.push(`- Adopted to Drive (${file.getId()})`)
|
||||
log(`- Adopted to Drive (${file.getId()})`)
|
||||
} catch (e) {
|
||||
logs.push(`- Failed to adopt ${item.filename}: ${e}`)
|
||||
log(`- Failed to adopt ${item.filename}: ${e}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -444,7 +483,7 @@ export class MediaService {
|
||||
const toUpload = finalState.filter(item => item.source === 'drive_only' && item.driveId)
|
||||
if (toUpload.length > 0) {
|
||||
const msg = `Uploading ${toUpload.length} new items from Drive`
|
||||
logs.push(msg)
|
||||
log(msg)
|
||||
const uploads = toUpload.map(item => {
|
||||
const f = driveSvc.getFileById(item.driveId)
|
||||
return {
|
||||
@ -471,7 +510,7 @@ export class MediaService {
|
||||
|
||||
if (stagedResp.userErrors && stagedResp.userErrors.length > 0) {
|
||||
console.error("[MediaService] stagedUploadsCreate Errors:", JSON.stringify(stagedResp.userErrors))
|
||||
logs.push(`- Upload preparation failed: ${stagedResp.userErrors.map(e => e.message).join(', ')}`)
|
||||
log(`- Upload preparation failed: ${stagedResp.userErrors.map(e => e.message).join(', ')}`)
|
||||
}
|
||||
|
||||
const targets = stagedResp.stagedTargets
|
||||
@ -480,7 +519,7 @@ export class MediaService {
|
||||
uploads.forEach((u, i) => {
|
||||
const target = targets[i]
|
||||
if (!target || !target.url) {
|
||||
logs.push(`- Failed to get upload target for ${u.filename}: Invalid target`)
|
||||
log(`- Failed to get upload target for ${u.filename}: Invalid target`)
|
||||
console.warn(`[MediaService] Missing target URL for ${u.filename}. Target:`, JSON.stringify(target))
|
||||
return
|
||||
}
|
||||
@ -507,7 +546,7 @@ export class MediaService {
|
||||
driveSvc.updateFileProperties(originalItem.driveId, { shopify_media_id: m.id })
|
||||
originalItem.shopifyId = m.id
|
||||
originalItem.source = 'synced'
|
||||
logs.push(`- Created in Shopify (${m.id}) and linked`)
|
||||
log(`- Created in Shopify (${m.id}) and linked`)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -541,7 +580,7 @@ export class MediaService {
|
||||
const timestamp = new Date().getTime()
|
||||
const newName = `${sku}_${timestamp}.${ext}`
|
||||
driveSvc.renameFile(item.driveId, newName)
|
||||
logs.push(`- Renamed ${currentName} -> ${newName} (Non-conforming)`)
|
||||
log(`- Renamed ${currentName} -> ${newName} (Non-conforming)`)
|
||||
}
|
||||
|
||||
// C. Prepare Shopify Reorder
|
||||
@ -550,17 +589,25 @@ export class MediaService {
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
logs.push(`- Error updating ${item.filename}: ${e}`)
|
||||
log(`- Error updating ${item.filename}: ${e}`)
|
||||
}
|
||||
})
|
||||
|
||||
// 6. Execute Shopify Reorder
|
||||
if (reorderMoves.length > 0) {
|
||||
shopifySvc.productReorderMedia(shopifyProductId, reorderMoves)
|
||||
logs.push("Reordered media in Shopify.")
|
||||
log("Reordered media in Shopify.")
|
||||
}
|
||||
|
||||
log("Processing Complete.")
|
||||
|
||||
// Clear Job (Success)
|
||||
if (jobId) {
|
||||
try {
|
||||
CacheService.getDocumentCache().remove(`active_job_${sku}`);
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
logs.push("Processing Complete.")
|
||||
return logs
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user