diff --git a/cypress/integration/vault.spec.js b/cypress/integration/vault.spec.js index 9f5f3c77..41d5861b 100644 --- a/cypress/integration/vault.spec.js +++ b/cypress/integration/vault.spec.js @@ -1,428 +1,454 @@ /** * Copyright (C) 2019-2020 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ let vaultItems = []; const progressbarColors = { 'new': 'rgba(128, 128, 128, 0.5)', 'pending': 'rgba(0, 0, 255, 0.5)', 'done': 'rgb(92, 184, 92)' }; function checkVaultCookingTask(objectType) { cy.contains('button', 'Download') .click(); cy.contains('.dropdown-item', objectType) .click(); cy.wait('@checkVaultCookingTask'); } function updateVaultItemList(vaultUrl, vaultItems) { cy.visit(vaultUrl) .then(() => { // Add uncooked task to localStorage // which updates it in vault items list window.localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultItems)); }); } // Mocks API response : /api/1/vault/(:objectType)/(:hash) // objectType : {'directory', 'revision'} function genVaultCookingResponse(objectType, objectId, status, message, fetchUrl) { return { 'obj_type': objectType, 'id': 1, 'progress_message': message, 'status': status, 'obj_id': objectId, 'fetch_url': fetchUrl }; }; // Tests progressbar color, status // And status in localStorage function testStatus(taskId, color, statusMsg, status) { cy.get(`.swh-vault-table #vault-task-${taskId}`) .should('be.visible') .find('.progress-bar') .should('be.visible') .and('have.css', 'background-color', color) .and('contain', statusMsg) .then(() => { // Vault item with object_id as taskId should exist in localStorage const currentVaultItems = JSON.parse(window.localStorage.getItem('swh-vault-cooking-tasks')); const vaultItem = currentVaultItems.find(obj => obj.object_id === taskId); assert.isNotNull(vaultItem); assert.strictEqual(vaultItem.status, status); }); } describe('Vault Cooking User Interface Tests', function() { before(function() { - this.directory = this.origin[0].directory[0].id; - this.directoryUrl = this.Urls.browse_directory(this.directory); + const dirInfo = this.origin[0].directory[0]; + this.directory = dirInfo.id; + this.directoryUrl = this.Urls.browse_origin_directory() + + `?origin_url=${this.origin[0].url}&path=${dirInfo.path}`; this.vaultDirectoryUrl = this.Urls.api_1_vault_cook_directory(this.directory); this.vaultFetchDirectoryUrl = this.Urls.api_1_vault_fetch_directory(this.directory); this.revision = this.origin[1].revisions[0]; this.revisionUrl = this.Urls.browse_revision(this.revision); this.vaultRevisionUrl = this.Urls.api_1_vault_cook_revision_gitfast(this.revision); this.vaultFetchRevisionUrl = this.Urls.api_1_vault_fetch_revision_gitfast(this.revision); vaultItems[0] = { 'object_type': 'revision', 'object_id': this.revision, 'email': '', 'status': 'done', 'fetch_url': `/api/1/vault/revision/${this.revision}/gitfast/raw/`, 'progress_message': null }; }); beforeEach(function() { this.genVaultDirCookingResponse = (status, message = null) => { return genVaultCookingResponse('directory', this.directory, status, message, this.vaultFetchDirectoryUrl); }; this.genVaultRevCookingResponse = (status, message = null) => { return genVaultCookingResponse('revision', this.revision, status, message, this.vaultFetchRevisionUrl); }; cy.server(); }); it('should create a directory cooking task and report its status', function() { // Browse a directory cy.visit(this.directoryUrl); // Stub responses when requesting the vault API to simulate // a task has been created cy.route({ method: 'GET', url: this.vaultDirectoryUrl, response: {'exception': 'NotFoundExc'} }).as('checkVaultCookingTask'); cy.route({ method: 'POST', url: this.vaultDirectoryUrl, response: this.genVaultDirCookingResponse('new') }).as('createVaultCookingTask'); cy.contains('button', 'Download') .click(); cy.route({ method: 'GET', url: this.vaultDirectoryUrl, response: this.genVaultDirCookingResponse('new') }).as('checkVaultCookingTask'); - // Create a vault cooking task through the GUI - cy.get('.modal-dialog') - .contains('button:visible', 'Ok') - .click(); + cy.window().then(win => { + const swhIdsContext = win.swh.webapp.getSwhIdsContext(); + const browseDirectoryUrl = swhIdsContext.directory.swhid_with_context_url; - cy.wait('@createVaultCookingTask'); + // Create a vault cooking task through the GUI + cy.get('.modal-dialog') + .contains('button:visible', 'Ok') + .click(); - // Check that a redirection to the vault UI has been performed - cy.url().should('eq', Cypress.config().baseUrl + this.Urls.browse_vault()); + cy.wait('@createVaultCookingTask'); - cy.wait('@checkVaultCookingTask').then(() => { - testStatus(this.directory, progressbarColors['new'], 'new', 'new'); - }); + // Check that a redirection to the vault UI has been performed + cy.url().should('eq', Cypress.config().baseUrl + this.Urls.browse_vault()); - // Stub response to the vault API indicating the task is processing - cy.route({ - method: 'GET', - url: this.vaultDirectoryUrl, - response: this.genVaultDirCookingResponse('pending', 'Processing...') - }).as('checkVaultCookingTask'); + cy.wait('@checkVaultCookingTask').then(() => { + testStatus(this.directory, progressbarColors['new'], 'new', 'new'); + }); - cy.wait('@checkVaultCookingTask').then(() => { - testStatus(this.directory, progressbarColors['pending'], 'Processing...', 'pending'); - }); + // Stub response to the vault API indicating the task is processing + cy.route({ + method: 'GET', + url: this.vaultDirectoryUrl, + response: this.genVaultDirCookingResponse('pending', 'Processing...') + }).as('checkVaultCookingTask'); - // Stub response to the vault API indicating the task is finished - cy.route({ - method: 'GET', - url: this.vaultDirectoryUrl, - response: this.genVaultDirCookingResponse('done') - }).as('checkVaultCookingTask'); + cy.wait('@checkVaultCookingTask').then(() => { + testStatus(this.directory, progressbarColors['pending'], 'Processing...', 'pending'); + }); - cy.wait('@checkVaultCookingTask').then(() => { - testStatus(this.directory, progressbarColors['done'], 'done', 'done'); - }); + // Stub response to the vault API indicating the task is finished + cy.route({ + method: 'GET', + url: this.vaultDirectoryUrl, + response: this.genVaultDirCookingResponse('done') + }).as('checkVaultCookingTask'); - // Stub response to the vault API to simulate archive download - cy.route({ - method: 'GET', - url: this.vaultFetchDirectoryUrl, - response: `fx:${this.directory}.tar.gz,binary`, - headers: { - 'Content-disposition': `attachment; filename=${this.directory}.tar.gz`, - 'Content-Type': 'application/gzip' - } - }).as('fetchCookedArchive'); + cy.wait('@checkVaultCookingTask').then(() => { + testStatus(this.directory, progressbarColors['done'], 'done', 'done'); + }); - cy.get(`#vault-task-${this.directory} .vault-dl-link button`) - .click(); + // Stub response to the vault API to simulate archive download + cy.route({ + method: 'GET', + url: this.vaultFetchDirectoryUrl, + response: `fx:${this.directory}.tar.gz,binary`, + headers: { + 'Content-disposition': `attachment; filename=${this.directory}.tar.gz`, + 'Content-Type': 'application/gzip' + } + }).as('fetchCookedArchive'); + + cy.get(`#vault-task-${this.directory} .vault-origin a`) + .should('have.text', this.origin[0].url) + .should('have.attr', 'href', `${this.Urls.browse_origin()}?origin_url=${this.origin[0].url}`); + + cy.get(`#vault-task-${this.directory} .vault-object-info a`) + .should('have.text', this.directory) + .should('have.attr', 'href', browseDirectoryUrl); + + cy.get(`#vault-task-${this.directory} .vault-dl-link button`) + .click(); - cy.wait('@fetchCookedArchive').then((xhr) => { - assert.isNotNull(xhr.response.body); + cy.wait('@fetchCookedArchive').then((xhr) => { + assert.isNotNull(xhr.response.body); + }); }); - }); it('should create a revision cooking task and report its status', function() { cy.adminLogin(); + // Browse a revision cy.visit(this.revisionUrl); // Stub responses when requesting the vault API to simulate // a task has been created - cy.route({ method: 'GET', url: this.vaultRevisionUrl, response: {'exception': 'NotFoundExc'} }).as('checkVaultCookingTask'); cy.route({ method: 'POST', url: this.vaultRevisionUrl, response: this.genVaultRevCookingResponse('new') }).as('createVaultCookingTask'); // Create a vault cooking task through the GUI checkVaultCookingTask('as git'); cy.route({ method: 'GET', url: this.vaultRevisionUrl, response: this.genVaultRevCookingResponse('new') }).as('checkVaultCookingTask'); - // Create a vault cooking task through the GUI - cy.get('.modal-dialog') - .contains('button:visible', 'Ok') - .click(); + cy.window().then(win => { + const swhIdsContext = win.swh.webapp.getSwhIdsContext(); + const browseRevisionUrl = swhIdsContext.revision.swhid_url; - cy.wait('@createVaultCookingTask'); + // Create a vault cooking task through the GUI + cy.get('.modal-dialog') + .contains('button:visible', 'Ok') + .click(); - // Check that a redirection to the vault UI has been performed - cy.url().should('eq', Cypress.config().baseUrl + this.Urls.browse_vault()); + cy.wait('@createVaultCookingTask'); - cy.wait('@checkVaultCookingTask').then(() => { - testStatus(this.revision, progressbarColors['new'], 'new', 'new'); - }); + // Check that a redirection to the vault UI has been performed + cy.url().should('eq', Cypress.config().baseUrl + this.Urls.browse_vault()); - // Stub response to the vault API indicating the task is processing - cy.route({ - method: 'GET', - url: this.vaultRevisionUrl, - response: this.genVaultRevCookingResponse('pending', 'Processing...') - }).as('checkVaultCookingTask'); + cy.wait('@checkVaultCookingTask').then(() => { + testStatus(this.revision, progressbarColors['new'], 'new', 'new'); + }); - cy.wait('@checkVaultCookingTask').then(() => { - testStatus(this.revision, progressbarColors['pending'], 'Processing...', 'pending'); - }); + // Stub response to the vault API indicating the task is processing + cy.route({ + method: 'GET', + url: this.vaultRevisionUrl, + response: this.genVaultRevCookingResponse('pending', 'Processing...') + }).as('checkVaultCookingTask'); - // Stub response to the vault API indicating the task is finished - cy.route({ - method: 'GET', - url: this.vaultRevisionUrl, - response: this.genVaultRevCookingResponse('done') - }).as('checkVaultCookingTask'); + cy.wait('@checkVaultCookingTask').then(() => { + testStatus(this.revision, progressbarColors['pending'], 'Processing...', 'pending'); + }); - cy.wait('@checkVaultCookingTask').then(() => { - testStatus(this.revision, progressbarColors['done'], 'done', 'done'); - }); + // Stub response to the vault API indicating the task is finished + cy.route({ + method: 'GET', + url: this.vaultRevisionUrl, + response: this.genVaultRevCookingResponse('done') + }).as('checkVaultCookingTask'); - // Stub response to the vault API indicating to simulate archive - // download - cy.route({ - method: 'GET', - url: this.vaultFetchRevisionUrl, - response: `fx:${this.revision}.gitfast.gz,binary`, - headers: { - 'Content-disposition': `attachment; filename=${this.revision}.gitfast.gz`, - 'Content-Type': 'application/gzip' - } - }).as('fetchCookedArchive'); + cy.wait('@checkVaultCookingTask').then(() => { + testStatus(this.revision, progressbarColors['done'], 'done', 'done'); + }); - cy.get(`#vault-task-${this.revision} .vault-dl-link button`) - .click(); + // Stub response to the vault API indicating to simulate archive + // download + cy.route({ + method: 'GET', + url: this.vaultFetchRevisionUrl, + response: `fx:${this.revision}.gitfast.gz,binary`, + headers: { + 'Content-disposition': `attachment; filename=${this.revision}.gitfast.gz`, + 'Content-Type': 'application/gzip' + } + }).as('fetchCookedArchive'); + + cy.get(`#vault-task-${this.revision} .vault-origin`) + .should('have.text', 'unknown'); + + cy.get(`#vault-task-${this.revision} .vault-object-info a`) + .should('have.text', this.revision) + .should('have.attr', 'href', browseRevisionUrl); + + cy.get(`#vault-task-${this.revision} .vault-dl-link button`) + .click(); - cy.wait('@fetchCookedArchive').then((xhr) => { - assert.isNotNull(xhr.response.body); + cy.wait('@fetchCookedArchive').then((xhr) => { + assert.isNotNull(xhr.response.body); + }); }); }); it('should offer to recook an archive if no more available to download', function() { updateVaultItemList(this.Urls.browse_vault(), vaultItems); // Send 404 when fetching vault item cy.route({ method: 'GET', status: 404, url: this.vaultFetchRevisionUrl, response: { 'exception': 'NotFoundExc', 'reason': `Revision with ID '${this.revision}' not found.` }, headers: { 'Content-Type': 'json' } }).as('fetchCookedArchive'); cy.get(`#vault-task-${this.revision} .vault-dl-link button`) .click(); cy.wait('@fetchCookedArchive').then(() => { cy.route({ method: 'POST', url: this.vaultRevisionUrl, response: this.genVaultRevCookingResponse('new') }).as('createVaultCookingTask'); cy.route({ method: 'GET', url: this.vaultRevisionUrl, response: this.genVaultRevCookingResponse('new') }).as('checkVaultCookingTask'); cy.get('#vault-recook-object-modal > .modal-dialog') .should('be.visible') .contains('button:visible', 'Ok') .click(); cy.wait('@createVaultCookingTask') .wait('@checkVaultCookingTask') .then(() => { testStatus(this.revision, progressbarColors['new'], 'new', 'new'); }); }); }); it('should remove selected vault items', function() { updateVaultItemList(this.Urls.browse_vault(), vaultItems); cy.get(`#vault-task-${this.revision}`) .find('input[type="checkbox"]') .click({force: true}); cy.contains('button', 'Remove selected tasks') .click(); cy.get(`#vault-task-${this.revision}`) .should('not.exist'); }); it('should offer to immediately download a directory tarball if already cooked', function() { // Browse a directory cy.visit(this.directoryUrl); // Stub responses when requesting the vault API to simulate // the directory tarball has already been cooked cy.route({ method: 'GET', url: this.vaultDirectoryUrl, response: this.genVaultDirCookingResponse('done') }).as('checkVaultCookingTask'); // Stub response to the vault API to simulate archive download cy.route({ method: 'GET', url: this.vaultFetchDirectoryUrl, response: `fx:${this.directory}.tar.gz,binary`, headers: { 'Content-disposition': `attachment; filename=${this.directory}.tar.gz`, 'Content-Type': 'application/gzip' } }).as('fetchCookedArchive'); // Create a vault cooking task through the GUI cy.contains('button', 'Download') .click(); // Start archive download through the GUI cy.get('.modal-dialog') .contains('button:visible', 'Ok') .click(); cy.wait('@fetchCookedArchive'); }); it('should offer to immediately download a revision gitfast archive if already cooked', function() { cy.adminLogin(); // Browse a directory cy.visit(this.revisionUrl); // Stub responses when requesting the vault API to simulate // the directory tarball has already been cooked cy.route({ method: 'GET', url: this.vaultRevisionUrl, response: this.genVaultRevCookingResponse('done') }).as('checkVaultCookingTask'); // Stub response to the vault API to simulate archive download cy.route({ method: 'GET', url: this.vaultFetchRevisionUrl, response: `fx:${this.revision}.gitfast.gz,binary`, headers: { 'Content-disposition': `attachment; filename=${this.revision}.gitfast.gz`, 'Content-Type': 'application/gzip' } }).as('fetchCookedArchive'); checkVaultCookingTask('as git'); // Start archive download through the GUI cy.get('.modal-dialog') .contains('button:visible', 'Ok') .click(); cy.wait('@fetchCookedArchive'); }); it('should offer to recook an object if previous vault task failed', function() { cy.visit(this.directoryUrl); // Stub responses when requesting the vault API to simulate // the last cooking of the directory tarball has failed cy.route({ method: 'GET', url: this.vaultDirectoryUrl, response: this.genVaultDirCookingResponse('failed') }).as('checkVaultCookingTask'); cy.contains('button', 'Download') .click(); // Check that recooking the directory is offered to user cy.get('.modal-dialog') .contains('button:visible', 'Ok') .should('be.visible'); }); }); diff --git a/swh/web/assets/src/bundles/vault/vault-create-tasks.js b/swh/web/assets/src/bundles/vault/vault-create-tasks.js index 8238dbc4..e0c35702 100644 --- a/swh/web/assets/src/bundles/vault/vault-create-tasks.js +++ b/swh/web/assets/src/bundles/vault/vault-create-tasks.js @@ -1,125 +1,134 @@ /** * Copyright (C) 2018-2019 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ import {handleFetchError, csrfPost} from 'utils/functions'; export function vaultRequest(objectType, objectId) { let vaultUrl; if (objectType === 'directory') { vaultUrl = Urls.api_1_vault_cook_directory(objectId); } else { vaultUrl = Urls.api_1_vault_cook_revision_gitfast(objectId); } // check if object has already been cooked fetch(vaultUrl) .then(response => response.json()) .then(data => { // object needs to be cooked if (data.exception === 'NotFoundExc' || data.status === 'failed') { // if last cooking has failed, remove previous task info from localStorage // in order to force the recooking of the object swh.vault.removeCookingTaskInfo([objectId]); $(`#vault-cook-${objectType}-modal`).modal('show'); // object has been cooked and should be in the vault cache, // it will be asked to cook it again if it is not } else if (data.status === 'done') { $(`#vault-fetch-${objectType}-modal`).modal('show'); } }); } function addVaultCookingTask(cookingTask) { + + const swhidsContext = swh.webapp.getSwhIdsContext(); + cookingTask.origin = swhidsContext[cookingTask.object_type].context.origin; + cookingTask.path = swhidsContext[cookingTask.object_type].context.path; + cookingTask.browse_url = swhidsContext[cookingTask.object_type].swhid_with_context_url; + if (!cookingTask.browse_url) { + cookingTask.browse_url = swhidsContext[cookingTask.object_type].swhid_url; + } + let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks')); if (!vaultCookingTasks) { vaultCookingTasks = []; } if (vaultCookingTasks.find(val => { return val.object_type === cookingTask.object_type && val.object_id === cookingTask.object_id; }) === undefined) { let cookingUrl; if (cookingTask.object_type === 'directory') { cookingUrl = Urls.api_1_vault_cook_directory(cookingTask.object_id); } else { cookingUrl = Urls.api_1_vault_cook_revision_gitfast(cookingTask.object_id); } if (cookingTask.email) { cookingUrl += '?email=' + cookingTask.email; } csrfPost(cookingUrl) .then(handleFetchError) .then(() => { vaultCookingTasks.push(cookingTask); localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks)); $('#vault-cook-directory-modal').modal('hide'); $('#vault-cook-revision-modal').modal('hide'); window.location = Urls.browse_vault(); }) .catch(() => { $('#vault-cook-directory-modal').modal('hide'); $('#vault-cook-revision-modal').modal('hide'); }); } else { window.location = Urls.browse_vault(); } } function validateEmail(email) { let re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(String(email).toLowerCase()); } export function cookDirectoryArchive(directoryId) { let email = $('#swh-vault-directory-email').val().trim(); if (!email || validateEmail(email)) { let cookingTask = { 'object_type': 'directory', 'object_id': directoryId, 'email': email, 'status': 'new' }; addVaultCookingTask(cookingTask); } else { $('#invalid-email-modal').modal('show'); } } export function fetchDirectoryArchive(directoryId) { $('#vault-fetch-directory-modal').modal('hide'); const vaultUrl = Urls.api_1_vault_cook_directory(directoryId); fetch(vaultUrl) .then(response => response.json()) .then(data => { swh.vault.fetchCookedObject(data.fetch_url); }); } export function cookRevisionArchive(revisionId) { let email = $('#swh-vault-revision-email').val().trim(); if (!email || validateEmail(email)) { let cookingTask = { 'object_type': 'revision', 'object_id': revisionId, 'email': email, 'status': 'new' }; addVaultCookingTask(cookingTask); } else { $('#invalid-email-modal').modal('show'); } } export function fetchRevisionArchive(revisionId) { $('#vault-fetch-directory-modal').modal('hide'); const vaultUrl = Urls.api_1_vault_cook_revision_gitfast(revisionId); fetch(vaultUrl) .then(response => response.json()) .then(data => { swh.vault.fetchCookedObject(data.fetch_url); }); } diff --git a/swh/web/assets/src/bundles/vault/vault-ui.js b/swh/web/assets/src/bundles/vault/vault-ui.js index 60553412..84717671 100644 --- a/swh/web/assets/src/bundles/vault/vault-ui.js +++ b/swh/web/assets/src/bundles/vault/vault-ui.js @@ -1,254 +1,279 @@ /** * Copyright (C) 2018-2019 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ import {handleFetchError, handleFetchErrors, csrfPost} from 'utils/functions'; -let progress = `
-
-
-
;`; +let progress = + `
+
+
+
;`; let pollingInterval = 5000; let checkVaultId; function updateProgressBar(progressBar, cookingTask) { if (cookingTask.status === 'new') { progressBar.css('background-color', 'rgba(128, 128, 128, 0.5)'); } else if (cookingTask.status === 'pending') { progressBar.css('background-color', 'rgba(0, 0, 255, 0.5)'); } else if (cookingTask.status === 'done') { progressBar.css('background-color', '#5cb85c'); } else if (cookingTask.status === 'failed') { progressBar.css('background-color', 'rgba(255, 0, 0, 0.5)'); progressBar.css('background-image', 'none'); } progressBar.text(cookingTask.progress_message || cookingTask.status); if (cookingTask.status === 'new' || cookingTask.status === 'pending') { progressBar.addClass('progress-bar-animated'); } else { progressBar.removeClass('progress-bar-striped'); } } let recookTask; // called when the user wants to download a cooked archive export function fetchCookedObject(fetchUrl) { recookTask = null; // first, check if the link is still available from the vault fetch(fetchUrl) .then(response => { // link is still alive, proceed to download if (response.ok) { $('#vault-fetch-iframe').attr('src', fetchUrl); // link is dead } else { // get the associated cooking task let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks')); for (let i = 0; i < vaultCookingTasks.length; ++i) { if (vaultCookingTasks[i].fetch_url === fetchUrl) { recookTask = vaultCookingTasks[i]; break; } } // display a modal asking the user if he wants to recook the archive $('#vault-recook-object-modal').modal('show'); } }); } // called when the user wants to recook an archive // for which the download link is not available anymore export function recookObject() { if (recookTask) { // stop cooking tasks status polling clearTimeout(checkVaultId); // build cook request url let cookingUrl; if (recookTask.object_type === 'directory') { cookingUrl = Urls.api_1_vault_cook_directory(recookTask.object_id); } else { cookingUrl = Urls.api_1_vault_cook_revision_gitfast(recookTask.object_id); } if (recookTask.email) { cookingUrl += '?email=' + recookTask.email; } // request archive cooking csrfPost(cookingUrl) .then(handleFetchError) .then(() => { // update task status recookTask.status = 'new'; let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks')); for (let i = 0; i < vaultCookingTasks.length; ++i) { if (vaultCookingTasks[i].object_id === recookTask.object_id) { vaultCookingTasks[i] = recookTask; break; } } // save updated tasks to local storage localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks)); // restart cooking tasks status polling checkVaultCookingTasks(); // hide recook archive modal $('#vault-recook-object-modal').modal('hide'); }) // something went wrong .catch(() => { checkVaultCookingTasks(); $('#vault-recook-object-modal').modal('hide'); }); } } function checkVaultCookingTasks() { let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks')); if (!vaultCookingTasks || vaultCookingTasks.length === 0) { $('.swh-vault-table tbody tr').remove(); checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval); return; } let cookingTaskRequests = []; let tasks = {}; let currentObjectIds = []; for (let i = 0; i < vaultCookingTasks.length; ++i) { let cookingTask = vaultCookingTasks[i]; currentObjectIds.push(cookingTask.object_id); tasks[cookingTask.object_id] = cookingTask; let cookingUrl; if (cookingTask.object_type === 'directory') { cookingUrl = Urls.api_1_vault_cook_directory(cookingTask.object_id); } else { cookingUrl = Urls.api_1_vault_cook_revision_gitfast(cookingTask.object_id); } if (cookingTask.status !== 'done' && cookingTask.status !== 'failed') { cookingTaskRequests.push(fetch(cookingUrl)); } } $('.swh-vault-table tbody tr').each((i, row) => { - let objectId = $(row).find('.vault-object-id').data('object-id'); + let objectId = $(row).find('.vault-object-info').data('object-id'); if ($.inArray(objectId, currentObjectIds) === -1) { $(row).remove(); } }); Promise.all(cookingTaskRequests) .then(handleFetchErrors) .then(responses => Promise.all(responses.map(r => r.json()))) .then(cookingTasks => { let table = $('#vault-cooking-tasks tbody'); for (let i = 0; i < cookingTasks.length; ++i) { let cookingTask = tasks[cookingTasks[i].obj_id]; cookingTask.status = cookingTasks[i].status; cookingTask.fetch_url = cookingTasks[i].fetch_url; cookingTask.progress_message = cookingTasks[i].progress_message; } for (let i = 0; i < vaultCookingTasks.length; ++i) { let cookingTask = vaultCookingTasks[i]; let rowTask = $('#vault-task-' + cookingTask.object_id); - let downloadLinkWait = 'Waiting for download link to be available'; if (!rowTask.length) { - let browseUrl; - if (cookingTask.object_type === 'directory') { - browseUrl = Urls.browse_directory(cookingTask.object_id); - } else { - browseUrl = Urls.browse_revision(cookingTask.object_id); + let browseUrl = cookingTask.browse_url; + if (!browseUrl) { + if (cookingTask.object_type === 'directory') { + browseUrl = Urls.browse_directory(cookingTask.object_id); + } else { + browseUrl = Urls.browse_revision(cookingTask.object_id); + } } let progressBar = $.parseHTML(progress)[0]; let progressBarContent = $(progressBar).find('.progress-bar'); updateProgressBar(progressBarContent, cookingTask); let tableRow; if (cookingTask.object_type === 'directory') { - tableRow = ``; + tableRow = + ``; } else { - tableRow = ``; + tableRow = + `'; } tableRow += '
'; - tableRow += ``; - tableRow += ``; - tableRow += `${cookingTask.object_type}`; - tableRow += `${cookingTask.object_id}`; - tableRow += `${progressBar.outerHTML}`; - let downloadLink = downloadLinkWait; + tableRow += + '`; + tableRow += + '`; + if (cookingTask.origin) { + tableRow += `` + + `` + + `${cookingTask.origin}`; + } else { + tableRow += `unknown`; + } + tableRow += + `` + + `${cookingTask.object_type}`; + tableRow += `` + + `id: ${cookingTask.object_id}`; + if (cookingTask.path) { + tableRow += `
path: ${cookingTask.path}`; + } + tableRow += ''; + tableRow += `${progressBar.outerHTML}`; + let downloadLink = ''; if (cookingTask.status === 'done') { - downloadLink = `'; - } else if (cookingTask.status === 'failed') { - downloadLink = ''; + downloadLink = + ''; } - tableRow += `${downloadLink}`; + tableRow += `${downloadLink}`; tableRow += ''; table.prepend(tableRow); } else { let progressBar = rowTask.find('.progress-bar'); updateProgressBar(progressBar, cookingTask); let downloadLink = rowTask.find('.vault-dl-link'); if (cookingTask.status === 'done') { - downloadLink[0].innerHTML = `'; - } else if (cookingTask.status === 'failed') { + downloadLink[0].innerHTML = + ''; + } else { downloadLink[0].innerHTML = ''; - } else if (cookingTask.status === 'new') { - downloadLink[0].innerHTML = downloadLinkWait; } } } localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks)); checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval); }) .catch(() => {}); } export function removeCookingTaskInfo(tasksToRemove) { let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks')); if (!vaultCookingTasks) { return; } vaultCookingTasks = $.grep(vaultCookingTasks, task => { return $.inArray(task.object_id, tasksToRemove) === -1; }); localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks)); } export function initUi() { $('#vault-tasks-toggle-selection').change(event => { $('.vault-task-toggle-selection').prop('checked', event.currentTarget.checked); }); $('#vault-remove-tasks').click(() => { clearTimeout(checkVaultId); let tasksToRemove = []; $('.swh-vault-table tbody tr').each((i, row) => { let taskSelected = $(row).find('.vault-task-toggle-selection').prop('checked'); if (taskSelected) { - let objectId = $(row).find('.vault-object-id').data('object-id'); + let objectId = $(row).find('.vault-object-info').data('object-id'); tasksToRemove.push(objectId); $(row).remove(); } }); removeCookingTaskInfo(tasksToRemove); $('#vault-tasks-toggle-selection').prop('checked', false); checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval); }); checkVaultCookingTasks(); window.onfocus = () => { clearTimeout(checkVaultId); checkVaultCookingTasks(); }; } diff --git a/swh/web/templates/browse/vault-ui.html b/swh/web/templates/browse/vault-ui.html index b99d2ee3..62c293d6 100644 --- a/swh/web/templates/browse/vault-ui.html +++ b/swh/web/templates/browse/vault-ui.html @@ -1,50 +1,51 @@ {% extends "./layout.html" %} {% comment %} Copyright (C) 2017-2019 The Software Heritage developers See the AUTHORS file at the top-level directory of this distribution License: GNU Affero General Public License version 3, or any later version See top-level LICENSE file for more information {% endcomment %} {% load render_bundle from webpack_loader %} {% block navbar-content %}

Download archived software

{% endblock %} {% block browse-content %}

This interface enables to track the status of the different Software Heritage Vault cooking tasks created while browsing the archive.

Once a cooking task is finished, a link will be made available in order to download the associated archive.

+ - - - + + +
Origin Object typeObject idCooking statusObject infoCooking status
{% include "includes/vault-common.html" %} {% endblock %}