From 3655c68a4c2cc2ffedf1b51dc2340b502463776c Mon Sep 17 00:00:00 2001 From: Antoine Lambert <anlambert@softwareheritage.org> Date: Tue, 16 Apr 2024 14:50:04 +0200 Subject: [PATCH] save_code_now: Add pending requests notification icon in admin menu When there is pending save code now requests to be reviewed by staff users, add a notification icon in the top right corner of the save code now admin menu entry icon in left sidebar. --- .../save_code_now/assets/origin-save-admin.js | 7 ++++ swh/web/save_code_now/origin_save.py | 9 +++++ swh/web/utils/__init__.py | 13 ++++++- swh/web/webapp/assets/webapp/webapp.css | 22 +++++++++++ swh/web/webapp/templates/layout.html | 13 +++++-- swh/web/webapp/tests/test_templates.py | 38 ++++++++++++++++++- 6 files changed, 97 insertions(+), 5 deletions(-) diff --git a/swh/web/save_code_now/assets/origin-save-admin.js b/swh/web/save_code_now/assets/origin-save-admin.js index 1755cde0f..0818ea9bd 100644 --- a/swh/web/save_code_now/assets/origin-save-admin.js +++ b/swh/web/save_code_now/assets/origin-save-admin.js @@ -265,6 +265,13 @@ export function acceptOriginSaveRequest() { const acceptSaveRequestUrl = Urls.admin_origin_save_request_accept(rowData['visit_type'], encodeURI(rowData['origin_url'])); await csrfPost(acceptSaveRequestUrl); pendingSaveRequestsTable.ajax.reload(null, false); + // ensure to remove notification icon in sidebar admin menu when + // there is no remaining pending requests + setTimeout(() => { + if ($('td.dt-empty').is(':visible')) { + location.reload(true); + } + }, 100); }; swh.webapp.showModalConfirm( diff --git a/swh/web/save_code_now/origin_save.py b/swh/web/save_code_now/origin_save.py index e472b0d13..e9600601f 100644 --- a/swh/web/save_code_now/origin_save.py +++ b/swh/web/save_code_now/origin_save.py @@ -885,3 +885,12 @@ def schedule_origins_recurrent_visits( listed_origins = scheduler().record_listed_origins(listed_origins) return len(listed_origins) + + +def has_pending_save_code_now_requests() -> bool: + """Return :const:`True` if at least one submitted save request requires + manual validation by staff member.""" + return ( + SaveOriginRequest.objects.filter(status=SAVE_REQUEST_PENDING).first() + is not None + ) diff --git a/swh/web/utils/__init__.py b/swh/web/utils/__init__.py index 4d2828b84..f2b65b946 100644 --- a/swh/web/utils/__init__.py +++ b/swh/web/utils/__init__.py @@ -309,7 +309,7 @@ def context_processor(request): # when rendering templates when standard Django user is logged in. request.user.backend = "django.contrib.auth.backends.ModelBackend" - return { + context = { "swh_object_icons": swh_object_icons, "available_languages": None, "swh_client_config": config["client_config"], @@ -338,6 +338,17 @@ def context_processor(request): "show_corner_ribbon": config.get("show_corner_ribbon", False), } + if ( + "swh.web.save_code_now" in settings.SWH_DJANGO_APPS + and hasattr(request, "user") + and request.user.is_staff + ): + from swh.web.save_code_now.origin_save import has_pending_save_code_now_requests + + context["pending_save_code_now_requests"] = has_pending_save_code_now_requests() + + return context + def resolve_branch_alias( snapshot: Dict[str, Any], branch: Optional[Dict[str, Any]] diff --git a/swh/web/webapp/assets/webapp/webapp.css b/swh/web/webapp/assets/webapp/webapp.css index a31a5d50c..a552a5c64 100644 --- a/swh/web/webapp/assets/webapp/webapp.css +++ b/swh/web/webapp/assets/webapp/webapp.css @@ -413,6 +413,11 @@ ul.nav-sidebar a { color: #fecd1b ! important; } +.swh-notification-icon { + color: #e20026 !important; + transform: translate(8px, -6px); +} + .swh-sidebar .nav-link.active, .swh-sidebar .nav-link:focus { color: #323232 !important; @@ -728,6 +733,23 @@ div.d3-tooltip { width: 1.25em; } +.mdi-stack { + position: relative; + display: inline-block; + width: 1em; + height: 1em; + line-height: 1em; + vertical-align: middle; +} + +.mdi-stack .mdi { + position: absolute; + left: 0; + top: 0; + width: 100%; + text-align: center; +} + .main-header .nav-link { height: inherit; } diff --git a/swh/web/webapp/templates/layout.html b/swh/web/webapp/templates/layout.html index 4442d3020..a0c12bf04 100644 --- a/swh/web/webapp/templates/layout.html +++ b/swh/web/webapp/templates/layout.html @@ -1,5 +1,5 @@ {% comment %} -Copyright (C) 2015-2023 The Software Heritage developers +Copyright (C) 2015-2024 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 @@ -38,7 +38,7 @@ See top-level LICENSE file for more information /* @licstart The following is the entire license notice for the JavaScript code in this page. -Copyright (C) 2015-2023 The Software Heritage developers +Copyright (C) 2015-2024 The Software Heritage developers This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -275,7 +275,14 @@ along with this program. If not, see <https://www.gnu.org/licenses />. role="menuitem"> <a href="{% url 'admin-origin-save-requests' %}" class="nav-link swh-origin-save-admin-link"> - <i class="nav-icon mdi mdi-24px mdi-camera"></i> + {% if pending_save_code_now_requests %} + <div class="nav-icon mdi-stack"> + <i class="mdi mdi-24px mdi-camera"></i> + <i class="mdi mdi-18px mdi-bell swh-notification-icon"></i> + </div> + {% else %} + <i class="nav-icon mdi mdi-24px mdi-camera"></i> + {% endif %} <p>Save code now</p> </a> </li> diff --git a/swh/web/webapp/tests/test_templates.py b/swh/web/webapp/tests/test_templates.py index 57a037ce8..908512239 100644 --- a/swh/web/webapp/tests/test_templates.py +++ b/swh/web/webapp/tests/test_templates.py @@ -1,9 +1,10 @@ -# Copyright (C) 2021-2023 The Software Heritage developers +# Copyright (C) 2021-2024 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 copy import deepcopy +from datetime import datetime, timezone from importlib.metadata import version import random @@ -17,6 +18,11 @@ from swh.web.config import ( SWH_WEB_STAGING_SERVER_NAMES, get_config, ) +from swh.web.save_code_now.models import ( + SAVE_REQUEST_ACCEPTED, + SAVE_REQUEST_PENDING, + SaveOriginRequest, +) from swh.web.tests.django_asserts import assert_contains, assert_not_contains from swh.web.tests.helpers import check_http_get_response, create_django_permission from swh.web.utils import reverse @@ -178,3 +184,33 @@ def test_top_bar_custom_links(client, config_updater): assert_contains(resp, doc_link) assert_contains(resp, donate_link) assert_contains(resp, status_link) + + +@pytest.mark.django_db +def test_save_code_now_pending_requests_notification(client, staff_user): + client.force_login(staff_user) + + url = reverse("swh-web-homepage") + + # no pending save requests, notification icon should not be displayed + assert SaveOriginRequest.objects.first() is None + resp = check_http_get_response(client, url, status_code=200) + assert_not_contains(resp, "mdi-bell swh-notification-icon") + + # new pending save request, notification icon should be displayed + sor = SaveOriginRequest.objects.create( + request_date=datetime.now(tz=timezone.utc), + visit_type="git", + origin_url="https://git.example.org/user/project", + status=SAVE_REQUEST_PENDING, + ) + assert SaveOriginRequest.objects.first() == sor + resp = check_http_get_response(client, url, status_code=200) + assert_contains(resp, "mdi-bell swh-notification-icon") + + # pending save request got accepted, notification icon should no longer + # be displayed + sor.status = SAVE_REQUEST_ACCEPTED + sor.save() + resp = check_http_get_response(client, url, status_code=200) + assert_not_contains(resp, "mdi-bell swh-notification-icon") -- GitLab