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 @@ -8,3 +8,4 @@ // bundle for add forge views export * from './create-request'; +export * from './moderation-dashboard'; diff --git a/assets/src/bundles/add_forge/moderation-dashboard.js b/assets/src/bundles/add_forge/moderation-dashboard.js new file mode 100644 --- /dev/null +++ b/assets/src/bundles/add_forge/moderation-dashboard.js @@ -0,0 +1,50 @@ +/** + * 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 + */ + +export function onModerationPageLoad() { + populateModerationList(); +} + +export async function populateModerationList() { + $('#swh-add-forge-now-moderation-list') + .on('error.dt', (e, settings, techNote, message) => { + $('#swh-add-forge-now-moderation-list-error').text(message); + }) + .DataTable({ + serverSide: true, + processing: true, + searching: false, + 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: 'id', + name: 'id' + }, + { + 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/cypress/fixtures/add-forge-now-requests.json b/cypress/fixtures/add-forge-now-requests.json new file mode 100644 --- /dev/null +++ b/cypress/fixtures/add-forge-now-requests.json @@ -0,0 +1,79 @@ +{ + "recordsTotal": 6, + "draw": 1, + "recordsFiltered": 6, + "data": [ + { + "id": 1, + "status": "PENDING", + "submission_date": "2022-03-09T14:06:09.092714Z", + "submitter_name": "user", + "submitter_email": "user@example.org", + "forge_type": "cgit", + "forge_url": "cgit.org", + "forge_contact_email": "cgit@cgit.org", + "forge_contact_name": "cgit", + "forge_contact_comment": "please" + }, + { + "id": 2, + "status": "PENDING", + "submission_date": "2022-03-09T14:07:01.442033Z", + "submitter_name": "user", + "submitter_email": "user@example.org", + "forge_type": "cgit", + "forge_url": "cgit2.org", + "forge_contact_email": "cgit2@cgit.org", + "forge_contact_name": "cgit2", + "forge_contact_comment": "please" + }, + { + "id": 3, + "status": "ACCEPTED", + "submission_date": "2022-03-11T14:53:58.576374Z", + "submitter_name": "admin", + "submitter_email": "admin@example.org", + "forge_type": "gitlab", + "forge_url": "https://gitlab-stuff.org", + "forge_contact_email": "admin@gitlab-stuff.org", + "forge_contact_name": "admin", + "forge_contact_comment": "hello" + }, + { + "id": 4, + "status": "PENDING", + "submission_date": "2022-03-15T08:53:29.845342Z", + "submitter_name": "admin", + "submitter_email": "admin@example.org", + "forge_type": "gitlab", + "forge_url": "https://gitlab.com/blah/dot-files", + "forge_contact_email": "blah@org.org", + "forge_contact_name": "blah", + "forge_contact_comment": "blah" + }, + { + "id": 5, + "status": "PENDING", + "submission_date": "2022-03-15T08:54:58.254710Z", + "submitter_name": "admin", + "submitter_email": "admin@example.org", + "forge_type": "heptapod", + "forge_url": "heptapod0", + "forge_contact_email": "pod@hepta.org", + "forge_contact_name": "hepta", + "forge_contact_comment": "heh" + }, + { + "id": 6, + "status": "PENDING", + "submission_date": "2022-03-15T08:55:16.984753Z", + "submitter_name": "admin", + "submitter_email": "admin@example.org", + "forge_type": "heptapod", + "forge_url": "heptapod1", + "forge_contact_email": "pod@hepta1.org", + "forge_contact_name": "hepta1", + "forge_contact_comment": "hi" + } + ] +} diff --git a/cypress/integration/add-forge-now-requests-moderation.spec.js b/cypress/integration/add-forge-now-requests-moderation.spec.js new file mode 100644 --- /dev/null +++ b/cypress/integration/add-forge-now-requests-moderation.spec.js @@ -0,0 +1,122 @@ +/** + * 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 defaultRedirect = '/admin/login/'; + +let addForgeModerationUrl; +let listAddForgeRequestsUrl; + +function logout() { + cy.contains('a', 'logout') + .click(); +} + +describe('Test "Add Forge Now" moderation Login/logout', function() { + before(function() { + addForgeModerationUrl = this.Urls.add_forge_now_requests_moderation(); + }); + + it('should redirect to default page', function() { + cy.visit(addForgeModerationUrl) + .get('input[name="username"]') + .type('admin') + .get('input[name="password"]') + .type('admin') + .get('.container form') + .submit(); + + cy.location('pathname') + .should('be.equal', addForgeModerationUrl); + }); + + it('should redirect to correct page after login', function() { + cy.visit(addForgeModerationUrl) + .location('pathname') + .should('be.equal', defaultRedirect); + + cy.adminLogin(); + cy.visit(addForgeModerationUrl) + .location('pathname') + .should('be.equal', addForgeModerationUrl); + + logout(); + }); + + it('should not display moderation link in sidebar when anonymous', function() { + cy.visit(addForgeModerationUrl); + cy.get(`.sidebar a[href="${addForgeModerationUrl}"]`) + .should('not.exist'); + }); + + it('should not display moderation link when connected as unprivileged user', function() { + cy.userLogin(); + cy.visit(addForgeModerationUrl); + + cy.get(`.sidebar a[href="${addForgeModerationUrl}"]`) + .should('not.exist'); + + }); + + it('should display moderation link in sidebar when connected as privileged user', function() { + cy.addForgeModeratorLogin(); + cy.visit(addForgeModerationUrl); + + cy.get(`.sidebar a[href="${addForgeModerationUrl}"]`) + .should('exist'); + }); + + it('should display moderation link in sidebar when connected as staff member', function() { + cy.adminLogin(); + cy.visit(addForgeModerationUrl); + + cy.get(`.sidebar a[href="${addForgeModerationUrl}"]`) + .should('exist'); + }); +}); + +describe('Test "Add Forge Now" moderation listing', function() { + before(function() { + addForgeModerationUrl = this.Urls.add_forge_now_requests_moderation(); + listAddForgeRequestsUrl = this.Urls.add_forge_request_list_datatables(); + }); + + it('should list add-forge-now requests', function() { + cy.intercept(`${listAddForgeRequestsUrl}**`, {fixture: 'add-forge-now-requests'}).as('listRequests'); + + let expectedRequests; + cy.readFile('cypress/fixtures/add-forge-now-requests.json').then((result) => { + expectedRequests = result['data']; + }); + + cy.addForgeModeratorLogin(); + cy.visit(addForgeModerationUrl); + + cy.wait('@listRequests').then((xhr) => { + cy.log('response:', xhr.response); + cy.log(xhr.response.body); + const requests = xhr.response.body.data; + cy.log('Requests: ', requests); + expect(requests.length).to.equal(expectedRequests.length); + + cy.get('#swh-add-forge-now-moderation-list').find('tbody > tr').as('rows'); + + // only 2 entries + cy.get('@rows').each((row, idx, collection) => { + const request = requests[idx]; + const expectedRequest = expectedRequests[idx]; + assert.isNotNull(request); + assert.isNotNull(expectedRequest); + expect(request.id).to.be.equal(expectedRequest['id']); + expect(request.status).to.be.equal(expectedRequest['status']); + expect(request.submission_date).to.be.equal(expectedRequest['submission_date']); + expect(request.forge_type).to.be.equal(expectedRequest['forge_type']); + expect(request.forge_url).to.be.equal(expectedRequest['forge_url']); + }); + }); + }); + +}); diff --git a/cypress/support/index.js b/cypress/support/index.js --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -60,6 +60,10 @@ return loginUser('deposit', 'deposit'); }); +Cypress.Commands.add('addForgeModeratorLogin', () => { + return loginUser('add-forge-moderator', 'add-forge-moderator'); +}); + function mockCostlyRequests() { cy.intercept('https://status.softwareheritage.org/**', { body: { diff --git a/swh/web/admin/add_forge_now.py b/swh/web/admin/add_forge_now.py new file mode 100644 --- /dev/null +++ b/swh/web/admin/add_forge_now.py @@ -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 + +from django.conf import settings +from django.contrib.auth.decorators import user_passes_test +from django.shortcuts import render + +from swh.web.add_forge_now.models import Request as AddForgeRequest +from swh.web.admin.adminurls import admin_route +from swh.web.auth.utils import ADD_FORGE_MODERATOR_PERMISSION + + +def _can_access_moderation(user): + return user.is_staff or user.has_perm(ADD_FORGE_MODERATOR_PERMISSION) + + +@admin_route( + r"add-forge/requests/", view_name="add-forge-now-requests-moderation", +) +@user_passes_test(_can_access_moderation, login_url=settings.LOGIN_URL) +def add_forge_now_requests_moderation_dashboard(request): + """Moderation dashboard to allow listing current requests. + + """ + existing = AddForgeRequest.objects.all() + return render( + request, "add_forge_now/requests-moderation.html", {"existing": existing}, + ) diff --git a/swh/web/admin/urls.py b/swh/web/admin/urls.py --- a/swh/web/admin/urls.py +++ b/swh/web/admin/urls.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018 The Software Heritage developers +# Copyright (C) 2018-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 @@ -7,6 +7,7 @@ from django.contrib.auth.views import LoginView from django.shortcuts import redirect +import swh.web.admin.add_forge_now # noqa from swh.web.admin.adminurls import AdminUrls import swh.web.admin.deposit # noqa import swh.web.admin.origin_save # noqa diff --git a/swh/web/common/utils.py b/swh/web/common/utils.py --- a/swh/web/common/utils.py +++ b/swh/web/common/utils.py @@ -27,7 +27,10 @@ from django.urls import resolve from django.urls import reverse as django_reverse -from swh.web.auth.utils import ADMIN_LIST_DEPOSIT_PERMISSION +from swh.web.auth.utils import ( + ADD_FORGE_MODERATOR_PERMISSION, + ADMIN_LIST_DEPOSIT_PERMISSION, +) from swh.web.common.exc import BadInputExc from swh.web.common.typing import QueryParameters from swh.web.config import SWH_WEB_SERVER_NAME, get_config, search @@ -311,6 +314,7 @@ "swh_web_version": get_distribution("swh.web").version, "iframe_mode": False, "ADMIN_LIST_DEPOSIT_PERMISSION": ADMIN_LIST_DEPOSIT_PERMISSION, + "ADD_FORGE_MODERATOR_PERMISSION": ADD_FORGE_MODERATOR_PERMISSION, } diff --git a/swh/web/templates/add_forge_now/requests-moderation.html b/swh/web/templates/add_forge_now/requests-moderation.html new file mode 100644 --- /dev/null +++ b/swh/web/templates/add_forge_now/requests-moderation.html @@ -0,0 +1,45 @@ +{% 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 moderation

+{% endblock %} + +{% block content %} +
+
+ + + + + + + + + + +
IDSubmission dateForge typeForge URLStatus
+
+
+
+ + + +{% 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 @@ -231,6 +231,14 @@ {% endif %} + {% if user.is_staff or ADD_FORGE_MODERATOR_PERMISSION in user.get_all_permissions %} + + {% endif %} {% if user.is_staff or ADMIN_LIST_DEPOSIT_PERMISSION in user.get_all_permissions %}