[backport cloud/1.43] fix: avoid false missing media after shared asset import (#12363)

This commit is contained in:
jaeone94
2026-05-20 20:20:32 +09:00
committed by GitHub
parent 41b258e023
commit 8df5cdcca8
4 changed files with 88 additions and 32 deletions

View File

@@ -222,7 +222,7 @@ describe('useSharedWorkflowUrlLoader', () => {
expect(mockHideTemplateSelector).not.toHaveBeenCalled()
})
it('calls import when non-owned assets exist and user confirms', async () => {
it('imports non-owned assets before loading graph when user confirms', async () => {
mockQueryParams = { share: 'share-id-1' }
const payload = makePayload({
assets: [
@@ -242,9 +242,13 @@ describe('useSharedWorkflowUrlLoader', () => {
})
const { loadSharedWorkflowFromUrl } = useSharedWorkflowUrlLoader()
await loadSharedWorkflowFromUrl()
const loaded = await loadSharedWorkflowFromUrl()
expect(loaded).toBe('loaded')
expect(mockImportPublishedAssets).toHaveBeenCalledWith(['a1'])
expect(mockImportPublishedAssets.mock.invocationCallOrder[0]).toBeLessThan(
mockLoadGraphData.mock.invocationCallOrder[0]
)
})
it('does not call import when user chooses open-only', async () => {
@@ -309,6 +313,13 @@ describe('useSharedWorkflowUrlLoader', () => {
const loaded = await loadSharedWorkflowFromUrl()
expect(loaded).toBe('loaded-without-assets')
expect(mockLoadGraphData).toHaveBeenCalledWith(
{ nodes: [] },
true,
true,
'Test Workflow',
{ openSource: 'shared_url' }
)
expect(mockToastAdd).toHaveBeenCalledWith(
expect.objectContaining({
severity: 'error',
@@ -317,6 +328,37 @@ describe('useSharedWorkflowUrlLoader', () => {
)
})
it('clears share intent when graph load fails after importing assets', async () => {
mockQueryParams = { share: 'share-id-1', tab: 'assets' }
const payload = makePayload({
assets: [
{
id: 'a1',
name: 'img.png',
preview_url: '',
storage_url: '',
model: false,
public: false,
in_library: false
}
]
})
mockShowLayoutDialog.mockImplementation(() => {
resolveDialogWithConfirm(payload)
})
mockLoadGraphData.mockRejectedValue(new Error('Graph load failed'))
const { loadSharedWorkflowFromUrl } = useSharedWorkflowUrlLoader()
const loaded = await loadSharedWorkflowFromUrl()
expect(loaded).toBe('failed')
expect(mockImportPublishedAssets).toHaveBeenCalledWith(['a1'])
expect(mockRouterReplace).toHaveBeenCalledWith({ query: { tab: 'assets' } })
expect(preservedQueryMocks.clearPreservedQuery).toHaveBeenCalledWith(
'share'
)
})
it('filters out in_library assets before importing', async () => {
mockQueryParams = { share: 'share-id-1' }
const payload = makePayload({

View File

@@ -63,6 +63,11 @@ export function useSharedWorkflowUrlLoader() {
void router.replace({ query: newQuery })
}
function clearShareIntent() {
cleanupUrlParams()
clearPreservedQuery(SHARE_NAMESPACE)
}
function showOpenSharedWorkflowDialog(
shareId: string
): Promise<DialogResult> {
@@ -108,8 +113,7 @@ export function useSharedWorkflowUrlLoader() {
}
if (typeof shareParam !== 'string') {
cleanupUrlParams()
clearPreservedQuery(SHARE_NAMESPACE)
clearShareIntent()
return 'not-present'
}
@@ -122,16 +126,14 @@ export function useSharedWorkflowUrlLoader() {
summary: t('g.error'),
detail: t('shareWorkflow.loadFailed')
})
cleanupUrlParams()
clearPreservedQuery(SHARE_NAMESPACE)
clearShareIntent()
return 'failed'
}
const result = await showOpenSharedWorkflowDialog(shareParam)
if (result.action === 'cancel') {
cleanupUrlParams()
clearPreservedQuery(SHARE_NAMESPACE)
clearShareIntent()
return 'cancelled'
}
@@ -140,6 +142,26 @@ export function useSharedWorkflowUrlLoader() {
const { payload } = result
const workflowName = payload.name || t('openSharedWorkflow.dialogTitle')
const nonOwnedAssets = payload.assets.filter((a) => !a.in_library)
let importFailed = false
if (result.action === 'copy-and-open' && nonOwnedAssets.length > 0) {
try {
await workflowShareService.importPublishedAssets(
nonOwnedAssets.map((a) => a.id)
)
} catch (importError) {
importFailed = true
console.error(
'[useSharedWorkflowUrlLoader] Failed to import assets:',
importError
)
toast.add({
severity: 'error',
summary: t('g.error'),
detail: t('openSharedWorkflow.importFailed')
})
}
}
try {
await app.loadGraphData(payload.workflowJson, true, true, workflowName, {
@@ -155,33 +177,12 @@ export function useSharedWorkflowUrlLoader() {
summary: t('g.error'),
detail: t('shareWorkflow.loadFailed')
})
clearShareIntent()
return 'failed'
}
if (result.action === 'copy-and-open' && nonOwnedAssets.length > 0) {
try {
await workflowShareService.importPublishedAssets(
nonOwnedAssets.map((a) => a.id)
)
} catch (importError) {
console.error(
'[useSharedWorkflowUrlLoader] Failed to import assets:',
importError
)
toast.add({
severity: 'error',
summary: t('g.error'),
detail: t('openSharedWorkflow.importFailed')
})
cleanupUrlParams()
clearPreservedQuery(SHARE_NAMESPACE)
return 'loaded-without-assets'
}
}
cleanupUrlParams()
clearPreservedQuery(SHARE_NAMESPACE)
return 'loaded'
clearShareIntent()
return importFailed ? 'loaded-without-assets' : 'loaded'
}
return {

View File

@@ -14,6 +14,7 @@ vi.mock('@/scripts/app', () => ({
const mockGetShareableAssets = vi.fn()
const mockFetchApi = vi.fn()
const mockInvalidateInputAssetsIncludingPublic = vi.hoisted(() => vi.fn())
vi.mock(
'@/platform/workflow/validation/schemas/workflowSchema',
@@ -32,6 +33,13 @@ vi.mock('@/scripts/api', () => ({
}
}))
vi.mock('@/platform/assets/services/assetService', () => ({
assetService: {
invalidateInputAssetsIncludingPublic:
mockInvalidateInputAssetsIncludingPublic
}
}))
describe(useWorkflowShareService, () => {
const mockShareableAssets: AssetInfo[] = [
{
@@ -345,6 +353,7 @@ describe(useWorkflowShareService, () => {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ published_asset_ids: ['pa-1', 'pa-2'] })
})
expect(mockInvalidateInputAssetsIncludingPublic).toHaveBeenCalledTimes(1)
})
it('throws when import request fails', async () => {
@@ -355,6 +364,7 @@ describe(useWorkflowShareService, () => {
await expect(service.importPublishedAssets(['bad-id'])).rejects.toThrow(
'Failed to import assets: 400'
)
expect(mockInvalidateInputAssetsIncludingPublic).not.toHaveBeenCalled()
})
it('throws when shared workflow payload is invalid', async () => {

View File

@@ -4,6 +4,7 @@ import type {
WorkflowPublishResult,
WorkflowPublishStatus
} from '@/platform/workflow/sharing/types/shareTypes'
import { assetService } from '@/platform/assets/services/assetService'
import type { ThumbnailType } from '@/platform/workflow/sharing/types/comfyHubTypes'
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
import { validateComfyWorkflow } from '@/platform/workflow/validation/schemas/workflowSchema'
@@ -265,6 +266,8 @@ export function useWorkflowShareService() {
if (!response.ok) {
throw new Error(`Failed to import assets: ${response.status}`)
}
assetService.invalidateInputAssetsIncludingPublic()
}
return {