Page MenuHomeSoftware Heritage

D7357.id26609.diff
No OneTemporary

D7357.id26609.diff

diff --git a/assets/src/bundles/add_forge/create-request.js b/assets/src/bundles/add_forge/create-request.js
new file mode 100644
--- /dev/null
+++ b/assets/src/bundles/add_forge/create-request.js
@@ -0,0 +1,91 @@
+/**
+ * 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} from 'utils/functions';
+
+export function onCreateRequestPageLoad() {
+ if (window.location.hash === '#browse-requests') {
+ $('.nav-tabs a[href="#swh-add-forge-requests-list"]').tab('show');
+ }
+
+ $('#requestCreateForm').submit(async function(event) {
+ event.preventDefault();
+ try {
+ const response = await fetch($(this).attr('action'), {
+ method: $(this).attr('method'),
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: $(this).serialize()
+ });
+ handleFetchError(response);
+ $('#userMessageDetail').empty();
+ $('#userMessage').text('Your request has been submitted');
+ $('#userMessage').removeClass('badge-danger');
+ $('#userMessage').addClass('badge-success');
+ populateRequesBrowseList(); // Update the listing
+ } catch (response) {
+ const responseText = await response.json();
+ $('#userMessageDetail').empty();
+ $('#userMessage').text('Sorry; an error occurred');
+ $('#userMessageDetail').text(responseText.substring(0, 500));
+ $('#userMessage').removeClass('badge-success');
+ $('#userMessage').addClass('badge-danger');
+ }
+ });
+
+ $('#swh-add-forge-requests-list-tab').on('shown.bs.tab', () => {
+ window.location.hash = '#browse-requests';
+ });
+
+ $('#swh-add-forge-tab').on('shown.bs.tab', () => {
+ removeUrlFragment();
+ });
+
+ $('#swh-show-forge-add-requests-list').click(function(e) {
+ $('.nav-tabs a[href="#swh-add-forge-requests-list"]').tab('show');
+ });
+
+ populateRequesBrowseList(); // Load existing requests
+}
+
+export async function populateRequesBrowseList() {
+ $('#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'
+ }
+ ]
+ });
+}
diff --git a/assets/src/bundles/add_forge/index.js b/assets/src/bundles/add_forge/index.js
new file mode 100644
--- /dev/null
+++ b/assets/src/bundles/add_forge/index.js
@@ -0,0 +1,10 @@
+/**
+ * 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 './create-request';
diff --git a/cypress/integration/add-forge-now-request-create.spec.js b/cypress/integration/add-forge-now-request-create.spec.js
new file mode 100644
--- /dev/null
+++ b/cypress/integration/add-forge-now-request-create.spec.js
@@ -0,0 +1,139 @@
+/**
+ * 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);
+}
+
+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 here');
+ });
+
+ 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();
+
+ // Change tab
+ 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');
+ });
+
+ 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', 'Sorry; an error occurred');
+ });
+});
diff --git a/swh/web/add_forge_now/views.py b/swh/web/add_forge_now/views.py
--- a/swh/web/add_forge_now/views.py
+++ b/swh/web/add_forge_now/views.py
@@ -3,22 +3,22 @@
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
-from typing import Any, Dict
+from typing import Any, Dict, List
from django.conf.urls import url
from django.core.paginator import Paginator
from django.db.models import Q
from django.http.request import HttpRequest
from django.http.response import HttpResponse, JsonResponse
+from django.shortcuts import render
+from swh.web.add_forge_now.models import Request as AddForgeRequest
from swh.web.api.views.add_forge_now import (
AddForgeNowRequestPublicSerializer,
AddForgeNowRequestSerializer,
)
from swh.web.auth.utils import ADD_FORGE_MODERATOR_PERMISSION
-from .models import Request as AddForgeRequest
-
def add_forge_request_list_datatables(request: HttpRequest) -> HttpResponse:
"""Dedicated endpoint used by datatables to display the add-forge
@@ -78,10 +78,30 @@
return JsonResponse(table_data)
+FORGE_TYPES: List[str] = [
+ "bitbucket",
+ "cgit",
+ "gitlab",
+ "gitea",
+ "heptapod",
+]
+
+
+def create_request(request):
+ """View to create a new 'add_forge_now' request.
+
+ """
+
+ return render(
+ request, "add_forge_now/create-request.html", {"forge_types": FORGE_TYPES},
+ )
+
+
urlpatterns = [
url(
r"^add-forge/request/list/datatables$",
add_forge_request_list_datatables,
name="add-forge-request-list-datatables",
),
+ url(r"^add-forge/request/create/$", create_request, name="forge-add"),
]
diff --git a/swh/web/templates/add_forge_now/create-request.html b/swh/web/templates/add_forge_now/create-request.html
new file mode 100644
--- /dev/null
+++ b/swh/web/templates/add_forge_now/create-request.html
@@ -0,0 +1,234 @@
+{% 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 %}
+Add forge now – Software Heritage archive
+{% endblock %}
+
+{% block navbar-content %}
+<h4>Request the addition of a forge into the archive</h4>
+{% endblock %}
+
+{% block content %}
+<div class="col-md-12 offset-md-1">
+ <div class="col-md-8">
+ <h5 class="d-flex justify-content-between align-items-center mb-3">
+ </h5>
+ <p style="margin-top: 1rem;">
+ “Add forge now” provides a service for Software Heritage users to save a
+ complete forge in the Software Heritage archive by requesting the addition
+ of the forge URL into the list of regularly visited forges.
+ {% if not user.is_authenticated %}
+ <p>
+ You can submit an “Add forge now” request only when you are authenticated,
+ please login to submit the request.
+ </p>
+ {% endif %}
+ </p>
+ </div>
+
+ <!-- Tabs in the page -->
+ <div class="col-md-8">
+ <ul class="nav nav-tabs">
+ <li class="nav-item"><a class="nav-link active" data-toggle="tab" id="swh-add-forge-tab" href="#swh-add-forge-submit-request">Submit a Request</a></li>
+ <li class="nav-item"><a class="nav-link" data-toggle="tab" id="swh-add-forge-requests-list-tab" href="#swh-add-forge-requests-list">Browse Requests</a></li>
+ </ul>
+
+ <div class="tab-content">
+ <div id="swh-add-forge-submit-request" class="tab-pane active" style="padding-top: 10px;">
+ {% if not user.is_authenticated %}
+ <h3>
+ <p class="text-primary">
+ You must be logged in to submit an add forge request. Please
+ <a id="loginLink" href="{% url 'login' %}?next={% url 'forge-add' %}" class="link-primary">log in here</a>
+ </p>
+ </h3>
+ {% else %}
+
+ <form method="POST" action="{% url 'api-1-add-forge-request-create' %}"
+ id="requestCreateForm" class="collapse show">
+ {% csrf_token %}
+ <div class="form-row">
+ <div class="form-group col-md-5">
+ <label for="swh-input-forge-type">Forge type</label>
+ <select class="form-control" id="swh-input-forge-type" name="forge_type" autofocus>
+ {% for each in forge_types %}
+ <option value={{ each }}>{{ each}}</option>
+ {% endfor %}
+ </select>
+ <small class="form-text text-muted">
+ Supported forge types in software archive.
+ </small>
+ </div>
+
+ <div class="form-group col-md-7">
+ <label for="swh-input-forge-url">Forge URL</label>
+ <input type="text" class="form-control" id="swh-input-forge-url" name="forge_url" required>
+ <small class="form-text text-muted">
+ Remote URL of the forge to list.
+ </small>
+ </div>
+ </div>
+
+ <div class="form-row">
+ <div class="form-group col-md-5">
+ <label for="swh-input-forge-contact-name">Forge contact name</label>
+ <input type="text" class="form-control" name="forge_contact_name" id="swh-input-forge-contact-name" required>
+ <small class="form-text text-muted">
+ Name of the Forge administrator.
+ </small>
+ </div>
+
+ <div class="form-group col-md-7">
+ <label for="swh-input-forge-contact-email">Forge contact email</label>
+ <input type="email" class="form-control" name="forge_contact_email" id="swh-input-forge-contact-email" required>
+ <small class="form-text text-muted">
+ Email of the forge administrator. The given email address will not be used for any purpose outside the “add forge now” process.
+ </small>
+ </div>
+ </div>
+
+ <div class="form-row">
+ <div class="form-group form-check">
+ <input class="form-check-input" type="checkbox"
+ id="swh-input-consent-check" name="consent_to_add_name">
+ <label for="swh-input-consent-check">
+ I consent to add my username in the communication with the forge
+ </label>
+ </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="swh-input-forge-comment" name="forge_contact_comment" rows="3"></textarea>
+ <small class="form-text text-muted">
+ If you wish to leave a comment regarding your request.
+ </small>
+ </div>
+ </div>
+
+ <div class="form-row">
+ <div class="col-md-12">
+ <input id="swh-input-form-submit" type="submit" value="Submit Add Request" class="btn btn-default float-right">
+ </div>
+ </div>
+
+ <div class="form-row">
+ <div class="col-md-12">
+ <h3 class="text-center">
+ <span id="userMessage" class="badge"></span>
+ </h3>
+ <p class="text-center">
+ <span id="userMessageDetail"></span>
+ </p>
+ </div>
+ </div>
+ </form>
+ <div class="panel panel-info">
+ <div class="panel-heading">
+ <a data-toggle="collapse" href="#swh-forge-add-request-help" role="button" aria-expanded="false" class="text-primary">
+ For more information
+ </a>
+ </div>
+ <div id="swh-forge-add-request-help" class="collapse panel-body">
+ <p>
+ Once submitted, your "add forge" request can either be:
+ </p>
+ <ul>
+ <li>
+ <strong>Pending:</strong>
+ the request was submitted and is waiting for a moderator
+ to check its validity.
+ </li>
+
+ <li>
+ <strong>Waiting for feedback:</strong>
+ the request was processed by a moderator
+ and the forge was contacted, the request is waiting for feedback from the
+ forge.
+ </li>
+
+ <li>
+ <strong>Feedback to handle:</strong>
+ the forge has responded to the request and
+ there is feedback to handle for the request.</li>
+
+ <li>
+ <strong>Accepted:</strong>
+ the request has been accepted and waiting to be
+ scheduled.
+ </li>
+
+ <li>
+ <strong>Scheduled:</strong>
+ the request has been scheduled is considered
+ done.
+ </li>
+
+ <li>
+ <strong>First listing done:</strong>
+ The first listing of the forge is
+ completed.
+ </li>
+
+ <li>
+ <strong>First origin loaded:</strong>
+ The first origin or repository processed by
+ loader and archived (using a search query).
+ </li>
+
+ <li><strong>Rejected:</strong> the request is not a valid request and is rejected by a
+ Software Heritage moderator.</li>
+
+ <li><strong>Denied:</strong> the forge has requested not to archive the forge.</li>
+
+ <li><strong>Suspended:</strong> the request is for a forge with a non supported
+ VCS.</li>
+ </ul>
+ <p>
+ Once a add request has been submitted, you can follow its current status in
+ the <a id="swh-show-forge-add-requests-list" href="#">
+ submitted requests list
+ </a>. This process is depending on human interactions and might take a few days to be
+ handled (it primarily depends on the response time of the forge).
+ </p>
+ </div>
+ </div>
+ {% endif %}
+ </div>
+ <div id="swh-add-forge-requests-list" class="tab-pane fade show">
+ <table id="add-forge-request-browse" class="table swh-table swh-table-striped" style="width: 100%;">
+ <thead>
+ <tr>
+ <th>Submission date</th>
+ <th>Forge type</th>
+ <th>Forge URL</th>
+ <th>Status</th>
+ </tr>
+ </thead>
+ </table>
+ <div id="add-forge-browse-request-error"></div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+ swh.add_forge.onCreateRequestPageLoad();
+</script>
+
+{% endblock %}
diff --git a/swh/web/templates/layout.html b/swh/web/templates/layout.html
--- a/swh/web/templates/layout.html
+++ b/swh/web/templates/layout.html
@@ -209,6 +209,12 @@
<p>Save code now</p>
</a>
</li>
+ <li class="nav-item swh-origin-save-item" title="Request adding a new forge listing">
+ <a href="{% url 'forge-add' %}" class="nav-link swh-forge-add-link">
+ <i style="color: #e20026;" class="nav-icon mdi mdi-24px mdi-anvil"></i>
+ <p>Add forge now</p>
+ </a>
+ </li>
<li class="nav-item swh-help-item" title="How to browse the archive ?">
<a href="#" class="nav-link swh-help-link" onclick="swh.guided_tour.guidedTourButtonClick(event)">
<i style="color: #e20026;" class="nav-icon mdi mdi-24px mdi-help-circle"></i>

File Metadata

Mime Type
text/plain
Expires
Thu, Jul 3, 1:34 PM (6 d, 10 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3216754

Event Timeline