diff --git a/assets/src/bundles/webapp/webapp.css b/assets/src/bundles/webapp/webapp.css index 2f8e6c39..ff1a43f6 100644 --- a/assets/src/bundles/webapp/webapp.css +++ b/assets/src/bundles/webapp/webapp.css @@ -1,750 +1,760 @@ /** * Copyright (C) 2018-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 */ html { height: 100%; overflow-x: hidden; scroll-behavior: auto !important; } body { min-height: 100%; margin: 0; position: relative; padding-bottom: 120px; } a:active, a.active { outline: none; } code { background-color: #f9f2f4; } pre code { background-color: transparent; } footer { background-color: #262626; color: #fff; font-size: 0.8rem; position: absolute; bottom: 0; width: 100%; padding-top: 10px; padding-bottom: 10px; } footer a, footer a:visited, footer a:hover { color: #fecd1b; } footer a:hover { text-decoration: underline; } .link-color { color: #fecd1b; } pre { background-color: #f5f5f5; border: 1px solid #ccc; border-radius: 4px; padding: 9.5px; font-size: 0.8rem; } .btn.active { background-color: #e7e7e7; } .card { margin-bottom: 5px !important; overflow-x: auto; } .navbar-brand { padding: 5px; margin-right: 0; } .table { margin-bottom: 0; } .swh-table thead { background-color: #f2f4f5; border-top: 1px solid rgb(0 0 0 / 20%); font-weight: normal; } .swh-table-striped th { border-top: none; } .swh-table-striped tbody tr:nth-child(even) { background-color: #f2f4f5; } .swh-table-striped tbody tr:nth-child(odd) { background-color: #fff; } .swh-web-app-link a { text-decoration: none; border: none; } .swh-web-app-link:hover { background-color: #efeff2; } .table > thead > tr > th { border-top: none; border-bottom: 1px solid #e20026; } .table > tbody > tr > td { border-style: none; } .sitename .first-word, .sitename .second-word { color: rgb(0 0 0 / 75%); font-weight: normal; font-size: 1.2rem; } .sitename .first-word { font-family: "Alegreya Sans", sans-serif; } .sitename .second-word { font-family: Alegreya, serif; } .swh-counter { font-size: 150%; } @media (max-width: 600px) { .swh-counter-container { margin-top: 1rem; } } .swh-http-error { margin: 0 auto; text-align: center; } .swh-http-error-head { color: #2d353c; font-size: 30px; } .swh-http-error-code { bottom: 60%; color: #2d353c; font-size: 96px; line-height: 80px; margin-bottom: 10px !important; } .swh-http-error-desc { font-size: 12px; color: #647788; text-align: center; } .swh-http-error-desc pre { display: inline-block; text-align: left; max-width: 800px; white-space: pre-wrap; } .swh-list-unstyled { list-style: none; } .popover { max-width: 97%; z-index: 40000; } .modal { text-align: center; padding: 0 !important; z-index: 50000; } .modal::before { content: ""; display: inline-block; height: 100%; vertical-align: middle; margin-right: -4px; } .modal-dialog { display: inline-block; text-align: left; vertical-align: middle; } .dropdown-submenu { position: relative; } .dropdown-submenu .dropdown-menu { top: 0; left: -100%; margin-top: -5px; margin-left: -2px; } .dropdown-item:hover, .dropdown-item:focus { background-color: rgb(0 0 0 / 10%); } a.dropdown-left::before { content: "\f035e"; font-family: "Material Design Icons"; display: block; width: 20px; height: 20px; float: left; margin-left: 0; } #swh-navbar { border-style: solid; border-top-style: none; border-left-style: none; border-right-style: none; border-bottom-width: 5px; border-image: linear-gradient( to right, rgb(226 0 38) 0%, rgb(254 205 27) 100% ) 1 1 1 1; width: 100%; padding: 5px; margin-bottom: 10px; - margin-top: 30px; + margin-top: 90px; justify-content: normal; flex-wrap: nowrap; height: 72px; overflow: hidden; } #back-to-top { display: none; position: fixed; bottom: 30px; right: 30px; z-index: 10; } #back-to-top a img { display: block; width: 32px; height: 32px; background-size: 32px 32px; text-indent: -999px; overflow: hidden; } .swh-top-bar { direction: ltr; height: 30px; position: fixed; top: 0; left: 0; width: 100%; z-index: 99999; background-color: #262626; color: #fff; text-align: center; font-size: 14px; } .swh-top-bar ul { margin-top: 4px; padding-left: 0; white-space: nowrap; } .swh-top-bar li { display: inline-block; margin-left: 10px; margin-right: 10px; } .swh-top-bar a, .swh-top-bar a:visited { color: white; } .swh-top-bar a.swh-current-site, .swh-top-bar a.swh-current-site:visited { color: #fecd1b; } +.swh-fundraising-banner-iframe { + height: 60px; + position: fixed; + top: 30px; + left: 0; + width: 100%; + z-index: 99999; + border: none; +} + .swh-position-left { position: absolute; left: 0; } .swh-position-right { position: absolute; right: 0; } .swh-background-gray { background: #efeff2; } .swh-donate-link { border: 1px solid #fecd1b; background-color: #e20026; color: white !important; padding: 3px; border-radius: 3px; } .swh-navbar-content h4 { padding-top: 7px; } .swh-navbar-content .bread-crumbs { display: block; margin-left: -40px; } .swh-navbar-content .bread-crumbs li.bc-no-root { padding-top: 7px; } .main-sidebar { - margin-top: 30px; + margin-top: 90px; } .content-wrapper { background: none; } .brand-image { max-height: 40px; } .brand-link { padding-top: 18.5px; padding-bottom: 18px; padding-left: 4px; border-bottom: 5px solid #e20026 !important; } .navbar-header a, ul.dropdown-menu a, ul.navbar-nav a, ul.nav-sidebar a { border-bottom-style: none; color: #323232; } .swh-sidebar .nav-link.active { color: #323232 !important; background-color: #e7e7e7 !important; } .nav-tabs .nav-link.active { border-top: 3px solid #e20026; } .swh-image-error { width: 80px; height: auto; } @media (max-width: 600px) { .card { min-width: 80%; } .swh-image-error { width: 40px; height: auto; } .swh-donate-link { display: none; } } .form-check-label { padding-top: 4px; } .swhid { white-space: pre-wrap; } .swhid .swhid-option { display: inline-block; margin-right: 5px; line-height: 1rem; } .nav-pills .nav-link:not(.active):hover { color: rgb(0 0 0 / 55%); } .swh-heading-color { color: #e20026 !important; } .sidebar-mini.sidebar-collapse .main-sidebar:hover { width: 4.6rem; } .sidebar-mini.sidebar-collapse .main-sidebar:hover .user-panel > .info, .sidebar-mini.sidebar-collapse .main-sidebar:hover .nav-sidebar .nav-link p, .sidebar-mini.sidebar-collapse .main-sidebar:hover .brand-text { visibility: hidden !important; } .sidebar .nav-link p, .main-sidebar .brand-text, .sidebar .user-panel .info { transition: none; } .sidebar-mini.sidebar-mini.sidebar-collapse .sidebar { padding-right: 0; } .swh-words-logo { position: absolute; top: 0; left: 0; width: 73px; height: 73px; text-align: center; font-size: 10pt; color: rgb(0 0 0 / 75%); } .swh-words-logo:hover { text-decoration: none; } .swh-words-logo-swh { line-height: 1; padding-top: 13px; visibility: hidden; } hr.swh-faded-line { border: 0; height: 1px; background-image: linear-gradient(to left, #f0f0f0, #8c8b8b, #f0f0f0); } /* Ensure that section title with link is colored like standard section title */ .swh-readme h1 a, .swh-readme h2 a, .swh-readme h3 a, .swh-readme h4 a, .swh-readme h5 a, .swh-readme h6 a { color: #e20026; } /* Make list compact in reStructuredText rendering */ .swh-rst li p { margin-bottom: 0; } .swh-readme-txt pre { background: none; border: none; } .swh-coverage { padding-top: 0.3rem; border: none; overflow: visible; } .swh-coverage a { text-decoration: none; } .swh-coverage-col { padding-left: 10px; padding-right: 10px; } .swh-coverage-header { padding-top: 0; padding-bottom: 0; } .swh-coverage-logo { display: block; width: 100%; height: 50px; margin-left: auto; margin-right: auto; object-fit: contain; /* polyfill for old browsers, see https://github.com/bfred-it/object-fit-images */ font-family: "object-fit: contain;"; } .swh-coverage-list { width: 100%; height: 320px; border: none; } .swh-coverage-chevron { position: absolute; right: 0; } .swh-coverage .card-header .mdi { transition: 0.3s transform ease-in-out; } .swh-coverage .card-header .collapsed .mdi { transform: rotate(90deg); } .swh-coverage-info-body { max-height: 150px; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; /* Firefox only */ padding: 0; } /* Thin scrollbar for chromium based browsers */ .swh-coverage-info-body::-webkit-scrollbar { width: 4px; } .swh-coverage-info-body::-webkit-scrollbar-track { background: #eff0f1; } .swh-coverage-info-body::-webkit-scrollbar-thumb { background: #909396; } tr.swh-tr-hover-highlight:hover td { background: #ededed; } tr.swh-api-doc-route a { text-decoration: none; } .swh-apidoc .col { margin: 10px; } .swh-apidoc .swh-rst blockquote { border: 0; margin: 0; padding: 0; } a.toggle-col { text-decoration: none; } a.toggle-col.col-hidden { text-decoration: line-through; } .admonition.warning { background: #fcf8e3; border: 1px solid #faebcc; padding: 15px; border-radius: 4px; } .admonition.warning p { margin-bottom: 0; } .admonition.warning .first { font-size: 1.5rem; } .swh-popover { max-height: 50vh; overflow-y: auto; overflow-x: auto; padding: 0; } @media screen and (min-width: 768px) { .swh-popover { max-width: 50vw; } } .swh-popover pre { white-space: pre-wrap; margin-bottom: 0; } .d3-wrapper { position: relative; height: 0; width: 100%; padding: 0; /* padding-bottom will be overwritten by JavaScript later */ padding-bottom: 100%; } .d3-wrapper > svg { position: absolute; height: 100%; width: 100%; left: 0; top: 0; } div.d3-tooltip { position: absolute; text-align: center; width: auto; height: auto; padding: 2px; font: 12px sans-serif; background: white; border: 1px solid black; border-radius: 4px; pointer-events: none; } .page-link { cursor: pointer; } .wrapper { overflow: hidden; } .swh-badge { padding-bottom: 1rem; cursor: pointer; } .swh-badge-html, .swh-iframe-html, .swh-badge-md, .swh-badge-rst { white-space: pre-wrap !important; } /* Material Design icons alignment tweaks */ .mdi { display: inline-block; } .mdi-camera { transform: translateY(1px); } .mdi-source-commit { transform: translateY(2px); } /* To set icons at a fixed width. Great to use when different icon widths throw off alignment. Courtesy of Font Awesome. */ .mdi-fw { text-align: center; width: 1.25em; } .main-header .nav-link { height: inherit; } .nav-sidebar .nav-header:not(:first-of-type) { padding-top: 1rem; } .nav-sidebar .nav-link { padding-top: 0; padding-bottom: 0; } .nav-sidebar > .nav-item .nav-icon { vertical-align: sub; } .swh-search-icon { line-height: 1rem; vertical-align: middle; } .swh-search-navbar { position: absolute; top: 0.7rem; right: 15rem; z-index: 50000; width: 500px; } .sidebar-collapse .swh-search-navbar { right: 4rem; } .swh-corner-ribbon { width: 200px; background: #fecd1b; color: #e20026; position: absolute; text-align: center; letter-spacing: 1px; box-shadow: 0 0 3px rgb(0 0 0 / 30%); - top: 55px; + top: 115px; right: -50px; left: auto; transform: rotate(45deg); z-index: 2000; } @media screen and (max-width: 600px) { .swh-corner-ribbon { top: 53px; right: -65px; } } .invalid-feedback { font-size: 100%; } diff --git a/static/img/swh_givingtuesday.png b/static/img/swh_givingtuesday.png new file mode 100644 index 00000000..2084c7c5 Binary files /dev/null and b/static/img/swh_givingtuesday.png differ diff --git a/swh/web/config.py b/swh/web/config.py index c01fef09..88f9cfe6 100644 --- a/swh/web/config.py +++ b/swh/web/config.py @@ -1,204 +1,205 @@ # Copyright (C) 2017-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 import os from typing import Any, Dict from swh.core import config from swh.counters import get_counters from swh.indexer.storage import get_indexer_storage from swh.scheduler import get_scheduler from swh.search import get_search from swh.storage import get_storage from swh.vault import get_vault from swh.web import settings SWH_WEB_INTERNAL_SERVER_NAME = "archive.internal.softwareheritage.org" STAGING_SERVER_NAMES = [ "webapp.staging.swh.network", "webapp.internal.staging.swh.network", ] SETTINGS_DIR = os.path.dirname(settings.__file__) DEFAULT_CONFIG = { "allowed_hosts": ("list", []), "storage": ( "dict", {"cls": "remote", "url": "http://127.0.0.1:5002/", "timeout": 10,}, ), "indexer_storage": ( "dict", {"cls": "remote", "url": "http://127.0.0.1:5007/", "timeout": 1,}, ), "counters": ( "dict", {"cls": "remote", "url": "http://127.0.0.1:5011/", "timeout": 1,}, ), "search": ( "dict", {"cls": "remote", "url": "http://127.0.0.1:5010/", "timeout": 10,}, ), "search_config": ( "dict", {"metadata_backend": "swh-indexer-storage",}, # or "swh-search" ), "log_dir": ("string", "/tmp/swh/log"), "debug": ("bool", False), "serve_assets": ("bool", False), "host": ("string", "127.0.0.1"), "port": ("int", 5004), "secret_key": ("string", "development key"), # do not display code highlighting for content > 1MB "content_display_max_size": ("int", 5 * 1024 * 1024), "snapshot_content_max_size": ("int", 1000), "throttling": ( "dict", { "cache_uri": None, # production: memcached as cache (127.0.0.1:11211) # development: in-memory cache so None "scopes": { "swh_api": { "limiter_rate": {"default": "120/h"}, "exempted_networks": ["127.0.0.0/8"], }, "swh_api_origin_search": { "limiter_rate": {"default": "10/m"}, "exempted_networks": ["127.0.0.0/8"], }, "swh_vault_cooking": { "limiter_rate": {"default": "120/h", "GET": "60/m"}, "exempted_networks": ["127.0.0.0/8"], }, "swh_save_origin": { "limiter_rate": {"default": "120/h", "POST": "10/h"}, "exempted_networks": ["127.0.0.0/8"], }, "swh_api_origin_visit_latest": { "limiter_rate": {"default": "700/m"}, "exempted_networks": ["127.0.0.0/8"], }, }, }, ), "vault": ("dict", {"cls": "remote", "args": {"url": "http://127.0.0.1:5005/",}}), "scheduler": ("dict", {"cls": "remote", "url": "http://127.0.0.1:5008/"}), "development_db": ("string", os.path.join(SETTINGS_DIR, "db.sqlite3")), "test_db": ("dict", {"name": "swh-web-test"}), "production_db": ("dict", {"name": "swh-web"}), "deposit": ( "dict", { "private_api_url": "https://deposit.softwareheritage.org/1/private/", "private_api_user": "swhworker", "private_api_password": "some-password", }, ), "e2e_tests_mode": ("bool", False), "es_workers_index_url": ("string", ""), "history_counters_url": ( "string", ( "http://counters1.internal.softwareheritage.org:5011" "/counters_history/history.json" ), ), "client_config": ("dict", {}), "keycloak": ("dict", {"server_url": "", "realm_name": ""}), "graph": ( "dict", {"server_url": "http://graph.internal.softwareheritage.org:5009/graph/"}, ), "status": ( "dict", { "server_url": "https://status.softwareheritage.org/", "json_path": "1.0/status/578e5eddcdc0cc7951000520", }, ), "counters_backend": ("string", "swh-storage"), # or "swh-counters" "staging_server_names": ("list", STAGING_SERVER_NAMES), "instance_name": ("str", "archive-test.softwareheritage.org"), + "give": ("dict", {"public_key": "", "token": ""}), } swhweb_config: Dict[str, Any] = {} def get_config(config_file="web/web"): """Read the configuration file `config_file`. If an environment variable SWH_CONFIG_FILENAME is defined, this takes precedence over the config_file parameter. In any case, update the app with parameters (secret_key, conf) and return the parsed configuration as a dict. If no configuration file is provided, return a default configuration. """ if not swhweb_config: config_filename = os.environ.get("SWH_CONFIG_FILENAME") if config_filename: config_file = config_filename cfg = config.load_named_config(config_file, DEFAULT_CONFIG) swhweb_config.update(cfg) config.prepare_folders(swhweb_config, "log_dir") if swhweb_config.get("search"): swhweb_config["search"] = get_search(**swhweb_config["search"]) else: swhweb_config["search"] = None swhweb_config["storage"] = get_storage(**swhweb_config["storage"]) swhweb_config["vault"] = get_vault(**swhweb_config["vault"]) swhweb_config["indexer_storage"] = get_indexer_storage( **swhweb_config["indexer_storage"] ) swhweb_config["scheduler"] = get_scheduler(**swhweb_config["scheduler"]) swhweb_config["counters"] = get_counters(**swhweb_config["counters"]) return swhweb_config def search(): """Return the current application's search. """ return get_config()["search"] def storage(): """Return the current application's storage. """ return get_config()["storage"] def vault(): """Return the current application's vault. """ return get_config()["vault"] def indexer_storage(): """Return the current application's indexer storage. """ return get_config()["indexer_storage"] def scheduler(): """Return the current application's scheduler. """ return get_config()["scheduler"] def counters(): """Return the current application's counters. """ return get_config()["counters"] diff --git a/swh/web/misc/fundraising.py b/swh/web/misc/fundraising.py new file mode 100644 index 00000000..03c0683b --- /dev/null +++ b/swh/web/misc/fundraising.py @@ -0,0 +1,62 @@ +# Copyright (C) 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 + +import requests + +from django.conf.urls import url +from django.shortcuts import render +from django.views.decorators.clickjacking import xframe_options_exempt + +from swh.web.config import get_config + + +@xframe_options_exempt +def fundraising_banner(request): + config = get_config() + public_key = config["give"]["public_key"] + token = config["give"]["token"] + give_api_forms_url = ( + "https://www.softwareheritage.org/give-api/v1/forms/" + f"?key={public_key}&token={token}&form=27047" + ) + + donations_goal = 100 + nb_donations = -1 + + try: + fundraising_form = requests.get(give_api_forms_url).json().get("forms", []) + if fundraising_form: + nb_donations = int( + fundraising_form[0] + .get("stats", {}) + .get("total", {}) + .get("donations", -1) + ) + except Exception: + pass + + goal_percent = int(nb_donations / donations_goal * 100) + + lang = request.GET.get("lang") + + return render( + request, + "misc/fundraising-banner.html", + { + "nb_donations": nb_donations, + "donations_goal": donations_goal, + "goal_percent": goal_percent, + "lang": lang if lang else "en", + "donation_form_url": ( + "https://www.softwareheritage.org/donations/" + "help-preserve-sourcecode-2021/" + ), + }, + ) + + +urlpatterns = [ + url(r"^fundraising/banner", fundraising_banner, name="swh-fundraising-banner"), +] diff --git a/swh/web/misc/urls.py b/swh/web/misc/urls.py index eb405b1a..7e7cad07 100644 --- a/swh/web/misc/urls.py +++ b/swh/web/misc/urls.py @@ -1,103 +1,104 @@ # 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 import json import requests import sentry_sdk from django.conf.urls import include, url from django.contrib.staticfiles import finders from django.http import JsonResponse from django.shortcuts import render from swh.web.common import archive from swh.web.config import get_config from swh.web.misc.metrics import prometheus_metrics def _jslicenses(request): jslicenses_file = finders.find("jssources/jslicenses.json") jslicenses_data = json.load(open(jslicenses_file)) jslicenses_data = sorted( jslicenses_data.items(), key=lambda item: item[0].split("/")[-1] ) return render(request, "misc/jslicenses.html", {"jslicenses_data": jslicenses_data}) def _stat_counters(request): stat_counters = archive.stat_counters() url = get_config()["history_counters_url"] stat_counters_history = {} try: response = requests.get(url, timeout=5) stat_counters_history = json.loads(response.text) except Exception as exc: sentry_sdk.capture_exception(exc) counters = { "stat_counters": stat_counters, "stat_counters_history": stat_counters_history, } return JsonResponse(counters) urlpatterns = [ url(r"^", include("swh.web.misc.coverage")), url(r"^jslicenses/$", _jslicenses, name="jslicenses"), url(r"^", include("swh.web.misc.origin_save")), url(r"^stat_counters/", _stat_counters, name="stat-counters"), url(r"^", include("swh.web.misc.badges")), url(r"^metrics/prometheus/$", prometheus_metrics, name="metrics-prometheus"), url(r"^", include("swh.web.misc.iframe")), + url(r"^", include("swh.web.misc.fundraising")), ] # when running end to end tests trough cypress, declare some extra # endpoints to provide input data for some of those tests if get_config()["e2e_tests_mode"]: from swh.web.tests.views import ( get_content_code_data_all_exts, get_content_code_data_all_filenames, get_content_code_data_by_ext, get_content_code_data_by_filename, get_content_other_data_by_ext, ) urlpatterns.append( url( r"^tests/data/content/code/extension/(?P.+)/$", get_content_code_data_by_ext, name="tests-content-code-extension", ) ) urlpatterns.append( url( r"^tests/data/content/other/extension/(?P.+)/$", get_content_other_data_by_ext, name="tests-content-other-extension", ) ) urlpatterns.append( url( r"^tests/data/content/code/extensions/$", get_content_code_data_all_exts, name="tests-content-code-extensions", ) ) urlpatterns.append( url( r"^tests/data/content/code/filename/(?P.+)/$", get_content_code_data_by_filename, name="tests-content-code-filename", ) ) urlpatterns.append( url( r"^tests/data/content/code/filenames/$", get_content_code_data_all_filenames, name="tests-content-code-filenames", ) ) diff --git a/swh/web/templates/layout.html b/swh/web/templates/layout.html index fb5622aa..c10c3fda 100644 --- a/swh/web/templates/layout.html +++ b/swh/web/templates/layout.html @@ -1,290 +1,291 @@ {% comment %} Copyright (C) 2015-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 {% endcomment %} {% load js_reverse %} {% load static %} {% load render_bundle from webpack_loader %} {% load swh_templatetags %} {% block title %}{% endblock %} {% render_bundle 'vendors' %} {% render_bundle 'webapp' %} {% render_bundle 'guided_tour' %} {{ request.user.is_authenticated|json_script:"swh_user_logged_in" }} {% block header %}{% endblock %} {% if not swh_web_dev and not swh_web_staging %} {% endif %}
+
{% if swh_web_staging %}
Staging
v{{ swh_web_version }}
{% elif swh_web_dev %}
Development
v{{ swh_web_version|split:"+"|first }}
{% endif %} {% block content %}{% endblock %}
{% include "includes/global-modals.html" %}
back to top
diff --git a/swh/web/templates/misc/fundraising-banner.html b/swh/web/templates/misc/fundraising-banner.html new file mode 100644 index 00000000..db40da07 --- /dev/null +++ b/swh/web/templates/misc/fundraising-banner.html @@ -0,0 +1,167 @@ +{% comment %} +Copyright (C) 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 +{% endcomment %} + + + +{% load static %} +{% load render_bundle from webpack_loader %} + + + + + + + Software Heritage fundraising banner + {% render_bundle 'vendors' %} + {% render_bundle 'webapp' %} + + + + + + + + diff --git a/swh/web/tests/misc/test_fundraising.py b/swh/web/tests/misc/test_fundraising.py new file mode 100644 index 00000000..80c70c56 --- /dev/null +++ b/swh/web/tests/misc/test_fundraising.py @@ -0,0 +1,53 @@ +# Copyright (C) 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 + +from swh.web.common.utils import reverse +from swh.web.tests.django_asserts import assert_contains, assert_not_contains +from swh.web.tests.utils import check_html_get_response + + +def test_fundraising_banner(client, requests_mock): + nb_donations = 40 + requests_mock.get( + "https://www.softwareheritage.org/give-api/v1/forms/", + json={"forms": [{"stats": {"total": {"donations": nb_donations}}}]}, + ) + url = reverse("swh-fundraising-banner") + resp = check_html_get_response( + client, url, status_code=200, template_used="misc/fundraising-banner.html" + ) + assert_contains(resp, f'aria-valuenow="{nb_donations}"') + + +def test_fundraising_banner_languages(client, requests_mock): + nb_donations = 40 + requests_mock.get( + "https://www.softwareheritage.org/give-api/v1/forms/", + json={"forms": [{"stats": {"total": {"donations": nb_donations}}}]}, + ) + + url = reverse("swh-fundraising-banner") + resp = check_html_get_response( + client, url, status_code=200, template_used="misc/fundraising-banner.html" + ) + assert_contains(resp, "Become a donor") + + url = reverse("swh-fundraising-banner", query_params={"lang": "fr"}) + resp = check_html_get_response( + client, url, status_code=200, template_used="misc/fundraising-banner.html" + ) + assert_contains(resp, "Devenez donateur") + + +def test_fundraising_banner_give_api_error(client, requests_mock): + requests_mock.get( + "https://www.softwareheritage.org/give-api/v1/forms/", + exc=Exception("HTTP error"), + ) + url = reverse("swh-fundraising-banner") + resp = check_html_get_response( + client, url, status_code=200, template_used="misc/fundraising-banner.html" + ) + assert_not_contains(resp, 'class="progress-bar"')