Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F7123308
D7374.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
17 KB
Subscribers
None
D7374.diff
View Options
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
+%>
+
+<div class="card">
+ <div class="card-header" id="heading<%= index %>}">
+ <h2 class="mb-0">
+ <button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapse<%= index %>"
+ aria-expanded="true" aria-controls="collapse$<%= index %>">
+ From <%= event.actor %> (<%= event.actor_role %>) on <%= event.date.slice(0, 16) %>
+ <%if (event.new_status !== null) { %>
+ <span style="padding-left: 10px;">New status:<span> <%= event.new_status %>
+ <% } %>
+ </button>
+ </h2>
+ </div>
+ <div id="collapse<%= index %>" class="collapse" aria-labelledby="headingOne" data-parent="#requestHistory">
+ <div class="card-body">
+ <p><%= event.text %></p>
+ <%if (event.new_status !== null) { %>
+ <p>
+ <span>Status changed to:<span> <strong><%= event.new_status %></strong>
+ </p>
+ <% } %>
+ </div>
+ </div>
+</div>
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 `<a href=${dashboardUrl}>${data}</a>`;
+ }
},
{
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(
+ `<option value="${status}">${label}</option>`
+ );
+ }
+ // Remove all the options and add new ones
+ $('#decisionOptions').children().remove();
+ nextStatuses.forEach(addStatusOption);
+ $('#decisionOptions').append(
+ '<option hidden disabled selected value> -- Add a comment -- </option>'
+ );
+}
+
+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<request_id>(\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 %}
+<h4>Add forge now request dashboard</h4>
+
+{% endblock %}
+
+{% block content %}
+<div class="col-md-7 offset-md-2">
+ <div class="container">
+ <div id="fetchError" class="d-none">
+ <h3>Error fetching information about the request</h3>
+ </div>
+ <div id="requestDetails">
+ <div class="row">
+ <div class="col-md-12">
+ <label>Request Info</label>
+ <ul class="list-group mb-3">
+ <li class="list-group-item d-flex justify-content-between lh-condensed">
+ <div>
+ <h6 class="my-0">Status</h6>
+ </div>
+ <span class="text-muted" id="requestStatus"></span>
+ </li>
+ <li class="list-group-item d-flex justify-content-between lh-condensed">
+ <div>
+ <h6 class="my-0">Type</h6>
+ </div>
+ <span class="text-muted" id="requestType"></span>
+ </li>
+ <li class="list-group-item d-flex justify-content-between lh-condensed">
+ <div>
+ <h6 class="my-0">Forge URL</h6>
+ </div>
+ <span class="text-muted" id="requestURL"></span>
+ </li>
+ <li class="list-group-item d-flex justify-content-between lh-condensed">
+ <div>
+ <h6 class="my-0">Forge contact email</h6>
+ </div>
+ <span id="requestEmail"></span>
+ </li>
+ <li class="list-group-item d-flex justify-content-between lh-condensed"
+ title="Submitter comment">
+ <p id="submitterMessage"></p>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-12">
+ <label>Request history</label>
+ <div class="accordion" id="requestHistory">
+ </div>
+ </div>
+ </div>
+
+ <div class="row">
+ <label style="padding-top: 10px;">Message for the Forge administrator (Editable)</label>
+ <div class="form-group col-md-12">
+ <textarea class="form-control" id="swh-input-forge-admin-email"
+ name="forge_contact_comment" rows="4"></textarea>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="form-group col-md-12">
+ <button class="btn btn-default float-right" id="contactForgeAdmin"
+ emailSubject="" emailTo="">
+ Send message to forge
+ </button>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="form-group col-md-12">
+ <form method="POST"
+ action="{% url 'api-1-add-forge-request-update' request_id %}"
+ style="padding-top: 5px;" id="updateRequestForm">
+ {% csrf_token %}
+ <div class="form-row">
+ <div class="form-group col-md-6">
+ <label for="decisionOptions">Choose your decision</label>
+ <select class="form-control" id="decisionOptions" name="new_status">
+ </select>
+ </div>
+ </div>
+
+ <div class="form-row">
+ <div class="form-group col-md-12">
+ <label for="swh-input-forge-comment">Comment</label>
+ <textarea class="form-control" id="updateComment" name="text" rows="3" required></textarea>
+ <small class="form-text text-muted">
+ Enter any comment related to your decision.
+ </small>
+ </div>
+ </div>
+ <div class="form-group col-md-6">
+ <button type="submit" class="btn btn-default mb-2 btn-lg">Submit</button>
+ </div>
+
+ <div class="form-row">
+ <div class="col-md-12">
+ <h3 class="text-center">
+ <span id="userMessage" class="badge"></span>
+ </h3>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+ swh.add_forge.onRequestDashboardLoad("{{ request_id }}");
+</script>
+{% endblock %}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Dec 18, 3:54 PM (4 h, 56 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3216541
Attached To
D7374: Add-forge-now request dashboard
Event Timeline
Log In to Comment