diff --git a/Makefile.local b/Makefile.local --- a/Makefile.local +++ b/Makefile.local @@ -34,7 +34,7 @@ run-migrations-test: rm -f swh/web/settings/testdb.sqlite3 django-admin migrate --settings=swh.web.settings.tests -v0 2>/dev/null - cat swh/web/tests/create_test_admin.py | django-admin shell --settings=swh.web.settings.tests + cat swh/web/tests/create_test_users.py | django-admin shell --settings=swh.web.settings.tests .PHONY: clear-memcached clear-memcached: diff --git a/assets/src/bundles/save/index.js b/assets/src/bundles/save/index.js --- a/assets/src/bundles/save/index.js +++ b/assets/src/bundles/save/index.js @@ -33,6 +33,16 @@ }); } +const userRequestsFilterCheckbox = ` +
+ + +
+`; + export function initOriginSave() { $(document).ready(() => { @@ -58,8 +68,26 @@ language: { processing: `` }, - ajax: Urls.origin_save_requests_list('all'), + ajax: { + url: Urls.origin_save_requests_list('all'), + data: (d) => { + if (swh.webapp.isUserLoggedIn() && $('#swh-save-requests-user-filter').prop('checked')) { + d.user_requests_only = '1'; + } + } + }, searchDelay: 1000, + 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>>', + fnInitComplete: function() { + if (swh.webapp.isUserLoggedIn()) { + $('div.user-requests-filter').html(userRequestsFilterCheckbox); + $('#swh-save-requests-user-filter').on('change', () => { + saveRequestsTable.draw(); + }); + } + }, columns: [ { data: 'save_request_date', diff --git a/cypress/integration/origin-save.spec.js b/cypress/integration/origin-save.spec.js --- a/cypress/integration/origin-save.spec.js +++ b/cypress/integration/origin-save.spec.js @@ -400,7 +400,7 @@ }); it('should create save request for authenticated user', function() { - cy.adminLogin(); + cy.userLogin(); cy.visit(url); const originUrl = 'https://git.example.org/account/repo'; stubSaveRequest({requestUrl: this.Urls.api_1_save_origin('git', originUrl), @@ -415,4 +415,58 @@ }); }); + it('should not show user requests filter checkbox for anonymous users', function() { + cy.get('#swh-origin-save-requests-list-tab').click(); + cy.get('#swh-save-requests-user-filter').should('not.exist'); + }); + + it('should show user requests filter checkbox for authenticated users', function() { + cy.userLogin(); + cy.visit(url); + cy.get('#swh-origin-save-requests-list-tab').click(); + cy.get('#swh-save-requests-user-filter').should('exist'); + }); + + it('should show only user requests when filter is activated', function() { + cy.intercept('POST', '/api/1/origin/save/**') + .as('saveRequest'); + + const originAnonymousUser = 'https://some.git.server/project/'; + const originAuthUser = 'https://other.git.server/project/'; + + makeOriginSaveRequest('git', originAnonymousUser); + cy.wait('@saveRequest'); + + cy.userLogin(); + cy.visit(url); + + makeOriginSaveRequest('git', originAuthUser); + cy.wait('@saveRequest'); + + cy.get('#swh-origin-save-requests-list-tab').click(); + cy.get('#swh-save-requests-user-filter').should('exist'); + + cy.get('tbody tr').then(rows => { + expect(rows.length).to.eq(2); + expect($(rows[0].cells[2]).text()).to.contain(originAuthUser); + expect($(rows[1].cells[2]).text()).to.contain(originAnonymousUser); + }); + + cy.get('#swh-save-requests-user-filter') + .click({force: true}); + + cy.get('tbody tr').then(rows => { + expect(rows.length).to.eq(1); + expect($(rows[0].cells[2]).text()).to.contain(originAuthUser); + }); + + cy.get('#swh-save-requests-user-filter') + .click({force: true}); + + cy.get('tbody tr').then(rows => { + expect(rows.length).to.eq(2); + }); + + }); + }); diff --git a/cypress/support/index.js b/cypress/support/index.js --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -19,7 +19,7 @@ expect(Object.keys(aliasRoute.requests || {})).to.have.length(timesCalled); }); -Cypress.Commands.add('adminLogin', () => { +function loginUser(username, password) { const url = '/admin/login/'; return cy.request({ url: url, @@ -33,8 +33,8 @@ form: true, followRedirect: false, body: { - username: 'admin', - password: 'admin', + username: username, + password: password, csrfmiddlewaretoken: token } }).then(() => { @@ -43,6 +43,14 @@ }); }); }); +} + +Cypress.Commands.add('adminLogin', () => { + return loginUser('admin', 'admin'); +}); + +Cypress.Commands.add('userLogin', () => { + return loginUser('user', 'user'); }); before(function() { diff --git a/swh/web/misc/origin_save.py b/swh/web/misc/origin_save.py --- a/swh/web/misc/origin_save.py +++ b/swh/web/misc/origin_save.py @@ -61,6 +61,12 @@ | Q(origin_url__icontains=search_value) ) + if ( + int(request.GET.get("user_requests_only", "0")) + and request.user.is_authenticated + ): + save_requests = save_requests.filter(user_id=str(request.user.id)) + table_data["recordsFiltered"] = save_requests.count() paginator = Paginator(save_requests, length) table_data["data"] = [sor.to_dict() for sor in paginator.page(page).object_list] diff --git a/swh/web/templates/misc/origin-save.html b/swh/web/templates/misc/origin-save.html --- a/swh/web/templates/misc/origin-save.html +++ b/swh/web/templates/misc/origin-save.html @@ -96,6 +96,9 @@

Once a save request has been accepted, you can follow its current status in the submitted save requests list. +
+ If you submitted requests while being authenticated, you will be able + to only display your requests.

diff --git a/swh/web/tests/create_test_admin.py b/swh/web/tests/create_test_users.py rename from swh/web/tests/create_test_admin.py rename to swh/web/tests/create_test_users.py --- a/swh/web/tests/create_test_admin.py +++ b/swh/web/tests/create_test_users.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019 The Software Heritage developers +# Copyright (C) 2019-2021 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 @@ -14,3 +14,10 @@ if not User.objects.filter(username=username).exists(): User.objects.create_superuser(username, email, password) + +username = "user" +password = "user" +email = "user@swh-web.org" + +if not User.objects.filter(username=username).exists(): + User.objects.create_user(username, email, password) diff --git a/swh/web/tests/misc/test_origin_save.py b/swh/web/tests/misc/test_origin_save.py --- a/swh/web/tests/misc/test_origin_save.py +++ b/swh/web/tests/misc/test_origin_save.py @@ -8,6 +8,7 @@ import pytest +from swh.auth.django.utils import oidc_user_from_profile from swh.web.common.models import SaveOriginRequest from swh.web.common.origin_save import SAVE_REQUEST_ACCEPTED, SAVE_TASK_SUCCEEDED from swh.web.common.utils import reverse @@ -23,7 +24,7 @@ @pytest.mark.django_db -def test_save_origin_requests_list(client, mocker): +def test_save_origin_requests_list(client, mocker, keycloak_oidc): visit_types = ("git", "svn", "hg") nb_origins_per_type = 10 for visit_type in visit_types: @@ -94,3 +95,42 @@ assert sors["recordsTotal"] == len(visit_types) * nb_origins_per_type assert len(sors["data"]) == nb_origins_per_type assert all(d["visit_type"] == visit_type for d in sors["data"]) + + # simulate a user is logged in and create a save request + user = oidc_user_from_profile(keycloak_oidc, keycloak_oidc.login()) + client.login(code="", code_verifier="", redirect_uri="") + + sor = SaveOriginRequest.objects.create( + request_date=datetime.now(tz=timezone.utc), + visit_type=visit_type, + origin_url="https://git.example.org/user/project", + status=SAVE_REQUEST_ACCEPTED, + visit_date=datetime.now(tz=timezone.utc) + timedelta(hours=1), + loading_task_id=i, + loading_task_status=SAVE_TASK_SUCCEEDED, + user_id=str(user.id), + ) + + # filter save requests according to user id + url = reverse( + "origin-save-requests-list", + url_args={"status": "all"}, + query_params={ + "draw": i + 1, + "search[value]": "", + "order[0][column]": "0", + "columns[0][name]": "request_date", + "order[0][dir]": "desc", + "length": nb_origins_per_type, + "start": "0", + "user_requests_only": "1", + }, + ) + + resp = check_http_get_response( + client, url, status_code=200, content_type="application/json" + ) + sors = json.loads(resp.content.decode("utf-8")) + assert sors["recordsFiltered"] == 1 + assert sors["recordsTotal"] == len(visit_types) * nb_origins_per_type + 1 + assert sors["data"][0] == sor.to_dict()