diff --git a/test/core/workflow/workflow-acrobat/action-binder.test.js b/test/core/workflow/workflow-acrobat/action-binder.test.js index 7dc4a7ba4..6dc4f1526 100644 --- a/test/core/workflow/workflow-acrobat/action-binder.test.js +++ b/test/core/workflow/workflow-acrobat/action-binder.test.js @@ -2,8 +2,6 @@ import { expect } from '@esm-bundle/chai'; import sinon from 'sinon'; import ActionBinder from '../../../../unitylibs/core/workflow/workflow-acrobat/action-binder.js'; -let mockUpdateProgressBar = null; - describe('ActionBinder', () => { let actionBinder; let mockWorkflowCfg; @@ -18,12 +16,11 @@ describe('ActionBinder', () => { window.import = function mockImport(specifier) { if (specifier && typeof specifier === 'string' && specifier.includes('transition-screen.js')) { return Promise.resolve({ - default: function TransitionScreen(splashScreenEl) { - this.splashScreenEl = splashScreenEl; + default: function TransitionScreen() { this.delayedSplashLoader = () => Promise.resolve(); this.loadSplashFragment = () => Promise.resolve(); this.showSplashScreen = () => Promise.resolve(); - this.updateProgressBar = mockUpdateProgressBar || (() => Promise.resolve()); + this.updateProgressBar = () => Promise.resolve(); return this; }, }); @@ -1354,36 +1351,11 @@ describe('ActionBinder', () => { }); }); - describe('isDirectUploadVerb', () => { - beforeEach(() => { - actionBinder.workflowCfg.enabledFeatures = ['word-to-pdf']; - actionBinder.workflowCfg.targetCfg.directUploadVerbs = ['word-to-pdf']; - actionBinder.workflowCfg.targetCfg.directUploadMaxSize = 1048576; - }); - - it('should return true for configured direct upload verbs within max size', () => { - expect(actionBinder.isDirectUploadVerb(1048576)).to.be.true; - expect(actionBinder.isDirectUploadVerb(500000)).to.be.true; - }); - - it('should return false for direct upload verbs exceeding max size', () => { - expect(actionBinder.isDirectUploadVerb(1048577)).to.be.false; - }); - - it('should return false for direct upload verbs without file size', () => { - expect(actionBinder.isDirectUploadVerb()).to.be.false; - }); - - it('should return false for verbs not configured for direct upload', () => { - actionBinder.workflowCfg.enabledFeatures = ['compress-pdf']; - expect(actionBinder.isDirectUploadVerb(500000)).to.be.false; - }); - }); - describe('continueInApp', () => { let locationSpy; beforeEach(() => { + // Create a spy for location changes locationSpy = sinon.spy(); actionBinder.redirectUrl = 'https://test.com?param=value'; @@ -1414,40 +1386,6 @@ describe('ActionBinder', () => { expect(actionBinder.showTransitionScreen.called).to.be.false; expect(locationSpy.called).to.be.false; }); - - it('should log to splunk and continue when direct upload verb progress bar update throws', async () => { - const splashLayer = document.createElement('div'); - const updateProgressBar = sinon.stub().throws(new TypeError('Cannot set properties of null')); - actionBinder.transitionScreen = { - splashScreenEl: splashLayer, - updateProgressBar, - showSplashScreen: sinon.stub().resolves(), - }; - await actionBinder.runProgressBarUpdate(splashLayer); - expect(updateProgressBar.calledOnce).to.be.true; - expect(actionBinder.dispatchErrorToast.calledOnce).to.be.true; - expect(actionBinder.dispatchErrorToast.calledWith( - 'warn_update_no_progress_bar', - null, - 'Cannot set properties of null', - true, - true, - { code: 'warn_update_no_progress_bar', desc: 'Cannot set properties of null' }, - )).to.be.true; - }); - - it('should propagate error when non-tolerant verb progress bar update throws', () => { - const splashLayer = document.createElement('div'); - const updateProgressBar = sinon.stub().throws(new TypeError('Cannot set properties of null')); - actionBinder.transitionScreen = { - splashScreenEl: splashLayer, - updateProgressBar, - showSplashScreen: sinon.stub().resolves(), - }; - // The else branch in continueInApp calls updateProgressBar without error handling - expect(() => actionBinder.transitionScreen.updateProgressBar(splashLayer, 100)).to.throw(TypeError, /null/); - expect(actionBinder.dispatchErrorToast.called).to.be.false; - }); }); describe('cancelAcrobatOperation', () => { diff --git a/test/core/workflow/workflow-acrobat/upload-handler.test.js b/test/core/workflow/workflow-acrobat/upload-handler.test.js index c230736d9..3db58230a 100644 --- a/test/core/workflow/workflow-acrobat/upload-handler.test.js +++ b/test/core/workflow/workflow-acrobat/upload-handler.test.js @@ -125,7 +125,6 @@ describe('UploadHandler', () => { operations: [], transitionScreen: { splashScreenEl: document.createElement('div') }, initActionListeners: sinon.stub(), - isDirectUploadVerb: sinon.stub().returns(false), dispatchAnalyticsEvent: sinon.stub(), dispatchErrorToast: sinon.stub().resolves(), getAbortSignal: sinon.stub().returns({ aborted: false }), @@ -510,22 +509,6 @@ describe('UploadHandler', () => { expect(mockActionBinder.setIsUploading.calledWith(true)).to.be.true; }); - it('should take the direct upload path and return early on success', async () => { - mockActionBinder.isDirectUploadVerb.returns(true); - uploadHandler.directUploadSingleFile = sinon.stub().resolves(true); - await uploadHandler.uploadSingleFile(file, fileData); - expect(uploadHandler.directUploadSingleFile.calledOnce).to.be.true; - expect(uploadHandler.getBlobData.called).to.be.false; - }); - - it('should fall through to chunked upload when direct upload fails', async () => { - mockActionBinder.isDirectUploadVerb.returns(true); - uploadHandler.directUploadSingleFile = sinon.stub().resolves(false); - await uploadHandler.uploadSingleFile(file, fileData); - expect(uploadHandler.directUploadSingleFile.calledOnce).to.be.true; - expect(uploadHandler.getBlobData.calledOnce).to.be.true; - }); - it('should handle asset creation error', async () => { uploadHandler.createAsset = sinon.stub().rejects(new Error('fail')); diff --git a/unitylibs/core/widgets/prompt-bar-upload/prompt-bar-upload.css b/unitylibs/core/widgets/prompt-bar-upload/prompt-bar-upload.css index 59a6db0a1..c48db813a 100644 --- a/unitylibs/core/widgets/prompt-bar-upload/prompt-bar-upload.css +++ b/unitylibs/core/widgets/prompt-bar-upload/prompt-bar-upload.css @@ -943,7 +943,8 @@ margin: 0; .pbu-preview { position: absolute; -inset: 0; +top: 0; +left: 0; width: 100%; height: 100%; box-sizing: border-box; @@ -957,14 +958,11 @@ display: none; .pbu-preview-img { display: block; -width: 100%; -height: 100%; -max-width: none; +width: stretch; +height: stretch; object-fit: cover; -object-position: center; border-radius: 13.667px; border: 2px solid #4069FD; -box-sizing: border-box; } .unity-prompt-bar-upload.unity-enabled .pbu-delete-btn { diff --git a/unitylibs/core/workflow/workflow-acrobat/action-binder.js b/unitylibs/core/workflow/workflow-acrobat/action-binder.js index 98f8b6247..e679a60a4 100644 --- a/unitylibs/core/workflow/workflow-acrobat/action-binder.js +++ b/unitylibs/core/workflow/workflow-acrobat/action-binder.js @@ -91,7 +91,6 @@ export default class ActionBinder { pre_upload_error_create_asset: -55, pre_upload_error_missing_verb_config: -56, pre_upload_error_transition_screen: -57, - pre_upload_error_direct_upload: -58, validation_error_validate_files: -100, validation_error_unsupported_type: -101, validation_error_empty_file: -102, @@ -119,7 +118,6 @@ export default class ActionBinder { upload_warn_delete_asset: -603, validation_warn_validate_files: -604, warn_fetch_experiment: -605, - warn_update_no_progress_bar: -606, }; static NEW_TO_OLD_ERROR_KEY_MAP = { @@ -231,7 +229,6 @@ export default class ActionBinder { createAsset: `${unityConfig.apiEndPoint}/asset`, finalizeAsset: `${unityConfig.apiEndPoint}/asset/finalize`, getMetadata: `${unityConfig.apiEndPoint}/asset/metadata`, - directUpload: `${unityConfig.apiEndPoint}/asset/upload`, }; return unityConfig; } @@ -625,32 +622,12 @@ export default class ActionBinder { return new Promise((res) => { setTimeout(() => { res(); }, ms); }); } - isDirectUploadVerb(fileSize) { - const verb = this.workflowCfg.enabledFeatures[0]; - const directUploadVerbs = this.workflowCfg.targetCfg.directUploadVerbs || []; - const directUploadMaxSize = this.workflowCfg.targetCfg.directUploadMaxSize || 0; - return directUploadVerbs.includes(verb) && fileSize != null && fileSize <= directUploadMaxSize; - } - - async runProgressBarUpdate(splashLayer) { - try { - this.transitionScreen.updateProgressBar(splashLayer, 100); - } catch (error) { - await this.dispatchErrorToast('warn_update_no_progress_bar', null, error.message, true, true, { - code: 'warn_update_no_progress_bar', - desc: error.message, - }); - } - } - async continueInApp() { if (!this.redirectUrl || !(this.operations.length || this.redirectWithoutUpload)) return; this.LOADER_LIMIT = 100; const { default: TransitionScreen } = await import(`${getUnityLibs()}/scripts/transition-screen.js`); this.transitionScreen = new TransitionScreen(this.transitionScreen.splashScreenEl, this.initActionListeners, this.LOADER_LIMIT, this.workflowCfg); - const splashLayer = this.transitionScreen.splashScreenEl; - if (this.isDirectUploadVerb(this.filesData?.size)) await this.runProgressBarUpdate(splashLayer); - else this.transitionScreen.updateProgressBar(splashLayer, 100); + this.transitionScreen.updateProgressBar(this.transitionScreen.splashScreenEl, 100); try { await this.delay(500); const [baseUrl, queryString] = this.redirectUrl.split('?'); diff --git a/unitylibs/core/workflow/workflow-acrobat/target-config.json b/unitylibs/core/workflow/workflow-acrobat/target-config.json index 13d075912..ab7f56ad4 100644 --- a/unitylibs/core/workflow/workflow-acrobat/target-config.json +++ b/unitylibs/core/workflow/workflow-acrobat/target-config.json @@ -14,9 +14,6 @@ }, "sendSplunkAnalytics": true, "verbsWithoutMfuToSfuFallback": ["compress-pdf"], - "awaitFinalizeVerbs": ["word-to-pdf"], - "directUploadVerbs": ["word-to-pdf"], - "directUploadMaxSize": 1048576, "nonpdfMfuFeedbackScreenTypeNonpdf": ["combine-pdf"], "nonpdfSfuProductScreen": ["word-to-pdf", "jpg-to-pdf", "ppt-to-pdf", "excel-to-pdf", "png-to-pdf", "createpdf", "chat-pdf", "chat-pdf-student", "summarize-pdf", "pdf-ai", "heic-to-pdf", "quiz-maker", "flashcard-maker", "mindmap-maker"], "mfuUploadAllowed": ["combine-pdf", "rotate-pages", "chat-pdf", "chat-pdf-student", "summarize-pdf", "pdf-ai", "quiz-maker", "flashcard-maker", "mindmap-maker"], diff --git a/unitylibs/core/workflow/workflow-acrobat/upload-handler.js b/unitylibs/core/workflow/workflow-acrobat/upload-handler.js index 62e9a8088..ba04bef4e 100644 --- a/unitylibs/core/workflow/workflow-acrobat/upload-handler.js +++ b/unitylibs/core/workflow/workflow-acrobat/upload-handler.js @@ -19,75 +19,6 @@ export default class UploadHandler { return feature === 'pdf-ai' ? 'chat-pdf-pdf-ai' : feature; } - async isDocOrDocx(file) { - const { getExtension } = await import('../../../utils/FileUtils.js'); - return ['doc', 'docx'].includes(getExtension(file?.name || '').toLowerCase()); - } - - async directUploadAsset(file, signal, workflowId = null) { - const formData = new FormData(); - formData.append('surfaceId', unityConfig.surfaceId); - formData.append('targetProduct', this.actionBinder.workflowCfg.productName); - formData.append('name', file.name); - formData.append('size', file.size); - formData.append('format', 'application/pdf'); - formData.append('file', file); - if (workflowId) formData.append('workflowId', workflowId); - const opts = await getApiCallOptions( - 'POST', - unityConfig.apiKey, - this.actionBinder.getAdditionalHeaders() || {}, - { body: formData, signal }, - ); - delete opts.headers['Content-Type']; - const { response, attempt } = await this.networkUtils.fetchFromServiceWithRetry( - this.actionBinder.acrobatApiConfig.acrobatEndpoint.directUpload, - opts, - this.actionBinder.workflowCfg.targetCfg.fetchApiConfig.default, - ); - return { ...response, attempt }; - } - - async directUploadSingleFile(file, fileData, isPdf = true) { - const abortSignal = this.actionBinder.getAbortSignal(); - this.actionBinder.dispatchAnalyticsEvent('uploading', fileData); - this.actionBinder.setIsUploading(true); - let assetData; - try { - assetData = await this.directUploadAsset(file, abortSignal); - } catch (error) { - this.initSplashScreen(); - await this.transitionScreen.showSplashScreen(); - this.handleUploadError(error, 'pre_upload_error_direct_upload'); - return false; - } - fileData.assetId = assetData.id; - this.actionBinder.setAssetId(assetData.id); - const effectiveFileType = await this.getEffectiveFileType(file); - const cOpts = { - assetId: assetData.id, - targetProduct: this.actionBinder.workflowCfg.productName, - payload: { - languageRegion: this.actionBinder.workflowCfg.langRegion, - languageCode: this.actionBinder.workflowCfg.langCode, - verb: this.getVerbForFeature(), - assetMetadata: { [assetData.id]: { name: file.name, size: file.size, type: effectiveFileType } }, - ...(!isPdf ? { feedback: 'nonpdf' } : {}), - }, - }; - const redirectSuccess = await this.actionBinder.handleRedirect(cOpts, fileData); - if (!redirectSuccess) return false; - - this.actionBinder.operations.push(assetData.id); - this.actionBinder.uploadTimestamp = Date.now(); - this.actionBinder.dispatchAnalyticsEvent('uploaded', { - ...fileData, - assetId: assetData.id, - maxRetryCount: assetData.attempt || 0, - }); - return true; - } - async getEffectiveFileType(file) { const { getExtension } = await import('../../../utils/FileUtils.js'); const isHeicWithoutMimeType = this.actionBinder.workflowCfg.enabledFeatures[0] === 'heic-to-pdf' @@ -267,22 +198,7 @@ export default class UploadHandler { return { failedFiles, attemptMap }; } - async isAwaitFinalizeVerb(verb, file) { - const awaitFinalizeVerbs = this.actionBinder.workflowCfg.targetCfg.awaitFinalizeVerbs || []; - return awaitFinalizeVerbs.includes(verb) && await this.isDocOrDocx(file); - } - - async getFinalizeRetryConfig(verb, file) { - const baseConfig = this.actionBinder.workflowCfg.targetCfg.fetchApiConfig.finalizeAsset; - if (await this.isAwaitFinalizeVerb(verb, file)) { - return { ...baseConfig, retryOn202: false }; - } - return baseConfig; - } - - async verifyContent(assetData, signal, file = null) { - const verb = this.actionBinder.workflowCfg.enabledFeatures[0]; - const finalizeRetryConfig = await this.getFinalizeRetryConfig(verb, file); + async verifyContent(assetData, signal) { try { const finalAssetData = { surfaceId: unityConfig.surfaceId, @@ -298,8 +214,7 @@ export default class UploadHandler { const finalizeJson = await this.networkUtils.fetchFromServiceWithRetry( this.actionBinder.acrobatApiConfig.acrobatEndpoint.finalizeAsset, finalizeOpts, - finalizeRetryConfig, - (responseJson) => responseJson, + this.actionBinder.workflowCfg.targetCfg.fetchApiConfig.finalizeAsset, ); if (!finalizeJson || Object.keys(finalizeJson).length !== 0) { if (this.actionBinder.MULTI_FILE) { @@ -423,11 +338,6 @@ export default class UploadHandler { } async uploadSingleFile(file, fileData, isPdf = true) { - if (this.actionBinder.isDirectUploadVerb(file.size)) { - const success = await this.directUploadSingleFile(file, fileData, isPdf); - if (success) return; - } - const { maxConcurrentChunks } = this.getConcurrentLimits(); const abortSignal = this.actionBinder.getAbortSignal(); let cOpts = {}; @@ -494,7 +404,7 @@ export default class UploadHandler { return; } this.actionBinder.operations.push(assetData.id); - const verified = await this.verifyContent(assetData, null, file); + const verified = await this.verifyContent(assetData); if (!verified || abortSignal.aborted) return; if (abortSignal.aborted || !this.actionBinder.isUploading) return; this.actionBinder.uploadTimestamp = Date.now(); diff --git a/unitylibs/utils/NetworkUtils.js b/unitylibs/utils/NetworkUtils.js index 20f7dff1b..555628b9e 100644 --- a/unitylibs/utils/NetworkUtils.js +++ b/unitylibs/utils/NetworkUtils.js @@ -122,14 +122,6 @@ export default class NetworkUtils { } } - shouldRetryPollingRequest(status, retryConfig, customRetryCheckResult) { - if (customRetryCheckResult) return true; - if (status >= 500 && status < 600) return true; - if (status === 429) return true; - if (status === 202 && retryConfig.retryOn202 !== false) return true; - return false; - } - async fetchFromServiceWithServerPollingRetry(url, options, retryConfig, onSuccess, onError) { const maxRetryDelay = retryConfig.retryParams?.maxRetryDelay || 300000; let timeLapsed = 0; @@ -141,7 +133,7 @@ export default class NetworkUtils { const { status, headers } = response; const responseJson = await this.getResponseJson(response); const customRetryCheckResult = retryConfig.extraRetryCheck && await retryConfig.extraRetryCheck(status, responseJson); - if (this.shouldRetryPollingRequest(status, retryConfig, customRetryCheckResult)) { + if (customRetryCheckResult || status === 202 || (status >= 500 && status < 600) || status === 429) { const retryDelay = (parseInt(headers.get('retry-after'), 10) * 1000) || retryConfig.retryParams?.defaultRetryDelay || 5000; await new Promise((resolve) => { setTimeout(resolve, retryDelay); }); timeLapsed += retryDelay;