diff --git a/assets/src/bundles/add_forge/add-request-history-item.ejs b/assets/src/bundles/add_forge/add-request-history-item.ejs index a930b469..710c3144 100644 --- a/assets/src/bundles/add_forge/add-request-history-item.ejs +++ b/assets/src/bundles/add_forge/add-request-history-item.ejs @@ -1,31 +1,31 @@ <%# Copyright (C) 2022 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 %>

<%= event.text %>

<%if (event.new_status !== null) { %>

- Status changed to: <%= event.new_status %> + Status changed to: <%= swh.add_forge.formatRequestStatusName(event.new_status) %>

<% } %>
\ No newline at end of file diff --git a/assets/src/bundles/add_forge/create-request.js b/assets/src/bundles/add_forge/create-request.js index f2e57bb5..cae10aea 100644 --- a/assets/src/bundles/add_forge/create-request.js +++ b/assets/src/bundles/add_forge/create-request.js @@ -1,113 +1,116 @@ /** * Copyright (C) 2022 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, removeUrlFragment, csrfPost} from 'utils/functions'; let requestBrowseTable; export function onCreateRequestPageLoad() { $(document).ready(() => { $('#requestCreateForm').submit(async function(event) { event.preventDefault(); try { const response = await csrfPost($(this).attr('action'), {'Content-Type': 'application/x-www-form-urlencoded'}, $(this).serialize()); handleFetchError(response); $('#userMessageDetail').empty(); $('#userMessage').text('Your request has been submitted'); $('#userMessage').removeClass('badge-danger'); $('#userMessage').addClass('badge-success'); requestBrowseTable.draw(); // redraw the table to update the list } catch (errorResponse) { $('#userMessageDetail').empty(); let errorMessage; let errorMessageDetail = ''; const errorData = await errorResponse.json(); // if (errorResponse.content_type === 'text/plain') { // does not work? if (errorResponse.status === 409) { errorMessage = errorData; } else { // assuming json response // const exception = errorData['exception']; errorMessage = 'An unknown error occurred during the request creation'; try { const reason = JSON.parse(errorData['reason']); Object.entries(reason).forEach((keys, _) => { const key = keys[0]; const message = keys[1][0]; // take only the first issue errorMessageDetail += `\n${key}: ${message}`; }); } catch (_) { errorMessageDetail = errorData['reason']; // can't parse it, leave it raw } } $('#userMessage').text( errorMessageDetail ? `Error: ${errorMessageDetail}` : errorMessage ); $('#userMessage').removeClass('badge-success'); $('#userMessage').addClass('badge-danger'); } }); $(window).on('hashchange', () => { if (window.location.hash === '#browse-requests') { $('.nav-tabs a[href="#swh-add-forge-requests-list"]').tab('show'); } else { $('.nav-tabs a[href="#swh-add-forge-submit-request"]').tab('show'); } }); $('#swh-add-forge-requests-list-tab').on('shown.bs.tab', () => { window.location.hash = '#browse-requests'; }); $('#swh-add-forge-tab').on('shown.bs.tab', () => { removeUrlFragment(); }); populateRequestBrowseList(); // Load existing requests }); } export function populateRequestBrowseList() { requestBrowseTable = $('#add-forge-request-browse') .on('error.dt', (e, settings, techNote, message) => { $('#add-forge-browse-request-error').text(message); }) .DataTable({ serverSide: true, processing: true, retrieve: true, searching: true, info: false, dom: '<<"d-flex justify-content-between align-items-center"f' + '<"#list-exclude">l>rt<"bottom"ip>>', ajax: { 'url': Urls.add_forge_request_list_datatables() }, columns: [ { data: 'submission_date', name: 'submission_date' }, { data: 'forge_type', name: 'forge_type' }, { data: 'forge_url', name: 'forge_url' }, { data: 'status', - name: 'status' + name: 'status', + render: function(data, type, row, meta) { + return swh.add_forge.formatRequestStatusName(data); + } } ] }); requestBrowseTable.draw(); } diff --git a/assets/src/bundles/add_forge/index.js b/assets/src/bundles/add_forge/index.js index 2ad140ac..2e92c2df 100644 --- a/assets/src/bundles/add_forge/index.js +++ b/assets/src/bundles/add_forge/index.js @@ -1,13 +1,30 @@ /** * Copyright (C) 2022 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 */ // bundle for add forge views export * from './add-forge.css'; export * from './create-request'; export * from './moderation-dashboard'; export * from './request-dashboard'; + +export function formatRequestStatusName(status) { + // Mapping to format the request status to a human readable text + const statusLabel = { + 'PENDING': 'Pending', + 'WAITING_FOR_FEEDBACK': 'Waiting for feedback', + 'FEEDBACK_TO_HANDLE': 'Feedback to handle', + 'ACCEPTED': 'Accepted', + 'SCHEDULED': 'Scheduled', + 'FIRST_LISTING_DONE': 'First listing done', + 'FIRST_ORIGIN_LOADED': 'First origin loaded', + 'REJECTED': 'Rejected', + 'SUSPENDED': 'Suspended', + 'DENIED': 'Denied' + }; + return status in statusLabel ? statusLabel[status] : status; +} diff --git a/assets/src/bundles/add_forge/moderation-dashboard.js b/assets/src/bundles/add_forge/moderation-dashboard.js index d77de5f8..2956c6bf 100644 --- a/assets/src/bundles/add_forge/moderation-dashboard.js +++ b/assets/src/bundles/add_forge/moderation-dashboard.js @@ -1,58 +1,60 @@ /** * Copyright (C) 2022 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 */ export function onModerationPageLoad() { populateModerationList(); } export async function populateModerationList() { $('#swh-add-forge-now-moderation-list') .on('error.dt', (e, settings, techNote, message) => { $('#swh-add-forge-now-moderation-list-error').text(message); }) .DataTable({ serverSide: true, processing: true, searching: true, info: false, dom: '<<"d-flex justify-content-between align-items-center"f' + '<"#list-exclude">l>rt<"bottom"ip>>', ajax: { 'url': Urls.add_forge_request_list_datatables() }, columns: [ { data: 'id', name: 'id', render: function(data, type, row, meta) { const dashboardUrl = Urls.add_forge_now_request_dashboard(data); return `${data}`; } }, { data: 'submission_date', name: 'submission_date', render: $.fn.dataTable.render.text() }, { data: 'forge_type', name: 'forge_type', render: $.fn.dataTable.render.text() }, { data: 'forge_url', name: 'forge_url', render: $.fn.dataTable.render.text() }, { data: 'status', name: 'status', - render: $.fn.dataTable.render.text() + render: function(data, type, row, meta) { + return swh.add_forge.formatRequestStatusName(data); + } } ] }); } diff --git a/assets/src/bundles/add_forge/request-dashboard.js b/assets/src/bundles/add_forge/request-dashboard.js index 578950db..056d1691 100644 --- a/assets/src/bundles/add_forge/request-dashboard.js +++ b/assets/src/bundles/add_forge/request-dashboard.js @@ -1,140 +1,127 @@ /** * Copyright (C) 2022 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'; import emailTempate from './forge-admin-email.ejs'; import requestHistoryItem from './add-request-history-item.ejs'; let forgeRequest; export function onRequestDashboardLoad(requestId) { $(document).ready(() => { populateRequestDetails(requestId); $('#contactForgeAdmin').click((event) => { contactForgeAdmin(event); }); $('#updateRequestForm').submit(async function(event) { event.preventDefault(); try { const response = await csrfPost($(this).attr('action'), {'Content-Type': 'application/x-www-form-urlencoded'}, $(this).serialize()); handleFetchError(response); $('#userMessage').text('The request status has been updated '); $('#userMessage').removeClass('badge-danger'); $('#userMessage').addClass('badge-success'); populateRequestDetails(requestId); } catch (response) { $('#userMessage').text('Sorry; Updating the request failed'); $('#userMessage').removeClass('badge-success'); $('#userMessage').addClass('badge-danger'); } }); }); } async function populateRequestDetails(requestId) { try { const response = await fetch(Urls.api_1_add_forge_request_get(requestId)); handleFetchError(response); const data = await response.json(); forgeRequest = data.request; - $('#requestStatus').text(forgeRequest.status); + $('#requestStatus').text(swh.add_forge.formatRequestStatusName(forgeRequest.status)); $('#requestType').text(forgeRequest.forge_type); $('#requestURL').text(forgeRequest.forge_url); $('#requestContactName').text(forgeRequest.forge_contact_name); $('#requestContactConsent').text(forgeRequest.submitter_forward_username); $('#requestContactEmail').text(forgeRequest.forge_contact_email); $('#submitterMessage').text(forgeRequest.forge_contact_comment); $('#updateComment').val(''); // Setting data for the email, now adding static data $('#contactForgeAdmin').attr('emailTo', forgeRequest.forge_contact_email); $('#contactForgeAdmin').attr('emailSubject', `[swh-add_forge_now] Request ${forgeRequest.id}`); populateRequestHistory(data.history); populateDecisionSelectOption(forgeRequest.status); } catch (response) { // The error message $('#fetchError').removeClass('d-none'); $('#requestDetails').addClass('d-none'); } } function populateRequestHistory(history) { $('#requestHistory').children().remove(); history.forEach((event, index) => { const historyEvent = requestHistoryItem({'event': event, 'index': index}); $('#requestHistory').append(historyEvent); }); } export function populateDecisionSelectOption(currentStatus) { const nextStatusesFor = { 'PENDING': ['WAITING_FOR_FEEDBACK', 'REJECTED', 'SUSPENDED'], 'WAITING_FOR_FEEDBACK': ['FEEDBACK_TO_HANDLE'], 'FEEDBACK_TO_HANDLE': [ 'WAITING_FOR_FEEDBACK', 'ACCEPTED', 'REJECTED', 'SUSPENDED' ], 'ACCEPTED': ['SCHEDULED'], 'SCHEDULED': [ 'FIRST_LISTING_DONE', 'FIRST_ORIGIN_LOADED' ], 'FIRST_LISTING_DONE': ['FIRST_ORIGIN_LOADED'], 'FIRST_ORIGIN_LOADED': [], 'REJECTED': [], 'SUSPENDED': ['PENDING'], 'DENIED': [] }; - const statusLabel = { - 'PENDING': 'pending', - 'WAITING_FOR_FEEDBACK': 'waiting for feedback', - 'FEEDBACK_TO_HANDLE': 'feedback to handle', - 'ACCEPTED': 'accepted', - 'SCHEDULED': 'scheduled', - 'FIRST_LISTING_DONE': 'first listing done', - 'FIRST_ORIGIN_LOADED': 'first origin loaded', - 'REJECTED': 'rejected', - 'SUSPENDED': 'suspended', - 'DENIED': 'denied' - }; - // Determine the possible next status out of the current one const nextStatuses = nextStatusesFor[currentStatus]; function addStatusOption(status, index) { // Push the next possible status options - const label = statusLabel[status]; + const label = swh.add_forge.formatRequestStatusName(status); $('#decisionOptions').append( `` ); } // Remove all the options and add new ones $('#decisionOptions').children().remove(); nextStatuses.forEach(addStatusOption); $('#decisionOptions').append( '' ); } function contactForgeAdmin(event) { // Open the mailclient with pre-filled text const mailTo = $('#contactForgeAdmin').attr('emailTo'); const subject = $('#contactForgeAdmin').attr('emailSubject'); const emailText = emailTempate({'forgeUrl': forgeRequest.forge_url}).trim().replace(/\n/g, '%0D%0A'); const w = window.open('', '_blank', '', true); w.location.href = `mailto: ${mailTo}?subject=${subject}&body=${emailText}`; w.focus(); } diff --git a/cypress/integration/add-forge-now-request-create.spec.js b/cypress/integration/add-forge-now-request-create.spec.js index 3b618b0e..e0feca08 100644 --- a/cypress/integration/add-forge-now-request-create.spec.js +++ b/cypress/integration/add-forge-now-request-create.spec.js @@ -1,167 +1,167 @@ /** * Copyright (C) 2022 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 */ function populateForm(type, url, contact, email, consent, comment) { cy.get('#swh-input-forge-type').select(type); cy.get('#swh-input-forge-url').type(url); cy.get('#swh-input-forge-contact-name').type(contact); cy.get('#swh-input-forge-contact-email').type(email); cy.get('#swh-input-forge-comment').type(comment); cy.get('#swh-input-consent-check').click({force: consent === 'on'}); } describe('Test add-forge-request creation', function() { beforeEach(function() { this.addForgeNowUrl = this.Urls.forge_add(); }); it('should show both tabs for every user', function() { cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-tab') .should('have.class', 'nav-link'); cy.get('#swh-add-forge-requests-list-tab') .should('have.class', 'nav-link'); }); it('should show create forge tab by default', function() { cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-tab') .should('have.class', 'active'); cy.get('#swh-add-forge-requests-list-tab') .should('not.have.class', 'active'); }); it('should show login link for anonymous user', function() { cy.visit(this.addForgeNowUrl); cy.get('#loginLink') .should('be.visible') .should('contain', 'log in'); }); it('should bring back after login', function() { cy.visit(this.addForgeNowUrl); cy.get('#loginLink') .should('have.attr', 'href') .and('include', `${this.Urls.login()}?next=${this.Urls.forge_add()}`); }); it('should change tabs on click', function() { cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#swh-add-forge-tab') .should('not.have.class', 'active'); cy.get('#swh-add-forge-requests-list-tab') .should('have.class', 'active'); cy.get('#swh-add-forge-tab').click(); cy.get('#swh-add-forge-tab') .should('have.class', 'active'); cy.get('#swh-add-forge-requests-list-tab') .should('not.have.class', 'active'); }); it('should show create form elements to authenticated user', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); cy.get('#swh-input-forge-type') .should('be.visible'); cy.get('#swh-input-forge-url') .should('be.visible'); cy.get('#swh-input-forge-contact-name') .should('be.visible'); cy.get('#swh-input-consent-check') .should('be.visible'); cy.get('#swh-input-forge-comment') .should('be.visible'); cy.get('#swh-input-form-submit') .should('be.visible'); }); it('should show browse requests table for every user', function() { // testing only for anonymous cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#add-forge-request-browse') .should('be.visible'); cy.get('#loginLink') .should('not.be.visible'); }); it('should update browse list on successful submission', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); populateForm('bitbucket', 'gitlab.com', 'test', 'test@example.com', 'on', 'test comment'); cy.get('#requestCreateForm').submit(); cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#add-forge-request-browse') .should('be.visible') .should('contain', 'gitlab.com'); cy.get('#add-forge-request-browse') .should('be.visible') - .should('contain', 'PENDING'); + .should('contain', 'Pending'); }); it('should show error message on conflict', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); populateForm('bitbucket', 'gitlab.com', 'test', 'test@example.com', 'on', 'test comment'); cy.get('#requestCreateForm').submit(); cy.get('#requestCreateForm').submit(); // Submitting the same data again cy.get('#userMessage') .should('have.class', 'badge-danger') .should('contain', 'already exists'); }); it('should show error message', function() { cy.userLogin(); cy.intercept('POST', `${this.Urls.api_1_add_forge_request_create()}**`, { body: { 'exception': 'BadInputExc', 'reason': '{"add-forge-comment": ["This field is required"]}' }, statusCode: 400 }).as('errorRequest'); cy.visit(this.addForgeNowUrl); populateForm( 'bitbucket', 'gitlab.com', 'test', 'test@example.com', 'off', 'comment' ); cy.get('#requestCreateForm').submit(); cy.wait('@errorRequest').then((xhr) => { cy.get('#userMessage') .should('have.class', 'badge-danger') .should('contain', 'field is required'); }); }); }); diff --git a/cypress/integration/add-forge-now-request-dashboard.spec.js b/cypress/integration/add-forge-now-request-dashboard.spec.js index 9a1d432c..8353ca43 100644 --- a/cypress/integration/add-forge-now-request-dashboard.spec.js +++ b/cypress/integration/add-forge-now-request-dashboard.spec.js @@ -1,72 +1,72 @@ /** * Copyright (C) 2022 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 */ const requestId = 1; describe('Test add forge now request dashboard load', function() { beforeEach(function() { const url = this.Urls.add_forge_now_request_dashboard(requestId); cy.adminLogin(); cy.intercept(`${this.Urls.api_1_add_forge_request_get(requestId)}**`, {fixture: 'add-forge-now-request'}).as('forgeAddRequest'); cy.visit(url); }); it('should load add forge request details', function() { cy.wait('@forgeAddRequest'); cy.get('#requestStatus') - .should('contain', 'PENDING'); + .should('contain', 'Pending'); cy.get('#requestType') .should('contain', 'bitbucket'); cy.get('#requestURL') .should('contain', 'test.com'); cy.get('#requestContactEmail') .should('contain', 'test@example.com'); cy.get('#requestContactName') .should('contain', 'test user'); }); it('should not show any error message', function() { cy.get('#fetchError') .should('have.class', 'd-none'); cy.get('#requestDetails') .should('not.have.class', 'd-none'); }); it('should show error message for an api error', function() { const invalidRequestId = 2; const url = this.Urls.add_forge_now_request_dashboard(invalidRequestId); cy.visit(url); cy.get('#fetchError') .should('not.have.class', 'd-none'); cy.get('#requestDetails') .should('have.class', 'd-none'); }); it('should load add forge request history', function() { cy.get('#requestHistory') .children() .should('have.length', 1); cy.get('#requestHistory') .children() - .should('contain', 'New status: PENDING'); + .should('contain', 'New status: Pending'); }); it('should load possible next status', function() { // 3 possible next status and the comment option cy.get('#decisionOptions') .children() .should('have.length', 4); }); });