diff --git a/assets/src/bundles/add_forge/add-request-history-item.ejs b/assets/src/bundles/add_forge/add-request-history-item.ejs new file mode 100644 --- /dev/null +++ b/assets/src/bundles/add_forge/add-request-history-item.ejs @@ -0,0 +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 +%> + +
+
+

+ +

+
+
+
+

<%= event.text %>

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

+ Status changed to: <%= event.new_status %> +

+ <% } %> +
+
+
diff --git a/assets/src/bundles/add_forge/forge-admin-email.ejs b/assets/src/bundles/add_forge/forge-admin-email.ejs new file mode 100644 --- /dev/null +++ b/assets/src/bundles/add_forge/forge-admin-email.ejs @@ -0,0 +1,20 @@ +<%# + 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 +%> +Dear forge administrator, + +The mission of Software Heritage is to collect, preserve and share all the publicly available source code (see https://www.softwareheritage.org for more information). + +We just received a request to add the forge hosted at [URL] to the list of software origins that are archived, and it is our understanding that you are the contact person for this forge. + +In order to archive the forge contents, we will have to periodically pull the public repositories it contains and clone them into the Software Heritage archive. + +Would you be so kind as to reply to this message to acknowledge the reception of this email and let us know if there are any special steps we should take in order to properly archive the public repositories hosted on your infrastructure? + +Thank you in advance for your help. + +Kind regards, +The Software Heritage team diff --git a/assets/src/bundles/add_forge/index.js b/assets/src/bundles/add_forge/index.js --- a/assets/src/bundles/add_forge/index.js +++ b/assets/src/bundles/add_forge/index.js @@ -9,3 +9,4 @@ export * from './create-request'; export * from './moderation-dashboard'; +export * from './request-dashboard'; diff --git a/assets/src/bundles/add_forge/moderation-dashboard.js b/assets/src/bundles/add_forge/moderation-dashboard.js --- a/assets/src/bundles/add_forge/moderation-dashboard.js +++ b/assets/src/bundles/add_forge/moderation-dashboard.js @@ -28,7 +28,10 @@ { data: 'id', name: 'id', - render: $.fn.dataTable.render.text() + render: function(data, type, row, meta) { + const dashboardUrl = Urls.add_forge_now_request_dashboard(data); + return `${data}`; + } }, { data: 'submission_date', diff --git a/assets/src/bundles/add_forge/request-dashboard.js b/assets/src/bundles/add_forge/request-dashboard.js new file mode 100644 --- /dev/null +++ b/assets/src/bundles/add_forge/request-dashboard.js @@ -0,0 +1,135 @@ +/** + * 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'; + +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(); + $('#requestStatus').text(data.request.status); + $('#requestType').text(data.request.forge_type); + $('#requestURL').text(data.request.forge_url); + $('#requestEmail').text(data.request.forge_contact_email); + $('#submitterMessage').text(data.request.forge_contact_comment); + $('#updateComment').val(''); + + // Setting data for the email, now adding static data + $('#swh-input-forge-admin-email').val(emailTempate({}).trim()); + $('#contactForgeAdmin').attr('emailTo', data.request.forge_contact_email); + $('#contactForgeAdmin').attr('emailSubject', `[swh-add_forge_now] Request ${data.request.id}`); + populateRequestHistory(data.history); + populateDecisionSelectOption(data.request.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]; + $('#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 = $('#swh-input-forge-admin-email').val().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/fixtures/add-forge-now-request.json b/cypress/fixtures/add-forge-now-request.json new file mode 100644 --- /dev/null +++ b/cypress/fixtures/add-forge-now-request.json @@ -0,0 +1,23 @@ +{ + "request":{ + "id":1, + "status":"PENDING", + "submission_date":"2022-03-17T13:35:24.324848Z", + "submitter_name":"admin", + "submitter_email":"admin@swh-web.org", + "forge_type":"bitbucket", + "forge_url":"test.com", + "forge_contact_email":"test@example.com", + "forge_contact_name":"test user", + "forge_contact_comment":"test comment" + },"history":[ + { + "id":1, + "text":"", + "actor":"admin", + "actor_role":"SUBMITTER", + "date":"2022-03-17T13:35:24.326190Z", + "new_status":"PENDING" + } + ] +} diff --git a/cypress/integration/add-forge-now-request-dashboard.spec.js b/cypress/integration/add-forge-now-request-dashboard.spec.js new file mode 100644 --- /dev/null +++ b/cypress/integration/add-forge-now-request-dashboard.spec.js @@ -0,0 +1,68 @@ +/** + * 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'); + + cy.get('#requestType') + .should('contain', 'bitbucket'); + + cy.get('#requestURL') + .should('contain', 'test.com'); + + cy.get('#requestEmail') + .should('contain', 'test@example.com'); + }); + + 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'); + }); + + it('should load possible next status', function() { + // 3 possible next status and the comment option + cy.get('#decisionOptions') + .children() + .should('have.length', 4); + }); +}); diff --git a/swh/web/admin/add_forge_now.py b/swh/web/admin/add_forge_now.py --- a/swh/web/admin/add_forge_now.py +++ b/swh/web/admin/add_forge_now.py @@ -28,3 +28,19 @@ "add_forge_now/requests-moderation.html", {"heading": "Add forge now requests moderation"}, ) + + +@admin_route( + r"add-forge/request/(?P(\d)+)/", + view_name="add-forge-now-request-dashboard", +) +@user_passes_test(_can_access_moderation, login_url=settings.LOGIN_URL) +def add_forge_now_request_dashboard(request, request_id): + """Moderation dashboard to allow listing current requests. + + """ + return render( + request, + "add_forge_now/request-dashboard.html", + {"request_id": request_id, "heading": "Add forge now request dashboard"}, + ) diff --git a/swh/web/templates/add_forge_now/request-dashboard.html b/swh/web/templates/add_forge_now/request-dashboard.html new file mode 100644 --- /dev/null +++ b/swh/web/templates/add_forge_now/request-dashboard.html @@ -0,0 +1,135 @@ +{% extends "../layout.html" %} + +{% comment %} +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 +{% endcomment %} + +{% load render_bundle from webpack_loader %} +{% load static %} + +{% block header %} +{% render_bundle 'add_forge' %} +{% endblock %} + +{% block title %}{{heading}} – Software Heritage archive{% endblock %} + +{% block navbar-content %} +

Add forge now request dashboard

+ +{% endblock %} + +{% block content %} +
+
+
+

Error fetching information about the request

+
+
+
+
+ +
    +
  • +
    +
    Status
    +
    + +
  • +
  • +
    +
    Type
    +
    + +
  • +
  • +
    +
    Forge URL
    +
    + +
  • +
  • +
    +
    Forge contact email
    +
    + +
  • +
  • +

    +
  • +
+
+
+
+
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+
+ {% csrf_token %} +
+
+ + +
+
+ +
+
+ + + + Enter any comment related to your decision. + +
+
+
+ +
+ +
+
+

+ +

+
+
+
+
+
+
+
+
+ + +{% endblock %}