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@swh-web.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@swh-web.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@swh-web.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@swh-web.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@swh-web.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@swh-web.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-moderation.spec.js b/cypress/integration/add-forge-now-moderation.spec.js new file mode 100644 --- /dev/null +++ b/cypress/integration/add-forge-now-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 moderationAddForgeUrl; +let listAddForgeRequestsUrl; + +function logout() { + cy.contains('a', 'logout') + .click(); +} + +describe('Test "Add Forge Now" moderation Login/logout', function() { + before(function() { + moderationAddForgeUrl = this.Urls.moderation_forge_add(); + }); + + it('should redirect to default page', function() { + cy.visit(moderationAddForgeUrl) + .get('input[name="username"]') + .type('admin') + .get('input[name="password"]') + .type('admin') + .get('.container form') + .submit(); + + cy.location('pathname') + .should('be.equal', moderationAddForgeUrl); + }); + + it('should redirect to correct page after login', function() { + cy.visit(moderationAddForgeUrl) + .location('pathname') + .should('be.equal', defaultRedirect); + + cy.adminLogin(); + cy.visit(moderationAddForgeUrl) + .location('pathname') + .should('be.equal', moderationAddForgeUrl); + + logout(); + }); + + it('should not display moderation link in sidebar when anonymous', function() { + cy.visit(moderationAddForgeUrl); + cy.get(`.sidebar a[href="${moderationAddForgeUrl}"]`) + .should('not.exist'); + }); + + it('should not display moderation link when connected as unprivileged user', function() { + cy.userLogin(); + cy.visit(moderationAddForgeUrl); + + cy.get(`.sidebar a[href="${moderationAddForgeUrl}"]`) + .should('not.exist'); + + }); + + it('should display moderation link in sidebar when connected as privileged user', function() { + cy.moderatorLogin(); + cy.visit(moderationAddForgeUrl); + + cy.get(`.sidebar a[href="${moderationAddForgeUrl}"]`) + .should('exist'); + }); + + it('should display moderation link in sidebar when connected as staff member', function() { + cy.adminLogin(); + cy.visit(moderationAddForgeUrl); + + cy.get(`.sidebar a[href="${moderationAddForgeUrl}"]`) + .should('exist'); + }); +}); + +describe('Test "Add Forge Now" moderation listing', function() { + before(function() { + moderationAddForgeUrl = this.Urls.moderation_forge_add(); + 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.moderatorLogin(); + cy.visit(moderationAddForgeUrl); + + 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/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 @@ -5,7 +5,9 @@ from typing import Any, Dict, List +from django.conf import settings from django.conf.urls import url +from django.contrib.auth.decorators import user_passes_test from django.core.paginator import Paginator from django.db.models import Q from django.http.request import HttpRequest @@ -13,6 +15,7 @@ from django.shortcuts import render from swh.web.add_forge_now.models import Request as AddForgeRequest +from swh.web.admin.adminurls import AdminUrls, admin_route from swh.web.api.views.add_forge_now import ( AddForgeNowRequestPublicSerializer, AddForgeNowRequestSerializer, @@ -97,6 +100,22 @@ ) +def _can_access_moderation(user): + return user.is_staff or user.has_perm(ADD_FORGE_MODERATOR_PERMISSION) + + +@admin_route( + r"moderation/", view_name="moderation-forge-add", +) +@user_passes_test(_can_access_moderation, login_url=settings.LOGIN_URL) +def moderation_dashboard(request): + """Moderation dashboard to allow listing current requests. + + """ + existing = AddForgeRequest.objects.all() + return render(request, "add_forge_now/moderation.html", {"existing": existing},) + + urlpatterns = [ url( r"^add-forge/request/list/datatables$", @@ -105,3 +124,5 @@ ), url(r"^add-forge/request/create$", create_request, name="forge-add"), ] + +urlpatterns += AdminUrls.get_url_patterns() 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/moderation.html b/swh/web/templates/add_forge_now/moderation.html new file mode 100644 --- /dev/null +++ b/swh/web/templates/add_forge_now/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 %} +
ID | +Submission date | +Forge type | +Forge URL | +Status | +
---|
Add forge now moderation
+ +