diff --git a/cypress/e2e/add-forge-now-request-create.cy.js b/cypress/e2e/add-forge-now-request-create.cy.js index ba72ccd8..eed84a42 100644 --- a/cypress/e2e/add-forge-now-request-create.cy.js +++ b/cypress/e2e/add-forge-now-request-create.cy.js @@ -1,293 +1,332 @@ /** * 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); if (url) { cy.get('#swh-input-forge-url').clear().type(url); } cy.get('#swh-input-forge-contact-name').clear().type(contact); cy.get('#swh-input-forge-contact-email').clear().type(email); if (comment) { cy.get('#swh-input-forge-comment').clear().type(comment); } cy.get('#swh-input-consent-check').click({force: consent === 'on'}); } function submitForm() { cy.get('#requestCreateForm input[type=submit]').click(); cy.get('#requestCreateForm').then($form => { if ($form[0].checkValidity()) { cy.wait('@addForgeRequestCreate'); } }); } function initTest(testEnv) { testEnv.addForgeNowUrl = testEnv.Urls.forge_add_create(); testEnv.addForgeNowRequestCreateUrl = testEnv.Urls.api_1_add_forge_request_create(); testEnv.listAddForgeRequestsUrl = testEnv.Urls.add_forge_request_list_datatables(); cy.intercept('POST', testEnv.addForgeNowRequestCreateUrl + '**') .as('addForgeRequestCreate'); cy.intercept(testEnv.listAddForgeRequestsUrl + '**') .as('addForgeRequestsList'); } describe('Browse requests list tests', function() { beforeEach(function() { initTest(this); }); it('should not show user requests filter checkbox for anonymous users', function() { cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#swh-add-forge-user-filter').should('not.exist'); }); it('should show user requests filter checkbox for authenticated users', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#swh-add-forge-user-filter').should('exist').should('be.checked'); }); it('should only display user requests when filter is activated', function() { // Clean up previous state cy.task('db:add_forge_now:delete'); // 'user2' logs in and create requests cy.user2Login(); cy.visit(this.addForgeNowUrl); // create requests for the user 'user' populateForm('gitlab', 'https://gitlab.org', 'admin', 'admin@example.org', 'on', ''); submitForm(); // user requests filter checkbox should be in the DOM cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#swh-add-forge-user-filter').should('exist').should('be.checked'); // check unfiltered user requests cy.get('tbody tr').then(rows => { expect(rows.length).to.eq(1); }); // user1 logout cy.contains('a', 'logout').click(); // user logs in cy.userLogin(); cy.visit(this.addForgeNowUrl); populateForm('gitea', 'https://gitea.org', 'admin', 'admin@example.org', 'on', ''); submitForm(); populateForm('cgit', 'https://cgit.org', 'admin', 'admin@example.org', 'on', ''); submitForm(); // user requests filter checkbox should be in the DOM cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#swh-add-forge-user-filter').should('exist').should('be.checked'); cy.wait('@addForgeRequestsList'); // check unfiltered user requests cy.get('tbody tr').then(rows => { expect(rows.length).to.eq(2); }); cy.get('#swh-add-forge-user-filter') .uncheck({force: true}); cy.wait('@addForgeRequestsList'); // Users now sees everything cy.get('tbody tr').then(rows => { expect(rows.length).to.eq(2 + 1); }); }); + + it('should display search link when first forge origin has been loaded', function() { + const forgeUrl = 'https://cgit.example.org'; + cy.intercept(this.listAddForgeRequestsUrl + '**', {body: { + 'recordsTotal': 1, + 'draw': 1, + 'recordsFiltered': 1, + 'data': [ + { + 'id': 1, + 'inbound_email_address': 'add-forge-now+15.yPalKD34nGJ-FYHwKXdmPQVkQ2c@example.org', + 'status': 'FIRST_ORIGIN_LOADED', + 'submission_date': '2022-09-22T05:31:47.566000Z', + 'submitter_name': 'johndoe', + 'submitter_email': 'johndoe@example.org', + 'submitter_forward_username': true, + 'forge_type': 'cgit', + 'forge_url': forgeUrl, + 'forge_contact_email': 'admin@example.org', + 'forge_contact_name': 'Admin', + 'last_modified_date': '2022-09-22T05:31:47.576000Z' + } + ] + }}).as('addForgeRequestsList'); + + cy.visit(this.addForgeNowUrl); + + cy.get('#swh-add-forge-requests-list-tab').click(); + + cy.wait('@addForgeRequestsList'); + + let originsSearchUrl = `${this.Urls.browse_search()}?q=${encodeURIComponent(forgeUrl)}`; + originsSearchUrl += '&with_visit=true&with_content=true'; + + cy.get('.swh-search-forge-origins') + .should('have.attr', 'href', originsSearchUrl); + + }); + }); describe('Test add-forge-request creation', function() { beforeEach(function() { initTest(this); }); it('should show all the 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'); cy.get('#swh-add-forge-requests-help-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_create()}`); }); 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-requests-help-tab') .should('not.have.class', 'active'); cy.url() .should('include', `${this.Urls.forge_add_list()}`); cy.get('.swh-add-forge-now-item') .should('have.class', 'active'); cy.get('#swh-add-forge-requests-help-tab').click(); cy.get('#swh-add-forge-tab') .should('not.have.class', 'active'); cy.get('#swh-add-forge-requests-list-tab') .should('not.have.class', 'active'); cy.get('#swh-add-forge-requests-help-tab') .should('have.class', 'active'); cy.url() .should('include', `${this.Urls.forge_add_help()}`); cy.get('.swh-add-forge-now-item') .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'); cy.get('#swh-add-forge-requests-help-tab') .should('not.have.class', 'active'); cy.url() .should('include', `${this.Urls.forge_add_create()}`); cy.get('.swh-add-forge-now-item') .should('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.exist'); }); it('should update browse list on successful submission', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); populateForm('bitbucket', 'https://gitlab.com', 'test', 'test@example.com', 'on', 'test comment'); submitForm(); cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); cy.wait('@addForgeRequestsList'); 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', 'https://gitlab.com', 'test', 'test@example.com', 'on', 'test comment'); submitForm(); submitForm(); // Submitting the same data again cy.get('#userMessage') .should('have.class', 'badge-danger') .should('contain', 'already exists'); }); it('should show error message', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); populateForm( 'bitbucket', '', 'test', 'test@example.com', 'off', 'comment' ); submitForm(); cy.get('#requestCreateForm').then( $form => expect($form[0].checkValidity()).to.be.false ); }); it('should not validate form when forge URL is invalid', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); populateForm('bitbucket', 'bitbucket.org', 'test', 'test@example.com', 'on', 'test comment'); submitForm(); cy.get('#swh-input-forge-url') .then(input => { assert.isFalse(input[0].checkValidity()); }); }); }); diff --git a/cypress/e2e/add-forge-now-requests-moderation.cy.js b/cypress/e2e/add-forge-now-requests-moderation.cy.js index c1ced073..99b55107 100644 --- a/cypress/e2e/add-forge-now-requests-moderation.cy.js +++ b/cypress/e2e/add-forge-now-requests-moderation.cy.js @@ -1,120 +1,161 @@ /** * 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 logout() { cy.contains('a', 'logout') .click(); } describe('Test "Add Forge Now" moderation Login/logout', function() { beforeEach(function() { this.addForgeModerationUrl = this.Urls.add_forge_now_requests_moderation(); }); it('should redirect to default page', function() { cy.visit(this.addForgeModerationUrl) .get('input[name="username"]') .type('admin') .get('input[name="password"]') .type('admin') .get('.container form button[type=submit]') .click(); cy.location('pathname') .should('be.equal', this.addForgeModerationUrl); }); it('should redirect to correct page after login', function() { cy.visit(this.addForgeModerationUrl) .location('pathname') .should('be.equal', this.Urls.login()); cy.adminLogin(); cy.visit(this.addForgeModerationUrl) .location('pathname') .should('be.equal', this.addForgeModerationUrl); logout(); }); it('should not display moderation link in sidebar when anonymous', function() { cy.visit(this.addForgeModerationUrl); cy.get(`.sidebar a[href="${this.addForgeModerationUrl}"]`) .should('not.exist'); }); it('should not display moderation link when connected as unprivileged user', function() { cy.userLogin(); cy.visit(this.addForgeModerationUrl); cy.get(`.sidebar a[href="${this.addForgeModerationUrl}"]`) .should('not.exist'); }); it('should display moderation link in sidebar when connected as privileged user', function() { cy.addForgeModeratorLogin(); cy.visit(this.addForgeModerationUrl); cy.get(`.sidebar a[href="${this.addForgeModerationUrl}"]`) .should('exist'); }); it('should display moderation link in sidebar when connected as staff member', function() { cy.adminLogin(); cy.visit(this.addForgeModerationUrl); cy.get(`.sidebar a[href="${this.addForgeModerationUrl}"]`) .should('exist'); }); }); describe('Test "Add Forge Now" moderation listing', function() { beforeEach(function() { this.addForgeModerationUrl = this.Urls.add_forge_now_requests_moderation(); this.listAddForgeRequestsUrl = this.Urls.add_forge_request_list_datatables(); }); it('should list add-forge-now requests', function() { cy.intercept(`${this.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(this.addForgeModerationUrl); cy.get('.swh-add-forge-now-moderation-item') .should('have.class', 'active'); 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']); }); }); }); + it('should display useful links in requests table', function() { + const forgeUrl = 'https://cgit.example.org'; + const requestId = 1; + cy.intercept(this.listAddForgeRequestsUrl + '**', {body: { + 'recordsTotal': 1, + 'draw': 1, + 'recordsFiltered': 1, + 'data': [ + { + 'id': requestId, + 'inbound_email_address': 'add-forge-now+15.yPalKD34nGJ-FYHwKXdmPQVkQ2c@example.org', + 'status': 'FIRST_ORIGIN_LOADED', + 'submission_date': '2022-09-22T05:31:47.566000Z', + 'submitter_name': 'johndoe', + 'submitter_email': 'johndoe@example.org', + 'submitter_forward_username': true, + 'forge_type': 'cgit', + 'forge_url': forgeUrl, + 'forge_contact_email': 'admin@example.org', + 'forge_contact_name': 'Admin', + 'last_modified_date': '2022-09-22T05:31:47.576000Z', + 'last_moderator': 'foo@softwareheritage.org' + } + ] + }}).as('addForgeRequestsList'); + + cy.addForgeModeratorLogin(); + cy.visit(this.addForgeModerationUrl); + + cy.wait('@addForgeRequestsList'); + + let originsSearchUrl = `${this.Urls.browse_search()}?q=${encodeURIComponent(forgeUrl)}`; + originsSearchUrl += '&with_visit=true&with_content=true'; + + cy.get('.swh-forge-request-dashboard-link') + .should('have.attr', 'href', this.Urls.add_forge_now_request_dashboard(requestId)); + + cy.get('.swh-search-forge-origins') + .should('have.attr', 'href', originsSearchUrl); + }); + }); diff --git a/swh/web/add_forge_now/assets/create-request.js b/swh/web/add_forge_now/assets/create-request.js index b51503a2..8ea536af 100644 --- a/swh/web/add_forge_now/assets/create-request.js +++ b/swh/web/add_forge_now/assets/create-request.js @@ -1,134 +1,147 @@ /** * 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 {swhSpinnerSrc} from 'utils/constants'; import { csrfPost, errorMessageFromResponse, genLink, getHumanReadableDate, handleFetchError, validateUrl } from 'utils/functions'; import userRequestsFilterCheckboxFn from 'utils/requests-filter-checkbox.ejs'; let requestBrowseTable; const addForgeCheckboxId = 'swh-add-forge-user-filter'; const userRequestsFilterCheckbox = userRequestsFilterCheckboxFn({ 'inputId': addForgeCheckboxId, 'checked': true // by default, display only user requests }); 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 (errorResponse) { $('#userMessageDetail').empty(); let errorMessage; const errorData = await errorResponse.json(); // if (errorResponse.content_type === 'text/plain') { // does not work? if (errorResponse.status === 409) { errorMessage = errorData; } else { // assuming json response // const exception = errorData['exception']; errorMessage = errorMessageFromResponse( errorData, 'An unknown error occurred during the request creation'); } $('#userMessage').text(errorMessage); $('#userMessage').removeClass('badge-success'); $('#userMessage').addClass('badge-danger'); } }); 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, language: { processing: `` }, retrieve: true, searching: true, // Layout configuration, see [1] for more details // [1] https://datatables.net/reference/option/dom dom: '<"row"<"col-sm-3"l><"col-sm-6 text-left user-requests-filter"><"col-sm-3"f>>' + '<"row"<"col-sm-12"tr>>' + '<"row"<"col-sm-5"i><"col-sm-7"p>>', ajax: { 'url': Urls.add_forge_request_list_datatables(), data: (d) => { const checked = $(`#${addForgeCheckboxId}`).prop('checked'); // If this function is called while the page is loading, 'checked' is // undefined. As the checkbox defaults to being checked, coerce this to true. if (swh.webapp.isUserLoggedIn() && (checked === undefined || checked)) { d.user_requests_only = '1'; } } }, fnInitComplete: function() { if (swh.webapp.isUserLoggedIn()) { $('div.user-requests-filter').html(userRequestsFilterCheckbox); $(`#${addForgeCheckboxId}`).on('change', () => { requestBrowseTable.draw(); }); } }, columns: [ { data: 'submission_date', name: 'submission_date', render: getHumanReadableDate }, { data: 'forge_type', name: 'forge_type', render: $.fn.dataTable.render.text() }, { data: 'forge_url', name: 'forge_url', render: (data, type, row) => { const sanitizedURL = $.fn.dataTable.render.text().display(data); return genLink(sanitizedURL, type, true); } }, { data: 'status', name: 'status', render: function(data, type, row, meta) { return swh.add_forge_now.formatRequestStatusName(data); } + }, + { + render: (data, type, row) => { + if (row.status === 'FIRST_ORIGIN_LOADED') { + const sanitizedURL = $.fn.dataTable.render.text().display(row.forge_url); + let originsSearchUrl = `${Urls.browse_search()}?q=${encodeURIComponent(sanitizedURL)}`; + originsSearchUrl += '&with_visit=true&with_content=true'; + return `' + + ''; + } + return ''; + } } ], order: [[0, 'desc']] }); } export function validateForgeUrl(input) { let customValidity = ''; if (!validateUrl(input.value.trim(), ['http:', 'https:'])) { customValidity = 'The provided forge URL is not valid.'; } input.setCustomValidity(customValidity); } diff --git a/swh/web/add_forge_now/assets/moderation-dashboard.js b/swh/web/add_forge_now/assets/moderation-dashboard.js index ac22ec43..f84ff219 100644 --- a/swh/web/add_forge_now/assets/moderation-dashboard.js +++ b/swh/web/add_forge_now/assets/moderation-dashboard.js @@ -1,75 +1,93 @@ /** * 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 {genLink, getHumanReadableDate} from 'utils/functions'; 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: true, 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', render: function(data, type, row, meta) { const dashboardUrl = Urls.add_forge_now_request_dashboard(data); return `${data}`; } }, { data: 'submission_date', name: 'submission_date', render: getHumanReadableDate }, { data: 'forge_type', name: 'forge_type', render: $.fn.dataTable.render.text() }, { data: 'forge_url', name: 'forge_url', render: (data, type, row) => { const sanitizedURL = $.fn.dataTable.render.text().display(data); return genLink(sanitizedURL, type, true); } }, { data: 'last_moderator', name: 'last_moderator', render: $.fn.dataTable.render.text() }, { data: 'last_modified_date', name: 'last_modified_date', render: getHumanReadableDate }, { data: 'status', name: 'status', render: function(data, type, row, meta) { return swh.add_forge_now.formatRequestStatusName(data); } + }, + { + render: (data, type, row) => { + let html = '
'; + const dashboardUrl = Urls.add_forge_now_request_dashboard(row.id); + html += `` + + ''; + if (row.status === 'FIRST_ORIGIN_LOADED') { + const sanitizedURL = $.fn.dataTable.render.text().display(row.forge_url); + let originsSearchUrl = `${Urls.browse_search()}?q=${encodeURIComponent(sanitizedURL)}`; + originsSearchUrl += '&with_visit=true&with_content=true'; + html += `' + + ''; + } + html += '
'; + return html; + } } ], order: [[0, 'desc']] }); } diff --git a/swh/web/add_forge_now/templates/add-forge-list.html b/swh/web/add_forge_now/templates/add-forge-list.html index 1987168f..d7d44450 100644 --- a/swh/web/add_forge_now/templates/add-forge-list.html +++ b/swh/web/add_forge_now/templates/add-forge-list.html @@ -1,24 +1,25 @@ {% extends "./add-forge-common.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 %} {% block tab_content %}
+
Submission date Forge type Forge URL Status
{% endblock %} diff --git a/swh/web/add_forge_now/templates/add-forge-requests-moderation.html b/swh/web/add_forge_now/templates/add-forge-requests-moderation.html index 588f153a..6e1d3d3f 100644 --- a/swh/web/add_forge_now/templates/add-forge-requests-moderation.html +++ b/swh/web/add_forge_now/templates/add-forge-requests-moderation.html @@ -1,48 +1,49 @@ {% 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_now' %} {% endblock %} {% block title %}{{ heading }} – Software Heritage archive{% endblock %} {% block navbar-content %}

Add forge now moderation

{% endblock %} {% block content %}
+
ID Submission date Forge type Forge URL Moderator Name Last Modified Date Status

{% endblock %}