瀏覽代碼

@uppy/transloadit: Reset `tus` key in the file on error, so retried files are re-uploaded (#4421)

* Reset `tus` key in the file on error, so retried files are re-uploaded

* add a test

* Use one file, try to set headers

* Pass Upload-Length and Upload-Offset, so tus knows when upload is complete

* test clean up

---------

Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
Artur Paikin 2 年之前
父節點
當前提交
acf2c8efeb
共有 2 個文件被更改,包括 183 次插入4 次删除
  1. 171 0
      e2e/cypress/integration/dashboard-transloadit.spec.ts
  2. 12 4
      packages/@uppy/transloadit/src/index.js

+ 171 - 0
e2e/cypress/integration/dashboard-transloadit.spec.ts

@@ -191,4 +191,175 @@ describe('Dashboard with Transloadit', () => {
       cy.wait('@createAssemblies').then(() => expect(handler).not.to.be.called)
     })
   })
+
+  it('should not re-use erroneous tus keys', () => {
+    function createAssemblyStatus ({ message, assembly_id, bytes_expected, ...other }) {
+      return {
+        message,
+        assembly_id,
+        parent_id:null,
+        account_id:'deadbeef',
+        account_name:'foo',
+        account_slug:'foo',
+        template_id:null,
+        template_name:null,
+        instance:'test.transloadit.com',
+        assembly_url:`http://api2.test.transloadit.com/assemblies/${assembly_id}`,
+        assembly_ssl_url:`https://api2-test.transloadit.com/assemblies/${assembly_id}`,
+        uppyserver_url:'https://api2-test.transloadit.com/companion/',
+        companion_url:'https://api2-test.transloadit.com/companion/',
+        websocket_url:'about:blank',
+        tus_url:'https://api2-test.transloadit.com/resumable/files/',
+        bytes_received:0,
+        bytes_expected,
+        upload_duration:0.162,
+        client_agent:null,
+        client_ip:null,
+        client_referer:null,
+        transloadit_client:'uppy-core:3.2.0,uppy-transloadit:3.1.3,uppy-tus:3.1.0,uppy-dropbox:3.1.1,uppy-box:2.1.1,uppy-facebook:3.1.1,uppy-google-drive:3.1.1,uppy-instagram:3.1.1,uppy-onedrive:3.1.1,uppy-zoom:2.1.1,uppy-url:3.3.1',
+        start_date: new Date().toISOString(),
+        upload_meta_data_extracted:false,
+        warnings:[],
+        is_infinite:false,
+        has_dupe_jobs:false,
+        execution_start:null,
+        execution_duration:null,
+        queue_duration:0.009,
+        jobs_queue_duration:0,
+        notify_start:null,
+        notify_url:null,
+        notify_response_code:null,
+        notify_response_data:null,
+        notify_duration:null,
+        last_job_completed:null,
+        fields:{},
+        running_jobs:[],
+        bytes_usage:0,
+        executing_jobs:[],
+        started_jobs:[],
+        parent_assembly_status:null,
+        params:'{}',
+        template:null,
+        merged_params:'{}',
+        expected_tus_uploads:1,
+        started_tus_uploads:0,
+        finished_tus_uploads:0,
+        tus_uploads:[],
+        uploads:[],
+        results:{},
+        build_id:'4765326956',
+        status_endpoint:`https://api2-test.transloadit.com/assemblies/${assembly_id}`,
+        ...other,
+      }
+    }
+    cy.get('@file-input').selectFile(
+      ['cypress/fixtures/images/cat.jpg'],
+      { force:true },
+    )
+
+    // SETUP for FIRST ATTEMPT (error response from Transloadit backend)
+    const assemblyIDAttempt1 = '500e56004f4347a288194edd7c7a0ae1'
+    const tusIDAttempt1 = 'a9daed4af4981880faf29b0e9596a14d'
+    cy.intercept('POST', 'https://api2.transloadit.com/assemblies', {
+      statusCode: 200,
+      body: JSON.stringify(createAssemblyStatus({
+        ok:'ASSEMBLY_UPLOADING',
+        message:'The Assembly is still in the process of being uploaded.',
+        assembly_id:assemblyIDAttempt1,
+        bytes_expected:263871,
+      })),
+    }).as('createAssembly')
+
+    cy.intercept('POST', 'https://api2-test.transloadit.com/resumable/files/', {
+      statusCode: 201,
+      headers: {
+        Location: `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`,
+      },
+      times: 1,
+    }).as('tusCall')
+    cy.intercept('PATCH', `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`, {
+      statusCode: 204,
+      headers: {
+        'Upload-Length': '263871',
+        'Upload-Offset': '263871',
+      },
+      times: 1,
+    })
+    cy.intercept('HEAD', `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`, { statusCode: 204 })
+
+    cy.intercept('GET', `https://api2-test.transloadit.com/assemblies/${assemblyIDAttempt1}`, {
+      statusCode: 200,
+      body: JSON.stringify(createAssemblyStatus({
+        error:'INVALID_FILE_META_DATA',
+        http_code:400,
+        message:'Whatever error message from Transloadit backend',
+        reason:'Whatever reason',
+        msg:'Whatever error from Transloadit backend',
+        assembly_id:'500e56004f4347a288194edd7c7a0ae1',
+        bytes_expected:263871,
+      })),
+    }).as('failureReported')
+
+    cy.intercept('POST', 'https://transloaditstatus.com/client_error', {
+      statusCode: 200,
+      body: '{}',
+    })
+
+    // FIRST ATTEMPT
+    cy.get('.uppy-StatusBar-actionBtn--upload').click()
+    cy.wait(['@createAssembly', '@tusCall', '@failureReported'])
+    cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed')
+
+    // SETUP for SECOND ATTEMPT
+    const assemblyIDAttempt2 = '6a3fa40e527d4d73989fce678232a5e1'
+    const tusIDAttempt2 = 'b8ebed4af4981880faf29b0e9596b25e'
+    cy.intercept('POST', 'https://api2.transloadit.com/assemblies', {
+      statusCode: 200,
+      body: JSON.stringify(createAssemblyStatus({
+        ok:'ASSEMBLY_UPLOADING',
+        message:'The Assembly is still in the process of being uploaded.',
+        assembly_id:assemblyIDAttempt2,
+        tus_url:'https://api2-test.transloadit.com/resumable/files/attempt2',
+        bytes_expected:263871,
+      })),
+    }).as('createAssembly-attempt2')
+
+    cy.intercept('POST', 'https://api2-test.transloadit.com/resumable/files/attempt2', {
+      statusCode: 201,
+      headers: {
+        'Upload-Length': '263871',
+        'Upload-Offset': '0',
+        Location: `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`,
+      },
+      times: 1,
+    }).as('tusCall-attempt2')
+
+    cy.intercept('PATCH', `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`, {
+      statusCode: 204,
+      headers: {
+        'Upload-Length': '263871',
+        'Upload-Offset': '263871',
+        'Tus-Resumable': '1.0.0',
+      },
+      times: 1,
+    })
+    cy.intercept('HEAD', `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`, { statusCode: 204 })
+
+    cy.intercept('GET', `https://api2-test.transloadit.com/assemblies/${assemblyIDAttempt2}`, {
+      statusCode: 200,
+      body: JSON.stringify(createAssemblyStatus({
+        ok:'ASSEMBLY_COMPLETED',
+        http_code:200,
+        message:'The Assembly was successfully completed.',
+        assembly_id:assemblyIDAttempt2,
+        bytes_received:263871,
+        bytes_expected:263871,
+      })),
+    }).as('assemblyCompleted-attempt2')
+
+    // SECOND ATTEMPT
+    cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click()
+    cy.wait(['@createAssembly-attempt2', '@tusCall-attempt2', '@assemblyCompleted-attempt2'])
+    cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
+  })
 })

+ 12 - 4
packages/@uppy/transloadit/src/index.js

@@ -290,14 +290,22 @@ export default class Transloadit extends BasePlugin {
 
     watcher.on('assembly-error', (id, error) => {
       // Clear postprocessing state for all our files.
-      const files = this.getAssemblyFiles(id)
-      files.forEach((file) => {
-      // TODO Maybe make a postprocess-error event here?
+      const filesFromAssembly = this.getAssemblyFiles(id)
+      filesFromAssembly.forEach((file) => {
+        // TODO Maybe make a postprocess-error event here?
 
         this.uppy.emit('upload-error', file, error)
-
         this.uppy.emit('postprocess-complete', file)
       })
+
+      // Reset `tus` key in the file state, so when the upload is retried,
+      // old tus upload is not re-used — Assebmly expects a new upload, can't currently
+      // re-use the old one. See: https://github.com/transloadit/uppy/issues/4412
+      // and `onReceiveUploadUrl` in @uppy/tus
+      const files = { ...this.uppy.getState().files }
+      filesFromAssembly.forEach(file => delete files[file.id].tus)
+      this.uppy.setState({ files })
+
       this.uppy.emit('error', error)
     })