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,92 @@ +/** + * 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 (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'); + } + }); + + $(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' + } + ] + }); + requestBrowseTable.draw(); +} 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'); + }); + + 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'); + }); + + 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 %} +
+ “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 %} +
+ You can submit an “Add forge now” request only when you are authenticated, + please login to submit the request. +
+ {% endif %} + ++ You must be logged in to submit an add forge request. Please + log in +
+ + {% else %} + + ++ Once submitted, your "add forge" request can either be: +
++ Once a add request has been submitted, you can follow its current status in + the + submitted requests list + . 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). +
+Submission date | +Forge type | +Forge URL | +Status | +
---|
Save code now
+Add forge now
+ +