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,28 @@ +<%# + 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 <%= forgeUrl %> 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({'forgeUrl': data.request.forge_url}).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 %}