diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index f6c6c0c745..aaadb600ce 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -5,12 +5,13 @@ on: - master - hotfixes - develop - - fixtestfront + - feat/import pull_request: branches: - master - hotfixes - develop + - feat/import jobs: mount_app_and_run_cypress: @@ -31,6 +32,15 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 + redis: + image: redis + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - name: Add postgis_raster database extension @@ -78,6 +88,7 @@ jobs: GEONATURE_CONFIG_FILE: config/test_config.toml srid_local: 2154 install_bdc_statuts: true + taxref_region: fr add_sample_data: true install_sig_layers: true install_grid_layer_5: true @@ -100,7 +111,7 @@ jobs: cp ./config/settings.ini.sample ./config/settings.ini ./install/05_install_frontend.sh --ci env: - GEONATURE_CONFIG_FILE: "${{ github.workspace }}/config/test_config.toml" + GEONATURE_CONFIG_FILE: '${{ github.workspace }}/config/test_config.toml' - name: Install core modules run: | geonature install-gn-module contrib/occtax OCCTAX --build=false @@ -109,18 +120,18 @@ jobs: geonature db upgrade occhab-samples@head geonature install-gn-module contrib/gn_module_validation VALIDATION --build=false geonature permissions supergrant --group --nom "Grp_admin" --yes + geonature db upgrade import-samples@head env: GEONATURE_CONFIG_FILE: config/test_config.toml - name: Run GeoNature backend run: geonature dev_back & env: GEONATURE_CONFIG_FILE: config/test_config.toml - - name: Run TaxHub backend - run: flask run --host=0.0.0.0 & - working-directory: ./backend/dependencies/TaxHub/ + - name: Run celery + run: celery -A geonature.celery_app:app worker & + working-directory: ./backend/geonature/ env: - TAXHUB_SETTINGS: test_config.py - TAXHUB_SQLALCHEMY_DATABASE_URI: "postgresql://geonatadmin:geonatpasswd@127.0.0.1:5432/geonature2db" + GEONATURE_CONFIG_FILE: "${{ github.workspace }}/config/test_config.toml" - name: Cypress run uses: cypress-io/github-action@v5 with: diff --git a/.github/workflows/eval.yml b/.github/workflows/eval.yml index 060cf8546b..048f90f321 100644 --- a/.github/workflows/eval.yml +++ b/.github/workflows/eval.yml @@ -94,6 +94,7 @@ jobs: GEONATURE_CONFIG_FILE: config/test_config.toml srid_local: 2154 install_bdc_statuts: true + taxref_region: fr add_sample_data: true install_sig_layers: true install_grid_layer_5: true diff --git a/.github/workflows/eval_perf.yml b/.github/workflows/eval_perf.yml index c6fbe44030..a1a764446f 100644 --- a/.github/workflows/eval_perf.yml +++ b/.github/workflows/eval_perf.yml @@ -9,16 +9,16 @@ jobs: strategy: fail-fast: false matrix: - debian-version: ["11", "12"] + debian-version: ['11', '12'] include: - - debian-version: "11" - python-version: "3.9" - postgres-version: "13" - postgis-version: "3.2" - - debian-version: "12" - python-version: "3.11" - postgres-version: "15" - postgis-version: "3.3" + - debian-version: '11' + python-version: '3.9' + postgres-version: '13' + postgis-version: '3.2' + - debian-version: '12' + python-version: '3.11' + postgres-version: '15' + postgis-version: '3.3' name: Debian ${{ matrix.debian-version }} @@ -50,7 +50,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - cache: "pip" + cache: 'pip' - name: Install GDAL run: | sudo apt update @@ -94,6 +94,7 @@ jobs: GEONATURE_CONFIG_FILE: config/test_config.toml srid_local: 2154 install_bdc_statuts: true + taxref_region: fr add_sample_data: true install_sig_layers: true install_grid_layer_5: true diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 32c6b218c4..313a345565 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -15,7 +15,6 @@ on: jobs: build: runs-on: ubuntu-latest - strategy: fail-fast: false matrix: @@ -46,6 +45,15 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 + redis: + image: redis + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - uses: actions/checkout@v4 @@ -95,6 +103,7 @@ jobs: GEONATURE_CONFIG_FILE: config/test_config.toml srid_local: 2154 install_bdc_statuts: true + taxref_region: fr add_sample_data: true install_sig_layers: true install_grid_layer_5: true @@ -122,11 +131,12 @@ jobs: GEONATURE_CONFIG_FILE: config/test_config.toml - name: Test with pytest run: | - pytest -v --cov --cov-report xml --benchmark-skip + pytest -v --cov --cov-report xml env: GEONATURE_CONFIG_FILE: config/test_config.toml - name: Upload coverage to Codecov - if: ${{ matrix.debian-version == '11' }} - uses: codecov/codecov-action@v3 + if: ${{ matrix.debian-version == '12' }} + uses: codecov/codecov-action@v4 with: + token: ${{ secrets.CODECOV_TOKEN }} flags: pytest diff --git a/.gitignore b/.gitignore index fd94ee2450..53c87ab55b 100755 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,11 @@ cache/* .angulardoc.json Pipfile.lock +custom/* +!custom/.gitkeep + +environ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/backend/dependencies/Nomenclature-api-module b/backend/dependencies/Nomenclature-api-module index 1520652bf9..ee9c44f616 160000 --- a/backend/dependencies/Nomenclature-api-module +++ b/backend/dependencies/Nomenclature-api-module @@ -1 +1 @@ -Subproject commit 1520652bf91632c31e68b0a7f45e55c977c6b344 +Subproject commit ee9c44f61674f8ffabb9a252369f3ab85e38b3df diff --git a/backend/dependencies/RefGeo b/backend/dependencies/RefGeo index d9f3feaa09..b24632fdbc 160000 --- a/backend/dependencies/RefGeo +++ b/backend/dependencies/RefGeo @@ -1 +1 @@ -Subproject commit d9f3feaa0994fd9cad5d3433b57ec918acb33a0e +Subproject commit b24632fdbc219db3fd844fcb6066a57230645004 diff --git a/backend/dependencies/TaxHub b/backend/dependencies/TaxHub index 14614a75ad..7d3808839c 160000 --- a/backend/dependencies/TaxHub +++ b/backend/dependencies/TaxHub @@ -1 +1 @@ -Subproject commit 14614a75ad80a09f6a0fcfddda54252340abf728 +Subproject commit 7d3808839c4ff0ef27fa0a19fa785a2a63ac4358 diff --git a/backend/dependencies/UsersHub b/backend/dependencies/UsersHub index 1b5b79f19f..29fb1e72ff 160000 --- a/backend/dependencies/UsersHub +++ b/backend/dependencies/UsersHub @@ -1 +1 @@ -Subproject commit 1b5b79f19fadb4688860b4219fbf58df4554547e +Subproject commit 29fb1e72ffadfb03e0e3a8bb0e758d47f3bd15f0 diff --git a/backend/dependencies/UsersHub-authentification-module b/backend/dependencies/UsersHub-authentification-module index 41fcc43aee..e27573a9d1 160000 --- a/backend/dependencies/UsersHub-authentification-module +++ b/backend/dependencies/UsersHub-authentification-module @@ -1 +1 @@ -Subproject commit 41fcc43aeef8abef7b2b09a890c166c9bbe2a50f +Subproject commit e27573a9d12e116ef91b1211d6cf9451ccd1aae7 diff --git a/backend/geonature/app.py b/backend/geonature/app.py index 5314d28b68..8cbf39a60e 100755 --- a/backend/geonature/app.py +++ b/backend/geonature/app.py @@ -11,10 +11,10 @@ from importlib_metadata import entry_points else: from importlib.metadata import entry_points - from flask import Flask, g, request, current_app, send_from_directory from flask.json.provider import DefaultJSONProvider from flask_mail import Message +from flask_babel import Babel from flask_cors import CORS from flask_login import current_user from flask_sqlalchemy.track_modifications import before_models_committed @@ -22,10 +22,7 @@ from werkzeug.middleware.shared_data import SharedDataMiddleware from werkzeug.middleware.dispatcher import DispatcherMiddleware from werkzeug.wrappers import Response -from psycopg2.errors import UndefinedTable import sqlalchemy as sa -from sqlalchemy.exc import OperationalError, ProgrammingError -from sqlalchemy.orm.exc import NoResultFound if version.parse(sa.__version__) >= version.parse("1.4"): from sqlalchemy.engine import Row @@ -33,18 +30,16 @@ from sqlalchemy.engine import RowProxy as Row from geonature.utils.config import config + from geonature.utils.env import MAIL, DB, db, MA, migrate, BACKEND_DIR from geonature.utils.logs import config_loggers from geonature.utils.module import iter_modules_dist from geonature.core.admin.admin import admin from geonature.middlewares import SchemeFix, RequestID -from pypnusershub.db.tools import ( - user_from_token, - UnreadableAccessRightsError, - AccessRightsExpiredError, -) + from pypnusershub.db.models import Application +from pypnusershub.auth import auth_manager from pypnusershub.login_manager import login_manager @@ -88,6 +83,17 @@ def default(o): return DefaultJSONProvider.default(o) +def get_locale(): + # if a user is logged in, use the locale from the user settings + user = getattr(g, "user", None) + if user is not None: + return user.locale + # otherwise try to guess the language from the user accept + # header the browser transmits. We support de/fr/en in this + # example. The best match wins. + return request.accept_languages.best_match(["de", "fr", "en"]) + + def create_app(with_external_mods=True): app = Flask( __name__.split(".")[0], @@ -96,7 +102,6 @@ def create_app(with_external_mods=True): static_url_path=config["STATIC_URL"], template_folder="geonature/templates", ) - app.config.update(config) # Enable deprecation warnings in debug mode @@ -129,12 +134,13 @@ def create_app(with_external_mods=True): migrate.init_app(app, DB, directory=BACKEND_DIR / "geonature" / "migrations") MA.init_app(app) CORS(app, supports_credentials=True) + auth_manager.init_app(app, providers_declaration=config["AUTHENTICATION"]["PROVIDERS"]) + auth_manager.home_page = config["URL_APPLICATION"] if "CELERY" in app.config: from geonature.utils.celery import celery_app celery_app.init_app(app) - celery_app.conf.update(app.config["CELERY"]) # Emails configuration if app.config["MAIL_CONFIG"]: @@ -148,8 +154,6 @@ def create_app(with_external_mods=True): # Pass parameters to the submodules app.config["MA"] = MA - login_manager.init_app(app) - # For deleting files on "delete" media @before_models_committed.connect_via(app) def on_before_models_committed(sender, changes): @@ -182,15 +186,24 @@ def set_sentry_context(): admin.init_app(app) + # babel + babel = Babel(app, locale_selector=get_locale) + # Enable serving of media files app.add_url_rule( f"{config['MEDIA_URL']}/", view_func=lambda filename: send_from_directory(config["MEDIA_FOLDER"], filename), endpoint="media", ) + app.add_url_rule( + f"{config['MEDIA_URL']}/taxhub/", + view_func=lambda filename: send_from_directory( + config["MEDIA_FOLDER"] + "/taxhub", filename + ), + endpoint="media_taxhub", + ) for blueprint_path, url_prefix in [ - ("pypnusershub.routes:routes", "/auth"), ("pypn_habref_api.routes:routes", "/habref"), ("pypnusershub.routes_register:bp", "/pypn/register"), ("pypnnomenclature.routes:routes", "/nomenclatures"), @@ -200,17 +213,37 @@ def set_sentry_context(): ("geonature.core.users.routes:routes", "/users"), ("geonature.core.gn_synthese.routes:routes", "/synthese"), ("geonature.core.gn_meta.routes:routes", "/meta"), - ("geonature.core.auth.routes:routes", "/gn_auth"), ("geonature.core.gn_monitoring.routes:routes", "/gn_monitoring"), ("geonature.core.gn_profiles.routes:routes", "/gn_profiles"), ("geonature.core.sensitivity.routes:routes", None), ("geonature.core.notifications.routes:routes", "/notifications"), + ("geonature.core.imports.blueprint:blueprint", "/import"), ]: module_name, blueprint_name = blueprint_path.split(":") blueprint = getattr(import_module(module_name), blueprint_name) app.register_blueprint(blueprint, url_prefix=url_prefix) with app.app_context(): + # taxhub api + from apptax import taxhub_api_routes + + base_api_prefix = app.config["TAXHUB"].get("API_PREFIX") + + for blueprint_path, url_prefix in taxhub_api_routes: + module_name, blueprint_name = blueprint_path.split(":") + blueprint = getattr(import_module(module_name), blueprint_name) + app.register_blueprint(blueprint, url_prefix="/taxhub" + base_api_prefix + url_prefix) + + # taxhub admin + from apptax.admin.admin import adresses + + app.register_blueprint(adresses, url_prefix="/taxhub") + + # register taxhub admin view which need app context + from geonature.core.taxonomie.admin import load_admin_views + + load_admin_views(app, admin) + # register errors handlers import geonature.core.errors diff --git a/backend/geonature/celery_app.py b/backend/geonature/celery_app.py index d614ba1354..e70ba8574b 100644 --- a/backend/geonature/celery_app.py +++ b/backend/geonature/celery_app.py @@ -1,6 +1,7 @@ from .app import create_app from .utils.celery import celery_app as app from .utils.module import iter_modules_dist +from .utils.env import db flask_app = create_app() @@ -9,7 +10,9 @@ class ContextTask(app.Task): def __call__(self, *args, **kwargs): with flask_app.app_context(): - return self.run(*args, **kwargs) + result = self.run(*args, **kwargs) + db.session.remove() + return result app.Task = ContextTask diff --git a/backend/geonature/core/admin/admin.py b/backend/geonature/core/admin/admin.py index bb171b2c1b..573a9096fd 100644 --- a/backend/geonature/core/admin/admin.py +++ b/backend/geonature/core/admin/admin.py @@ -1,3 +1,5 @@ +import os + from flask import g from werkzeug.exceptions import Unauthorized from flask_admin import Admin, AdminIndexView, expose @@ -152,4 +154,5 @@ class ProtectedTNomenclaturesAdmin( ) ) + flask_admin = admin # for retro-compatibility, usefull for export module for instance diff --git a/backend/geonature/core/admin/utils.py b/backend/geonature/core/admin/utils.py index 6bc577d4e6..8cd1d3f866 100644 --- a/backend/geonature/core/admin/utils.py +++ b/backend/geonature/core/admin/utils.py @@ -10,6 +10,8 @@ class CruvedProtectedMixin: def is_accessible(self): if g.current_user is None: raise Unauthorized # return False leads to Forbidden which is different + if not g.current_user.is_authenticated: + raise Unauthorized return self._can_action("R") def _can_action(self, action): diff --git a/backend/geonature/core/auth/routes.py b/backend/geonature/core/auth/routes.py deleted file mode 100644 index 58902cdd92..0000000000 --- a/backend/geonature/core/auth/routes.py +++ /dev/null @@ -1,221 +0,0 @@ -""" - Module d'identificiation provisoire pour test du CAS INPN -""" - -import datetime -import xmltodict -import logging -from copy import copy - - -from flask import ( - Blueprint, - request, - make_response, - redirect, - current_app, - jsonify, - render_template, - session, - Response, -) -from flask_login import login_user -import sqlalchemy as sa -from sqlalchemy import select -from utils_flask_sqla.response import json_resp - -from pypnusershub.db import models -from pypnusershub.db.models import User, Organisme, Application -from pypnusershub.db.tools import encode_token -from pypnusershub.routes import insert_or_update_organism, insert_or_update_role -from geonature.utils import utilsrequests -from geonature.utils.errors import CasAuthentificationError -from geonature.utils.env import db - - -routes = Blueprint("gn_auth", __name__, template_folder="templates") -log = logging.getLogger() - - -@routes.route("/login_cas", methods=["GET", "POST"]) -def loginCas(): - """ - Login route with the INPN CAS - - .. :quickref: User; - """ - config_cas = current_app.config["CAS"] - params = request.args - if "ticket" in params: - base_url = current_app.config["API_ENDPOINT"] + "/gn_auth/login_cas" - url_validate = "{url}?ticket={ticket}&service={service}".format( - url=config_cas["CAS_URL_VALIDATION"], - ticket=params["ticket"], - service=base_url, - ) - - response = utilsrequests.get(url_validate) - data = None - xml_dict = xmltodict.parse(response.content) - resp = xml_dict["cas:serviceResponse"] - if "cas:authenticationSuccess" in resp: - data = resp["cas:authenticationSuccess"]["cas:user"] - if data: - ws_user_url = "{url}/{user}/?verify=false".format( - url=config_cas["CAS_USER_WS"]["URL"], user=data - ) - try: - response = utilsrequests.get( - ws_user_url, - ( - config_cas["CAS_USER_WS"]["ID"], - config_cas["CAS_USER_WS"]["PASSWORD"], - ), - ) - assert response.status_code == 200 - except AssertionError: - log.error("Error with the inpn authentification service") - raise CasAuthentificationError( - "Error with the inpn authentification service", status_code=500 - ) - info_user = response.json() - data = insert_user_and_org(info_user, update_user_organism=False) - db.session.commit() - - # creation de la Response - response = make_response(redirect(current_app.config["URL_APPLICATION"])) - cookie_exp = datetime.datetime.utcnow() - expiration = current_app.config["COOKIE_EXPIRATION"] - cookie_exp += datetime.timedelta(seconds=expiration) - data["id_application"] = ( - db.session.execute( - select(Application).filter_by( - code_application=current_app.config["CODE_APPLICATION"] - ) - ) - .scalar_one() - .id_application - ) - token = encode_token(data) - - token_exp = datetime.datetime.now(datetime.timezone.utc) - token_exp += datetime.timedelta(seconds=current_app.config["COOKIE_EXPIRATION"]) - - # User cookie - organism_id = info_user["codeOrganisme"] - if not organism_id: - organism_id = ( - db.session.execute( - select(Organisme).filter_by(nom_organisme="Autre"), - ) - .scalar_one() - .id_organisme, - ) - current_user = { - "user_login": data["identifiant"], - "id_role": data["id_role"], - "id_organisme": organism_id, - } - - # Log the user in - user = db.session.execute( - sa.select(models.User) - .where(models.User.identifiant == current_user["user_login"]) - .where(models.User.filter_by_app()) - ).scalar_one() - login_user(user) - - return response - else: - log.info("Erreur d'authentification lié au CAS, voir log du CAS") - log.error("Erreur d'authentification lié au CAS, voir log du CAS") - return render_template( - "cas_login_error.html", - cas_logout=current_app.config["CAS_PUBLIC"]["CAS_URL_LOGOUT"], - url_geonature=current_app.config["URL_APPLICATION"], - ) - return jsonify({"message": "Authentification error"}, 500) - - -@routes.route("/logout_cruved", methods=["GET"]) -@json_resp -def logout_cruved(): - """ - Route to logout with cruved - To avoid multiples server call, we store the cruved in the session - when the user logout we need clear the session to get the new cruved session - - .. :quickref: User; - """ - copy_session_key = copy(session) - for key in copy_session_key: - session.pop(key) - return "Logout", 200 - - -def get_user_from_id_inpn_ws(id_user): - URL = f"https://inpn.mnhn.fr/authentication/rechercheParId/{id_user}" - config_cas = current_app.config["CAS"] - try: - response = utilsrequests.get( - URL, - ( - config_cas["CAS_USER_WS"]["ID"], - config_cas["CAS_USER_WS"]["PASSWORD"], - ), - ) - assert response.status_code == 200 - return response.json() - except AssertionError: - log.error("Error with the inpn authentification service") - - -def insert_user_and_org(info_user, update_user_organism: bool = True): - organism_id = info_user["codeOrganisme"] - organism_name = info_user.get("libelleLongOrganisme", "Autre") - user_login = info_user["login"] - user_id = info_user["id"] - - try: - assert user_id is not None and user_login is not None - except AssertionError: - log.error("'CAS ERROR: no ID or LOGIN provided'") - raise CasAuthentificationError("CAS ERROR: no ID or LOGIN provided", status_code=500) - - # Reconciliation avec base GeoNature - if organism_id: - organism = {"id_organisme": organism_id, "nom_organisme": organism_name} - insert_or_update_organism(organism) - - # Retrieve user information from `info_user` - user_info = { - "id_role": user_id, - "identifiant": user_login, - "nom_role": info_user["nom"], - "prenom_role": info_user["prenom"], - "id_organisme": organism_id, - "email": info_user["email"], - "active": True, - } - - # If not updating user organism and user already exists, retrieve existing user organism information rather than information from `info_user` - existing_user = User.query.get(user_id) - if not update_user_organism and existing_user: - user_info["id_organisme"] = existing_user.id_organisme - - # Insert or update user - user_info = insert_or_update_role(user_info) - - # Associate user to a default group if the user is not associated to any group - user = existing_user or db.session.get(User, user_id) - if not user.groups: - if current_app.config["CAS"]["USERS_CAN_SEE_ORGANISM_DATA"] and organism_id: - # group socle 2 - for a user associated to an organism if users can see data from their organism - group_id = current_app.config["BDD"]["ID_USER_SOCLE_2"] - else: - # group socle 1 - group_id = current_app.config["BDD"]["ID_USER_SOCLE_1"] - group = db.session.get(User, group_id) - user.groups.append(group) - - return user_info diff --git a/backend/geonature/core/auth/templates/cas_login_error.html b/backend/geonature/core/auth/templates/cas_login_error.html deleted file mode 100644 index ab90a5a933..0000000000 --- a/backend/geonature/core/auth/templates/cas_login_error.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - -

Echec de l'authentification

-

Deconnectez-vous du service INPN avant de retenter - une connexion à GeoNature

-

Deconnexion

-

Retour vers GeoNature

- - diff --git a/backend/geonature/core/command/main.py b/backend/geonature/core/command/main.py index 4b63ba18dc..a5db10d0e4 100644 --- a/backend/geonature/core/command/main.py +++ b/backend/geonature/core/command/main.py @@ -115,7 +115,6 @@ def default_config(): required_fields = ( "URL_APPLICATION", "API_ENDPOINT", - "API_TAXHUB", "SECRET_KEY", "SQLALCHEMY_DATABASE_URI", ) diff --git a/backend/geonature/core/gn_commons/routes.py b/backend/geonature/core/gn_commons/routes.py index c48b73d025..e82a8ac264 100644 --- a/backend/geonature/core/gn_commons/routes.py +++ b/backend/geonature/core/gn_commons/routes.py @@ -68,6 +68,7 @@ def list_modules(): query = ( select(TModules) .options(joinedload(TModules.objects)) + .options(joinedload(TModules.destination)) .where(TModules.module_code.notin_(exclude)) .order_by(TModules.module_order.asc()) .order_by(TModules.module_label.asc()) @@ -80,7 +81,7 @@ def list_modules(): # HACK : on a besoin d'avoir le module GeoNature en front pour l'URL de la doc if module.module_code == "GEONATURE": module_allowed = True - module_dict = module.as_dict(fields=["objects"]) + module_dict = module.as_dict(fields=["objects", "destination.code"]) # TODO : use has_any_permissions instead - must refactor the front module_dict["cruved"] = { action: get_scope(action, module_code=module.module_code, bypass_warning=True) @@ -88,10 +89,10 @@ def list_modules(): } if any(module_dict["cruved"].values()): module_allowed = True - if module.active_frontend: - module_dict["module_url"] = module.module_path - else: - module_dict["module_url"] = module.module_external_url + module_dict["module_external_url"] = ( + "" if module.active_frontend else module.module_external_url + ) + module_dict["module_url"] = module.module_path if module.active_frontend else "" module_dict["module_objects"] = {} # get cruved for each object for obj_dict in module_dict["objects"]: diff --git a/backend/geonature/core/gn_meta/models/aframework.py b/backend/geonature/core/gn_meta/models/aframework.py index e959ac4f9e..73672f660c 100644 --- a/backend/geonature/core/gn_meta/models/aframework.py +++ b/backend/geonature/core/gn_meta/models/aframework.py @@ -121,14 +121,21 @@ def user_actors(self): def organism_actors(self): return [actor.organism for actor in self.cor_af_actor if actor.organism] - def is_deletable(self): - return not ( - db.session.scalar( - exists() - .select_from() - .where(TDatasets.id_acquisition_framework == self.id_acquisition_framework) - .select() + def has_datasets(self): + return db.session.scalar( + exists(TDatasets) + .where(TDatasets.id_acquisition_framework == self.id_acquisition_framework) + .select() + ) + + def has_child_acquisition_framework(self): + return db.session.scalar( + exists(TAcquisitionFramework) + .where( + TAcquisitionFramework.acquisition_framework_parent_id + == self.id_acquisition_framework ) + .select() ) def has_instance_permission(self, scope, _through_ds=True): diff --git a/backend/geonature/core/gn_meta/mtd/__init__.py b/backend/geonature/core/gn_meta/mtd/__init__.py deleted file mode 100644 index fb7b11ec05..0000000000 --- a/backend/geonature/core/gn_meta/mtd/__init__.py +++ /dev/null @@ -1,301 +0,0 @@ -import logging -import time -from urllib.parse import urljoin - -from lxml import etree -import requests - -from geonature.core.auth.routes import insert_user_and_org -from geonature.core.gn_meta.models import ( - CorAcquisitionFrameworkActor, - CorDatasetActor, - TAcquisitionFramework, -) -from geonature.utils.config import config -from geonature.utils.env import db - -from pypnnomenclature.models import TNomenclatures -from pypnusershub.db.models import User -from sqlalchemy import func, select - -from .mtd_utils import associate_actors, sync_af, sync_ds -from .xml_parser import parse_acquisition_framework, parse_acquisition_framwork_xml, parse_jdd_xml - -# create logger -logger = logging.getLogger("MTD_SYNC") -# config logger -logger.setLevel(config["MTD"]["SYNC_LOG_LEVEL"]) -handler = logging.StreamHandler() -formatter = logging.Formatter("%(asctime)s | %(levelname)s : %(message)s", "%Y-%m-%d %H:%M:%S") -handler.setFormatter(formatter) -logger.addHandler(handler) -# avoid logging output dupplication -logger.propagate = False - - -class MTDInstanceApi: - af_path = "/mtd/cadre/export/xml/GetRecordsByInstanceId?id={ID_INSTANCE}" - ds_path = "/mtd/cadre/jdd/export/xml/GetRecordsByInstanceId?id={ID_INSTANCE}" - ds_user_path = "/mtd/cadre/jdd/export/xml/GetRecordsByUserId?id={ID_ROLE}" - af_user_path = "/mtd/cadre/export/xml/GetRecordsByUserId?id={ID_ROLE}" - single_af_path = "/mtd/cadre/export/xml/GetRecordById?id={ID_AF}" # NOTE: `ID_AF` is actually an UUID and not an ID from the point of view of geonature database. - - # https://inpn.mnhn.fr/mtd/cadre/jdd/export/xml/GetRecordsByUserId?id=41542" - def __init__(self, api_endpoint, instance_id, id_role=None): - self.api_endpoint = api_endpoint - self.instance_id = instance_id - self.id_role = id_role - - def _get_xml_by_url(self, url): - logger.debug("MTD - REQUEST : %s" % url) - response = requests.get(url) - response.raise_for_status() - return response.content - - def _get_xml(self, path): - url = urljoin(self.api_endpoint, path) - url = url.format(ID_INSTANCE=self.instance_id) - return self._get_xml_by_url(url) - - def _get_af_xml(self): - return self._get_xml(self.af_path) - - def get_af_list(self): - xml = self._get_af_xml() - _xml_parser = etree.XMLParser(ns_clean=True, recover=True, encoding="utf-8") - root = etree.fromstring(xml, parser=_xml_parser) - af_iter = root.iterfind(".//{http://inpn.mnhn.fr/mtd}CadreAcquisition") - af_list = [] - for af in af_iter: - af_list.append(parse_acquisition_framework(af)) - return af_list - - def _get_ds_xml(self): - return self._get_xml(self.ds_path) - - def get_ds_list(self): - xml = self._get_ds_xml() - return parse_jdd_xml(xml) - - def get_ds_user_list(self): - """ - Retrieve the list of of datasets (ds) for the user. - - Returns - ------- - list - A list of datasets (ds) for the user. - """ - url = urljoin(self.api_endpoint, self.ds_user_path) - url = url.format(ID_ROLE=self.id_role) - try: - xml = self._get_xml_by_url(url) - except requests.HTTPError as http_error: - error_code = http_error.response.status_code - warning_message = f"""[HTTPError : {error_code}] for URL "{url}".""" - if error_code == 404: - warning_message = f"""{warning_message} > Probably no dataset found for the user with ID '{self.id_role}'""" - logger.warning(warning_message) - return [] - ds_list = parse_jdd_xml(xml) - return ds_list - - def get_list_af_for_user(self): - """ - Retrieve a list of acquisition frameworks (af) for the user. - - Returns - ------- - list - A list of acquisition frameworks for the user. - """ - url = urljoin(self.api_endpoint, self.af_user_path).format(ID_ROLE=self.id_role) - try: - xml = self._get_xml_by_url(url) - except requests.HTTPError as http_error: - error_code = http_error.response.status_code - warning_message = f"""[HTTPError : {error_code}] for URL "{url}".""" - if error_code == 404: - warning_message = f"""{warning_message} > Probably no acquisition framework found for the user with ID '{self.id_role}'""" - logger.warning(warning_message) - return [] - _xml_parser = etree.XMLParser(ns_clean=True, recover=True, encoding="utf-8") - root = etree.fromstring(xml, parser=_xml_parser) - af_iter = root.findall(".//{http://inpn.mnhn.fr/mtd}CadreAcquisition") - af_list = [parse_acquisition_framework(af) for af in af_iter] - return af_list - - def get_single_af(self, af_uuid): - """ - Return a single acquistion framework based on its uuid. - - Parameters - ---------- - af_uuid : str - uuid of the acquisition framework - - Returns - ------- - dict - acquisition framework data - """ - url = urljoin(self.api_endpoint, self.single_af_path) - url = url.format(ID_AF=af_uuid) - xml = self._get_xml_by_url(url) - return parse_acquisition_framwork_xml(xml) - - -class INPNCAS: - base_url = config["CAS"]["CAS_USER_WS"]["BASE_URL"] - user = config["CAS"]["CAS_USER_WS"]["ID"] - password = config["CAS"]["CAS_USER_WS"]["PASSWORD"] - id_search_path = "rechercheParId/{user_id}" - - @classmethod - def _get_user_json(cls, user_id): - url = urljoin(cls.base_url, cls.id_search_path) - url = url.format(user_id=user_id) - response = requests.get(url, auth=(cls.user, cls.password)) - return response.json() - - @classmethod - def get_user(cls, user_id): - return cls._get_user_json(user_id) - - -def add_unexisting_digitizer(id_digitizer): - """ - Method to trigger global MTD sync. - - :param id_digitizer: as id role from meta info - """ - if ( - not db.session.scalar( - select(func.count("*")).select_from(User).filter_by(id_role=id_digitizer) - ) - > 0 - ): - # not fast - need perf optimization on user call - user = INPNCAS.get_user(id_digitizer) - # to avoid to create org - if user.get("codeOrganisme"): - user["codeOrganisme"] = None - # insert or update user - insert_user_and_org(user) - - -def process_af_and_ds(af_list, ds_list, id_role=None): - """ - Synchro AF, Synchro DS - - :param af_list: list af - :param ds_list: list ds - :param id_role: use role id pass on user authent only - """ - cas_api = INPNCAS() - # read nomenclatures from DB to avoid errors if GN nomenclature is not the same - list_cd_nomenclature = db.session.scalars( - select(TNomenclatures.cd_nomenclature).distinct() - ).all() - user_add_total_time = 0 - logger.debug("MTD - PROCESS AF LIST") - for af in af_list: - actors = af.pop("actors") - with db.session.begin_nested(): - start_add_user_time = time.time() - add_unexisting_digitizer(af["id_digitizer"] if not id_role else id_role) - user_add_total_time += time.time() - start_add_user_time - af = sync_af(af) - associate_actors( - actors, - CorAcquisitionFrameworkActor, - "id_acquisition_framework", - af.id_acquisition_framework, - ) - # TODO: remove actors removed from MTD - db.session.commit() - logger.debug("MTD - PROCESS DS LIST") - for ds in ds_list: - actors = ds.pop("actors") - # CREATE DIGITIZER - with db.session.begin_nested(): - start_add_user_time = time.time() - if not id_role: - add_unexisting_digitizer(ds["id_digitizer"]) - else: - add_unexisting_digitizer(id_role) - user_add_total_time += time.time() - start_add_user_time - ds = sync_ds(ds, list_cd_nomenclature) - if ds is not None: - associate_actors(actors, CorDatasetActor, "id_dataset", ds.id_dataset) - - user_add_total_time = round(user_add_total_time, 2) - db.session.commit() - - -def sync_af_and_ds(): - """ - Method to trigger global MTD sync. - """ - logger.info("MTD - SYNC GLOBAL : START") - mtd_api = MTDInstanceApi(config["MTD_API_ENDPOINT"], config["MTD"]["ID_INSTANCE_FILTER"]) - - af_list = mtd_api.get_af_list() - - ds_list = mtd_api.get_ds_list() - - # synchro a partir des listes - process_af_and_ds(af_list, ds_list) - logger.info("MTD - SYNC GLOBAL : FINISH") - - -def sync_af_and_ds_by_user(id_role, id_af=None): - """ - Method to trigger MTD sync on user authentication. - - Args: - id_role (int): The ID of the role (group or user). - id_af (str, optional): The ID of the AF (Acquisition Framework). Defaults to None. - """ - - logger.info("MTD - SYNC USER : START") - - # Create an instance of MTDInstanceApi - mtd_api = MTDInstanceApi( - config["MTD_API_ENDPOINT"], config["MTD"]["ID_INSTANCE_FILTER"], id_role - ) - - # Get the list of datasets (ds) for the user - # NOTE: `mtd_api.get_ds_user_list()` tested and timed to about 7 seconds on the PROD instance 'GINCO Occtax' with id_role = 13829 > a user with a lot of metadata to be retrieved from 'INPN Métadonnées' to 'GINCO Occtax' - ds_list = mtd_api.get_ds_user_list() - - if not id_af: - # Get the unique UUIDs of the acquisition frameworks for the user - set_user_af_uuids = {ds["uuid_acquisition_framework"] for ds in ds_list} - user_af_uuids = list(set_user_af_uuids) - - # TODO - voir avec INPN pourquoi les AF par user ne sont pas dans l'appel global des AF - # Ce code ne fonctionne pas pour cette raison -> AF manquants - # af_list = mtd_api.get_af_list() - # af_list = [af for af in af_list if af["unique_acquisition_framework_id"] in user_af_uuids] - - # Get the list of acquisition frameworks for the user - # call INPN API for each AF to retrieve info - af_list = mtd_api.get_list_af_for_user() - else: - # TODO: handle case where the AF ; corresponding to the provided `id_af` ; does not exist yet in the database - # this case should not happend from a user action because the only case where `id_af` is provided is for when the user click to unroll an AF in the module Metadata, in which case the AF already exists in the database. - # It would still be better to handle case where the AF does not exist in the database, and to first retrieve the AF from 'INPN Métadonnées' in this case - uuid_af = TAcquisitionFramework.query.get(id_af).unique_acquisition_framework_id - uuid_af = str(uuid_af).upper() - - # Get the acquisition framework for the specified UUID, thus a list of one element - af_list = [mtd_api.get_single_af(uuid_af)] - - # Filter the datasets based on the specified UUID - ds_list = [ds for ds in ds_list if ds["uuid_acquisition_framework"] == uuid_af] - - # Process the acquisition frameworks and datasets - process_af_and_ds(af_list, ds_list, id_role) - - logger.info("MTD - SYNC USER : FINISH") diff --git a/backend/geonature/core/gn_meta/mtd/mtd_utils.py b/backend/geonature/core/gn_meta/mtd/mtd_utils.py deleted file mode 100644 index e8bde8bf04..0000000000 --- a/backend/geonature/core/gn_meta/mtd/mtd_utils.py +++ /dev/null @@ -1,248 +0,0 @@ -import logging -import json -from copy import copy -from flask import current_app - -from sqlalchemy import select, exists -from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.sql import func, update - -from sqlalchemy.dialects.postgresql import insert as pg_insert - -from geonature.utils.env import DB -from geonature.core.gn_meta.models import ( - TDatasets, - CorDatasetActor, - TAcquisitionFramework, - CorAcquisitionFrameworkActor, -) -from geonature.core.gn_commons.models import TModules -from pypnusershub.db.models import Organisme as BibOrganismes, User -from geonature.core.users import routes as users -from geonature.core.auth.routes import insert_user_and_org, get_user_from_id_inpn_ws - -from .xml_parser import parse_acquisition_framwork_xml, parse_jdd_xml -from .mtd_webservice import get_jdd_by_user_id, get_acquisition_framework, get_jdd_by_uuid - -NOMENCLATURE_MAPPING = { - "cd_nomenclature_data_type": "DATA_TYP", - "cd_nomenclature_dataset_objectif": "JDD_OBJECTIFS", - "cd_nomenclature_data_origin": "DS_PUBLIQUE", - "cd_nomenclature_source_status": "STATUT_SOURCE", -} - -# get the root logger -log = logging.getLogger() - - -def sync_ds(ds, cd_nomenclatures): - """ - Will create or update a given DS according to UUID. - Only process DS if dataset's cd_nomenclatures exists in ref_normenclatures.t_nomenclatures. - - :param ds: DS infos - :param cd_nomenclatures: cd_nomenclature from ref_normenclatures.t_nomenclatures - """ - if not ds["cd_nomenclature_data_origin"]: - ds["cd_nomenclature_data_origin"] = "NSP" - - # FIXME: the following temporary fix was added due to possible differences in referential of nomenclatures values between INPN and GeoNature - # should be fixed by ensuring that the two referentials are identical, at least for instances that integrates with INPN and thus rely on MTD synchronization from INPN Métadonnées: GINCO and DEPOBIO instances. - if ds["cd_nomenclature_data_origin"] not in cd_nomenclatures: - return - - # CONTROL AF - af_uuid = ds.pop("uuid_acquisition_framework") - af = ( - DB.session.execute( - select(TAcquisitionFramework).filter_by(unique_acquisition_framework_id=af_uuid) - ) - .unique() - .scalar_one_or_none() - ) - - if af is None: - log.warning(f"AF with UUID '{af_uuid}' not found in database.") - return - - ds["id_acquisition_framework"] = af.id_acquisition_framework - ds = { - field.replace("cd_nomenclature", "id_nomenclature"): ( - func.ref_nomenclatures.get_id_nomenclature(NOMENCLATURE_MAPPING[field], value) - if field.startswith("cd_nomenclature") - else value - ) - for field, value in ds.items() - if value is not None - } - - ds_exists = DB.session.scalar( - exists() - .where( - TDatasets.unique_dataset_id == ds["unique_dataset_id"], - ) - .select() - ) - - statement = ( - pg_insert(TDatasets) - .values(**ds) - .on_conflict_do_nothing(index_elements=["unique_dataset_id"]) - ) - if ds_exists: - statement = ( - update(TDatasets) - .where(TDatasets.unique_dataset_id == ds["unique_dataset_id"]) - .values(**ds) - ) - DB.session.execute(statement) - - dataset = DB.session.scalars( - select(TDatasets).filter_by(unique_dataset_id=ds["unique_dataset_id"]) - ).first() - - # Associate dataset to the modules if new dataset - if not ds_exists: - associate_dataset_modules(dataset) - - return dataset - - -def sync_af(af): - """Will update a given AF (Acquisition Framework) if already exists in database according to UUID, else update the AF. - - Parameters - ---------- - af : dict - AF infos. - - Returns - ------- - TAcquisitionFramework - The updated or inserted acquisition framework. - """ - af_uuid = af["unique_acquisition_framework_id"] - af_exists = DB.session.scalar( - exists().where(TAcquisitionFramework.unique_acquisition_framework_id == af_uuid).select() - ) - - # Update statement if AF already exists in DB else insert statement - statement = ( - update(TAcquisitionFramework) - .where(TAcquisitionFramework.unique_acquisition_framework_id == af_uuid) - .values(**af) - ) - if not af_exists: - statement = ( - pg_insert(TAcquisitionFramework) - .values(**af) - .on_conflict_do_nothing(index_elements=["unique_acquisition_framework_id"]) - ) - DB.session.execute(statement) - - acquisition_framework = DB.session.scalars( - select(TAcquisitionFramework).filter_by(unique_acquisition_framework_id=af_uuid) - ).first() - - return acquisition_framework - - -def add_or_update_organism(uuid, nom, email): - """ - Create or update organism if UUID not exists in DB. - - :param uuid: uniq organism uuid - :param nom: org name - :param email: org email - """ - # Test if actor already exists to avoid nextVal increase - org_exist = DB.session.scalar(exists().where(BibOrganismes.uuid_organisme == uuid).select()) - - if org_exist: - statement = ( - update(BibOrganismes) - .where(BibOrganismes.uuid_organisme == uuid) - .values( - dict( - nom_organisme=nom, - email_organisme=email, - ) - ) - .returning(BibOrganismes.id_organisme) - ) - else: - statement = ( - pg_insert(BibOrganismes) - .values( - uuid_organisme=uuid, - nom_organisme=nom, - email_organisme=email, - ) - .on_conflict_do_nothing(index_elements=["uuid_organisme"]) - .returning(BibOrganismes.id_organisme) - ) - return DB.session.execute(statement).scalar() - - -def associate_actors(actors, CorActor, pk_name, pk_value): - """ - Associate actor and DS or AF according to CorActor value. - - Parameters - ---------- - actors : list - list of actors - CorActor : db.Model - table model - pk_name : str - pk attribute name - pk_value : str - pk value - """ - for actor in actors: - id_organism = None - uuid_organism = actor["uuid_organism"] - if uuid_organism: - with DB.session.begin_nested(): - # create or update organisme - # FIXME: prevent update of organism email from actor email ! Several actors may be associated to the same organism and still have different mails ! - id_organism = add_or_update_organism( - uuid=uuid_organism, - nom=actor["organism"] if actor["organism"] else "", - email=actor["email"], - ) - values = dict( - id_nomenclature_actor_role=func.ref_nomenclatures.get_id_nomenclature( - "ROLE_ACTEUR", actor["actor_role"] - ), - **{pk_name: pk_value}, - ) - if not id_organism: - values["id_role"] = DB.session.scalar( - select(User.id_role).filter_by(email=actor["email"]) - ) - else: - values["id_organism"] = id_organism - statement = ( - pg_insert(CorActor) - .values(**values) - .on_conflict_do_nothing( - index_elements=[pk_name, "id_organism", "id_nomenclature_actor_role"], - ) - ) - DB.session.execute(statement) - - -def associate_dataset_modules(dataset): - """ - Associate a dataset to modules specified in [MTD][JDD_MODULE_CODE_ASSOCIATION] parameter (geonature config) - - :param dataset: dataset (SQLAlchemy model object) - """ - dataset.modules.extend( - DB.session.scalars( - select(TModules).where( - TModules.module_code.in_(current_app.config["MTD"]["JDD_MODULE_CODE_ASSOCIATION"]) - ) - ).all() - ) diff --git a/backend/geonature/core/gn_meta/mtd/mtd_webservice.py b/backend/geonature/core/gn_meta/mtd/mtd_webservice.py deleted file mode 100644 index 78d90976cf..0000000000 --- a/backend/geonature/core/gn_meta/mtd/mtd_webservice.py +++ /dev/null @@ -1,52 +0,0 @@ -from geonature.utils import utilsrequests -from geonature.utils.errors import GeonatureApiError -from geonature.utils.config import config - -api_endpoint = config["MTD_API_ENDPOINT"] - - -def get_acquisition_framework(uuid_af): - """ - Fetch a AF from the MTD WS with the uuid of the AD - - Parameters: - - uuid_af (str): the uuid of the AF - Returns: - byte: the xml of the AF as byte - """ - url = "{}/cadre/export/xml/GetRecordById?id={}" - try: - r = utilsrequests.get(url.format(api_endpoint, uuid_af)) - except AssertionError: - raise GeonatureApiError( - message="Error with the MTD Web Service while getting Acquisition Framwork" - ) - return r.content - - -def get_jdd_by_user_id(id_user): - """fetch the jdd(s) created by a user from the MTD web service - Parameters: - - id (int): id_user from CAS - Return: - byte: a XML as byte - """ - url = "{}/cadre/jdd/export/xml/GetRecordsByUserId?id={}" - try: - r = utilsrequests.get(url.format(api_endpoint, str(id_user))) - assert r.status_code == 200 - except AssertionError: - raise GeonatureApiError( - message="Error with the MTD Web Service (JDD), status_code: {}".format(r.status_code) - ) - return r.content - - -def get_jdd_by_uuid(uuid): - ds_URL = f"{api_endpoint}/cadre/jdd/export/xml/GetRecordById?id={uuid.upper()}" - try: - r = utilsrequests.get(ds_URL) - assert r.status_code == 200 - except AssertionError: - print(f"NO JDD FOUND FOR UUID {uuid}") - return r.content diff --git a/backend/geonature/core/gn_meta/mtd/xml_parser.py b/backend/geonature/core/gn_meta/mtd/xml_parser.py deleted file mode 100644 index efcf98d6be..0000000000 --- a/backend/geonature/core/gn_meta/mtd/xml_parser.py +++ /dev/null @@ -1,210 +0,0 @@ -import datetime -import json - -from flask import current_app -from lxml import etree as ET - -from geonature.utils.config import config -from geonature.core.gn_meta.models import TAcquisitionFramework - - -namespace = config["XML_NAMESPACE"] - -_xml_parser = ET.XMLParser(ns_clean=True, recover=True, encoding="utf-8") - - -def get_tag_content(parent, tag_name, default_value=None): - """ - Return the content of a xml tag - Check if the node exist or return a default value - Params: - parent (etree Element): the parent where find the tag - tag_name (str): the name of the tag - default_value (any): the default value f the tag doesn't exist - Return - any: the tag content or the default value - """ - tag = parent.find(namespace + tag_name) - if tag is not None: - if tag.text and len(tag.text) > 0: - return tag.text - return default_value - - -def parse_actors_xml(actors): - """ - Parse the parameters of the Actor provided as an XML node in the input variable "actors" - Param: - actors (etree Element): Node of an actor type containing from one to multiple actors - Returns: - dict: A dictionnary of the actors informations - """ - actor_list = [] - if actors is not None: - for actor_node in actors: - name = get_tag_content(actor_node, "nomPrenom") - actor_role = get_tag_content(actor_node, "roleActeur") - uuid_organism = get_tag_content(actor_node, "idOrganisme") - organism = get_tag_content(actor_node, "organisme") - email = get_tag_content(actor_node, "mail") - - actor_list.append( - { - "name": name, - "uuid_organism": uuid_organism, - "organism": organism, - "actor_role": actor_role, - "email": email, - } - ) - - return actor_list - - -def parse_acquisition_framwork_xml(xml): - """ - Parse an xml of AF from a string - Return: - dict: a dict of the parsed xml - """ - root = ET.fromstring(xml, parser=_xml_parser) - ca = root.find(".//" + namespace + "CadreAcquisition") - return parse_acquisition_framework(ca) - - -def parse_acquisition_framework(ca): - # We extract all the required informations from the different tags of the XML file - ca_uuid = get_tag_content(ca, "identifiantCadre") - ca_name_max_length = TAcquisitionFramework.acquisition_framework_name.property.columns[ - 0 - ].type.length - ca_name = get_tag_content(ca, "libelle")[: ca_name_max_length - 1] - ca_desc = get_tag_content(ca, "description", default_value="") - date_info = ca.find(namespace + "ReferenceTemporelle") - ca_create_date = get_tag_content(ca, "dateCreationMtd", default_value=datetime.datetime.now()) - ca_update_date = get_tag_content(ca, "dateMiseAJourMtd") - ca_start_date = get_tag_content( - date_info, "dateLancement", default_value=datetime.datetime.now() - ) - ca_end_date = get_tag_content(date_info, "dateCloture") - ca_id_digitizer = None - attributs_additionnels_node = ca.find(namespace + "attributsAdditionnels") - - # We extract the ID of the user to assign it the JDD as an id_digitizer - for attr in attributs_additionnels_node: - if get_tag_content(attr, "nomAttribut") == "ID_CREATEUR": - ca_id_digitizer = get_tag_content(attr, "valeurAttribut") - - # We search for all the Contact nodes : - # - Main contact in acteurPrincipal node - # - Funder in acteurAutre node - # - Project owner in acteurAutre node - # - Project manager in acteurAutre node - list_contact_tags = ["acteurPrincipal", "acteurAutre"] - all_actors = [] - for contact_tag in list_contact_tags: - if get_tag_content(ca, contact_tag) is not None: - for actor_node in ca.findall(namespace + contact_tag): - actor = parse_actors_xml(actor_node) - all_actors = all_actors + actor - - return { - "unique_acquisition_framework_id": ca_uuid, - "acquisition_framework_name": ca_name, - "acquisition_framework_desc": ca_desc, - "acquisition_framework_start_date": ca_start_date, - "acquisition_framework_end_date": ca_end_date, - "meta_create_date": ca_create_date, - "meta_update_date": ca_update_date, - "id_digitizer": ca_id_digitizer, - "actors": all_actors, - } - - -def parse_jdd_xml(xml): - """ - Parse an xml of datasets from a string - Return: - list: a list of dict of the JDD in the xml - """ - - root = ET.fromstring(xml, parser=_xml_parser) - jdd_list = [] - for jdd in root.findall(".//" + namespace + "JeuDeDonnees"): - # We extract all the required informations from the different tags of the XML file - jdd_uuid = get_tag_content(jdd, "identifiantJdd") - ca_uuid = get_tag_content(jdd, "identifiantCadre") - dataset_name = get_tag_content(jdd, "libelle") - dataset_shortname = get_tag_content(jdd, "libelleCourt", default_value="") - dataset_desc = get_tag_content(jdd, "description", default_value="") - terrestrial_domain = get_tag_content(jdd, "domaineTerrestre", default_value=False) - marine_domain = get_tag_content(jdd, "domaineMarin", default_value=False) - data_type = get_tag_content(jdd, "typeDonnees") - collect_data_type = get_tag_content(jdd, "typeDonneesCollectees") - create_date = get_tag_content(jdd, "dateCreation", default_value=datetime.datetime.now()) - update_date = get_tag_content(jdd, "dateRevision") - attributs_additionnels_node = jdd.find(namespace + "attributsAdditionnels") - - # We extract the ID of the user to assign it the JDD as an id_digitizer - id_digitizer = None - id_instance = None - code_statut_donnees_source = None - for attr in attributs_additionnels_node: - if get_tag_content(attr, "nomAttribut") == "ID_CREATEUR": - id_digitizer = get_tag_content(attr, "valeurAttribut") - - if get_tag_content(attr, "nomAttribut") == "ID_INSTANCE": - id_instance = get_tag_content(attr, "valeurAttribut") - - if get_tag_content(attr, "nomAttribut") == "CODE_STATUT_DONNEES_SOURCE": - code_statut_donnees_source = get_tag_content(attr, "valeurAttribut") - - # We search for all the Contact nodes : - # - Main contact in pointContactPF node - # - JDD provider in pointContactJdd node - # - JDD builder in pointContactJdd node - # - Database contact in contactBaseProduction node - list_contact_tags = ["pointContactPF", "pointContactJdd", "contactBaseProduction"] - all_actors = [] - for contact_tag in list_contact_tags: - if contact_tag == "contactBaseProduction": - contact_node = jdd.find(namespace + "BaseProduction") - else: - contact_node = jdd - if get_tag_content(contact_node, contact_tag) is not None: - for actor_node in contact_node.findall(namespace + contact_tag): - actor = parse_actors_xml(actor_node) - all_actors = all_actors + actor - - keywords = None - - # We build the JDD data from all the variables collected from the XML file - current_jdd = { - "unique_dataset_id": jdd_uuid, - "uuid_acquisition_framework": ca_uuid, - "dataset_name": dataset_name if len(dataset_name) < 256 else f"{dataset_name[:253]}...", - "dataset_shortname": dataset_shortname, - "dataset_desc": ( - dataset_desc - if len(dataset_name) < 256 - else f"Nom complet du jeu de données dans MTD : {dataset_name}\n {dataset_desc}" - ), - "keywords": keywords, - "terrestrial_domain": json.loads(terrestrial_domain), - "marine_domain": json.loads(marine_domain), - "cd_nomenclature_data_type": data_type, - "id_digitizer": id_digitizer, - "cd_nomenclature_data_origin": code_statut_donnees_source, - "actors": all_actors, - "meta_create_date": create_date, - "meta_update_date": update_date, - } - - # filter with id_instance - if current_app.config["MTD"]["ID_INSTANCE_FILTER"]: - if id_instance and id_instance == str(current_app.config["MTD"]["ID_INSTANCE_FILTER"]): - jdd_list.append(current_jdd) - else: - jdd_list.append(current_jdd) - - return jdd_list diff --git a/backend/geonature/core/gn_meta/routes.py b/backend/geonature/core/gn_meta/routes.py index 5bb91bbee0..18814dfd90 100644 --- a/backend/geonature/core/gn_meta/routes.py +++ b/backend/geonature/core/gn_meta/routes.py @@ -1,5 +1,5 @@ """ - Routes for gn_meta + Routes for gn_meta """ import datetime as dt @@ -26,12 +26,10 @@ from geonature.utils.env import DB, db from geonature.core.gn_synthese.models import ( Synthese, - TSources, CorAreaSynthese, ) from geonature.core.gn_permissions.decorators import login_required -from .mtd import sync_af_and_ds as mtd_sync_af_and_ds, sync_af_and_ds_by_user from ref_geo.models import LAreas from pypnnomenclature.models import TNomenclatures @@ -56,11 +54,8 @@ from geonature.core.gn_permissions import decorators as permissions from geonature.core.gn_permissions.tools import get_scopes_by_action from geonature.core.gn_permissions.models import TObjects -from geonature.core.gn_meta.mtd import mtd_utils import geonature.utils.filemanager as fm import geonature.utils.utilsmails as mail -from geonature.utils.errors import GeonatureApiError -from .mtd import sync_af_and_ds as mtd_sync_af_and_ds from ref_geo.models import LAreas @@ -75,23 +70,6 @@ log = logging.getLogger() -if config["CAS_PUBLIC"]["CAS_AUTHENTIFICATION"]: - - @routes.before_request - def synchronize_mtd(): - if request.endpoint in ["gn_meta.get_datasets", "gn_meta.get_acquisition_frameworks_list"]: - from flask_login import current_user - - if current_user.is_authenticated: - params = request.json if request.is_json else request.args - try: - list_id_af = params.get("id_acquisition_frameworks", []) - for id_af in list_id_af: - sync_af_and_ds_by_user(id_role=current_user.id_role, id_af=id_af) - except Exception as e: - log.exception(f"Error while get JDD via MTD: {e}") - - @routes.route("/datasets", methods=["GET", "POST"]) @login_required def get_datasets(): @@ -265,14 +243,9 @@ def uuid_report(): select(Synthese) .where(Synthese.id_module == id_module if id_module is not None else True) .where(Synthese.id_dataset == ds_id if ds_id is not None else True) + .where(Synthese.id_import == id_import if id_import is not None else True) ) - # TODO test in module import ? - if id_import: - query = query.outerjoin(TSources, TSources.id_source == Synthese.id_source).where( - TSources.name_source == f"Import(id={id_import})" - ) - query = query.order_by(Synthese.id_synthese) data = [ @@ -344,13 +317,9 @@ def sensi_report(ds_id=None): .where(LAreas.id_type == func.ref_geo.get_id_area_type("DEP")) .where(Synthese.id_module == id_module if id_module else True) .where(Synthese.id_dataset == ds_id) + .where(Synthese.id_import == id_import if id_import else True) ) - if id_import: - query = query.outerjoin(TSources, TSources.id_source == Synthese.id_source).where( - TSources.name_source == "Import(id={})".format(id_import) - ) - query = query.group_by( Synthese.id_synthese, TNomenclatures.cd_nomenclature, TNomenclatures.label_fr ) @@ -793,11 +762,17 @@ def delete_acquisition_framework(scope, af_id): raise Forbidden( f"User {g.current_user} cannot delete acquisition framework {af.id_acquisition_framework}" ) - if not af.is_deletable(): + if af.has_datasets(): raise Conflict( - "La suppression du cadre d’acquisition n'est pas possible " + "La suppression du cadre d’acquisition est impossible " "car celui-ci contient des jeux de données." ) + + if af.has_child_acquisition_framework(): + raise Conflict( + "La suppression du cadre d’acquisition est impossible " + "car celui-ci est le parent d'autre(s) cadre(s) d'acquisition." + ) db.session.delete(af) db.session.commit() @@ -1064,24 +1039,3 @@ def publish_acquisition_framework(af_id): publish_acquisition_framework_mail(af) return af.as_dict() - - -@routes.cli.command() -@click.option("--id-role", nargs=1, required=False, default=None, help="ID of an user") -@click.option( - "--id-af", nargs=1, required=False, default=None, help="ID of an acquisition framework" -) -def mtd_sync(id_role, id_af): - """ - \b - Triggers : - - global sync for instance - - a sync for a given user only (if id_role is provided) - - a sync for a given AF (Acquisition Framework) only (if id_af is provided). NOTE: the AF should in this case already exist in the database, and only datasets associated to this AF will be retrieved - - NOTE: if both id_role and id_af are provided, only the datasets possibly associated to both the AF and the user will be retrieved. - """ - if id_role: - return sync_af_and_ds_by_user(id_role, id_af) - else: - return mtd_sync_af_and_ds() diff --git a/backend/geonature/core/gn_monitoring/routes.py b/backend/geonature/core/gn_monitoring/routes.py index eafe78c45d..0ad5889009 100644 --- a/backend/geonature/core/gn_monitoring/routes.py +++ b/backend/geonature/core/gn_monitoring/routes.py @@ -1,15 +1,12 @@ from flask import Blueprint, request -from sqlalchemy.sql import func from geojson import FeatureCollection - -from geonature.utils.env import DB from geonature.core.gn_monitoring.models import TBaseSites, corSiteArea, corSiteModule - -from utils_flask_sqla.response import json_resp -from utils_flask_sqla_geo.generic import get_geojson_feature +from geonature.utils.env import DB from ref_geo.models import LAreas from sqlalchemy import select - +from sqlalchemy.sql import func +from utils_flask_sqla.response import json_resp +from utils_flask_sqla_geo.generic import get_geojson_feature routes = Blueprint("gn_monitoring", __name__) @@ -82,6 +79,7 @@ def get_site_areas(id_site): params = request.args query = ( + # TODO@LAreas.geom_4326 select(corSiteArea, func.ST_Transform(LAreas.geom, 4326)) .join(LAreas, LAreas.id_area == corSiteArea.c.id_area) .where(corSiteArea.c.id_base_site == id_site) @@ -94,7 +92,7 @@ def get_site_areas(id_site): corSiteModule.c.id_module == params["id_module"] ) - data = DB.session.execute(query).all() + data = DB.session.scalars(query).all() features = [] for d in data: feature = get_geojson_feature(d[2]) diff --git a/backend/geonature/core/gn_permissions/admin.py b/backend/geonature/core/gn_permissions/admin.py index fa548f330d..885d2d7836 100644 --- a/backend/geonature/core/gn_permissions/admin.py +++ b/backend/geonature/core/gn_permissions/admin.py @@ -9,6 +9,7 @@ from markupsafe import Markup from sqlalchemy.orm import contains_eager, joinedload from sqlalchemy import select +from markupsafe import Markup from geonature.utils.env import db from geonature.utils.config import config diff --git a/backend/geonature/core/gn_permissions/tools.py b/backend/geonature/core/gn_permissions/tools.py index ec96a39ac0..4d1d86257e 100644 --- a/backend/geonature/core/gn_permissions/tools.py +++ b/backend/geonature/core/gn_permissions/tools.py @@ -4,7 +4,7 @@ import sqlalchemy as sa from sqlalchemy.orm import joinedload -from flask import g +from flask import has_request_context, g from geonature.core.gn_commons.models import TModules from geonature.core.gn_permissions.models import ( @@ -50,9 +50,12 @@ def _get_user_permissions(id_role): def get_user_permissions(id_role=None): if id_role is None: id_role = g.current_user.id_role - if id_role not in g._permissions_by_user: - g._permissions_by_user[id_role] = _get_user_permissions(id_role) - return g._permissions_by_user[id_role] + if has_request_context(): + if id_role not in g._permissions_by_user: + g._permissions_by_user[id_role] = _get_user_permissions(id_role) + return g._permissions_by_user[id_role] + else: + return _get_user_permissions(id_role) def _get_permissions(id_role, module_code, object_code, action_code): @@ -93,9 +96,12 @@ def get_permissions(action_code, id_role=None, module_code=None, object_code=Non object_code = "ALL" ident = (id_role, module_code, object_code, action_code) - if ident not in g._permissions: - g._permissions[ident] = _get_permissions(*ident) - return g._permissions[ident] + if has_request_context(): + if ident not in g._permissions: + g._permissions[ident] = _get_permissions(*ident) + return g._permissions[ident] + else: + return _get_permissions(*ident) def get_scope(action_code, id_role=None, module_code=None, object_code=None, bypass_warning=False): diff --git a/backend/geonature/core/gn_profiles/routes.py b/backend/geonature/core/gn_profiles/routes.py index 0277097822..b493cb8c1c 100644 --- a/backend/geonature/core/gn_profiles/routes.py +++ b/backend/geonature/core/gn_profiles/routes.py @@ -222,7 +222,7 @@ def get_observation_score(): if type(data["life_stages"]) is not list: raise BadRequest("life_stages must be a list") for life_stage in data["life_stages"]: - life_stage_value = DB.get(TNomenclatures, life_stage) + life_stage_value = DB.get_or_404(TNomenclatures, life_stage) q = q_pheno.where(VmCorTaxonPhenology.id_nomenclature_life_stage == life_stage) r_life_stage = DB.session.execute(q).all() if len(r_life_stage) == 0: diff --git a/backend/geonature/core/gn_synthese/imports/__init__.py b/backend/geonature/core/gn_synthese/imports/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/backend/geonature/core/gn_synthese/imports/__init__.py @@ -0,0 +1 @@ + diff --git a/backend/geonature/core/gn_synthese/imports/actions.py b/backend/geonature/core/gn_synthese/imports/actions.py new file mode 100644 index 0000000000..fc501b1d53 --- /dev/null +++ b/backend/geonature/core/gn_synthese/imports/actions.py @@ -0,0 +1,398 @@ +import typing +from math import ceil + +import sqlalchemy as sa +from apptax.taxonomie.models import Taxref +from bokeh.embed.standalone import StandaloneEmbedJson +from flask import current_app +from geonature.core.gn_commons.models import TModules +from geonature.core.gn_synthese.models import Synthese, TSources +from geonature.core.imports.actions import ( + ImportActions, + ImportInputUrl, + ImportStatisticsLabels, +) +from geonature.core.imports.checks.dataframe import ( + check_counts, + check_datasets, + check_geometry, + check_required_values, + check_types, + concat_dates, +) +from geonature.core.imports.checks.sql import ( + check_altitudes, + check_cd_hab, + check_cd_nom, + check_dates, + check_depths, + check_digital_proof_urls, + check_duplicate_source_pk, + check_duplicate_uuid, + check_existing_uuid, + check_geometry_outside, + check_is_valid_geometry, + check_nomenclature_blurring, + check_nomenclature_exist_proof, + check_nomenclature_source_status, + check_orphan_rows, + convert_geom_columns, + do_nomenclatures_mapping, + generate_altitudes, + generate_missing_uuid, + init_rows_validity, + set_geom_point, +) +from geonature.core.imports.models import BibFields, Entity, EntityField, TImports +from geonature.core.imports.utils import ( + compute_bounding_box, + load_transient_data_in_dataframe, + update_transient_data_from_dataframe, +) +from geonature.utils.env import db +from geonature.utils.sentry import start_sentry_child +from sqlalchemy import distinct, func, select + +from .geo import set_geom_columns_from_area_codes +from .plot import taxon_distribution_plot + + +class SyntheseImportActions(ImportActions): + + @staticmethod + def statistics_labels() -> typing.List[ImportStatisticsLabels]: + return [ + {"key": "import_count", "value": "Nombre d'observations importées"}, + {"key": "taxa_count", "value": "Nombre de taxons"}, + ] + + @staticmethod + def preprocess_transient_data(imprt: TImports, df) -> set: + pass + + @staticmethod + def check_transient_data(task, logger, imprt: TImports): + entity = db.session.execute( + select(Entity).where(Entity.destination == imprt.destination) + ).scalar_one() # Observation + + entity_bib_fields = db.session.scalars( + sa.select(BibFields) + .where(BibFields.destination == imprt.destination) + .options(sa.orm.selectinload(BibFields.entities).joinedload(EntityField.entity)) + ).all() + + fields = {field.name_field: field for field in entity_bib_fields} + # Note: multi fields are not selected here, and therefore, will be not loaded in dataframe or checked in SQL. + # Fix this code (see import function) if you want to operate on multi fields data. + selected_fields = { + field_name: fields[field_name] + for field_name, source_field in imprt.fieldmapping.items() + if source_field in imprt.columns + } + init_rows_validity(imprt) + task.update_state(state="PROGRESS", meta={"progress": 0.05}) + check_orphan_rows(imprt) + task.update_state(state="PROGRESS", meta={"progress": 0.1}) + + batch_size = current_app.config["IMPORT"]["DATAFRAME_BATCH_SIZE"] + batch_count = ceil(imprt.source_count / batch_size) + + def update_batch_progress(batch, step): + start = 0.1 + end = 0.4 + step_count = 8 + progress = start + ((batch + 1) / batch_count) * (step / step_count) * (end - start) + task.update_state(state="PROGRESS", meta={"progress": progress}) + + source_cols = [ + field.source_column + for field in selected_fields.values() + if field.mandatory or (field.source_field is not None and field.mnemonique is None) + ] + + for batch in range(batch_count): + offset = batch * batch_size + updated_cols = set() + + logger.info(f"[{batch+1}/{batch_count}] Loading import data in dataframe…") + with start_sentry_child(op="check.df", description="load dataframe"): + df = load_transient_data_in_dataframe( + imprt, entity, source_cols, offset=offset, limit=batch_size + ) + update_batch_progress(batch, 1) + + logger.info(f"[{batch+1}/{batch_count}] Concat dates…") + with start_sentry_child(op="check.df", description="concat dates"): + updated_cols |= concat_dates( + df, + fields["datetime_min"].dest_field, + fields["datetime_max"].dest_field, + fields["date_min"].source_field, + fields["date_max"].source_field, + fields["hour_min"].source_field, + fields["hour_max"].source_field, + ) + update_batch_progress(batch, 2) + + logger.info(f"[{batch+1}/{batch_count}] Check required values…") + with start_sentry_child(op="check.df", description="check required values"): + updated_cols |= check_required_values(imprt, entity, df, fields) + update_batch_progress(batch, 3) + + logger.info(f"[{batch+1}/{batch_count}] Check types…") + with start_sentry_child(op="check.df", description="check types"): + updated_cols |= check_types(imprt, entity, df, fields) + update_batch_progress(batch, 4) + + logger.info(f"[{batch+1}/{batch_count}] Check dataset rows") + with start_sentry_child(op="check.df", description="check datasets rows"): + updated_cols |= check_datasets( + imprt, + entity, + df, + uuid_field=fields["unique_dataset_id"], + id_field=fields["id_dataset"], + module_code="SYNTHESE", + ) + update_batch_progress(batch, 5) + logger.info(f"[{batch+1}/{batch_count}] Check geography…") + with start_sentry_child(op="check.df", description="set geography"): + updated_cols |= check_geometry( + imprt, + entity, + df, + file_srid=imprt.srid, + geom_4326_field=fields["the_geom_4326"], + geom_local_field=fields["the_geom_local"], + wkt_field=fields["WKT"], + latitude_field=fields["latitude"], + longitude_field=fields["longitude"], + codecommune_field=fields["codecommune"], + codemaille_field=fields["codemaille"], + codedepartement_field=fields["codedepartement"], + ) + update_batch_progress(batch, 6) + + logger.info(f"[{batch+1}/{batch_count}] Check counts…") + with start_sentry_child(op="check.df", description="check count"): + updated_cols |= check_counts( + imprt, + entity, + df, + fields["count_min"], + fields["count_max"], + default_count=current_app.config["IMPORT"]["DEFAULT_COUNT_VALUE"], + ) + update_batch_progress(batch, 7) + + logger.info(f"[{batch+1}/{batch_count}] Updating import data from dataframe…") + with start_sentry_child(op="check.df", description="save dataframe"): + update_transient_data_from_dataframe(imprt, entity, updated_cols, df) + update_batch_progress(batch, 8) + + # Checks in SQL + convert_geom_columns( + imprt, + entity, + geom_4326_field=fields["the_geom_4326"], + geom_local_field=fields["the_geom_local"], + ) + set_geom_columns_from_area_codes( + imprt, + entity, + geom_4326_field=fields["the_geom_4326"], + geom_local_field=fields["the_geom_local"], + codecommune_field=fields["codecommune"], + codemaille_field=fields["codemaille"], + codedepartement_field=fields["codedepartement"], + ) + set_geom_point( + imprt=imprt, + entity=entity, + geom_4326_field=fields["the_geom_4326"], + geom_point_field=fields["the_geom_point"], + ) + # All valid rows should have a geom as verified in dataframe check 'check_geometry' + + do_nomenclatures_mapping( + imprt, + entity, + selected_fields, + fill_with_defaults=current_app.config["IMPORT"][ + "FILL_MISSING_NOMENCLATURE_WITH_DEFAULT_VALUE" + ], + ) + + if current_app.config["IMPORT"]["CHECK_EXIST_PROOF"]: + check_nomenclature_exist_proof( + imprt, + entity, + fields["id_nomenclature_exist_proof"], + selected_fields.get("digital_proof"), + selected_fields.get("non_digital_proof"), + ) + if current_app.config["IMPORT"]["CHECK_PRIVATE_JDD_BLURING"]: + check_nomenclature_blurring( + imprt, + entity, + fields["id_nomenclature_blurring"], + fields["id_dataset"], + fields["unique_dataset_id"], + ) + if current_app.config["IMPORT"]["CHECK_REF_BIBLIO_LITTERATURE"]: + check_nomenclature_source_status( + imprt, entity, fields["id_nomenclature_source_status"], fields["reference_biblio"] + ) + + if "cd_nom" in selected_fields: + check_cd_nom( + imprt, + entity, + selected_fields["cd_nom"], + list_id=current_app.config["IMPORT"].get("ID_LIST_TAXA_RESTRICTION", None), + ) + if "cd_hab" in selected_fields: + check_cd_hab(imprt, entity, selected_fields["cd_hab"]) + if "entity_source_pk_value" in selected_fields: + check_duplicate_source_pk(imprt, entity, selected_fields["entity_source_pk_value"]) + + if imprt.fieldmapping.get("altitudes_generate", False): + generate_altitudes( + imprt, fields["the_geom_local"], fields["altitude_min"], fields["altitude_max"] + ) + check_altitudes( + imprt, entity, selected_fields.get("altitude_min"), selected_fields.get("altitude_max") + ) + + if "unique_id_sinp" in selected_fields: + check_duplicate_uuid(imprt, entity, selected_fields["unique_id_sinp"]) + if current_app.config["IMPORT"]["PER_DATASET_UUID_CHECK"]: + whereclause = Synthese.id_dataset == imprt.id_dataset + else: + whereclause = sa.true() + check_existing_uuid( + imprt, + entity, + selected_fields["unique_id_sinp"], + whereclause=whereclause, + ) + if imprt.fieldmapping.get( + "unique_id_sinp_generate", + current_app.config["IMPORT"]["DEFAULT_GENERATE_MISSING_UUID"], + ): + generate_missing_uuid(imprt, entity, fields["unique_id_sinp"]) + check_dates(imprt, entity, fields["datetime_min"], fields["datetime_max"]) + check_depths( + imprt, entity, selected_fields.get("depth_min"), selected_fields.get("depth_max") + ) + if "digital_proof" in selected_fields: + check_digital_proof_urls(imprt, entity, selected_fields["digital_proof"]) + + if "WKT" in selected_fields: + check_is_valid_geometry(imprt, entity, selected_fields["WKT"], fields["the_geom_4326"]) + if current_app.config["IMPORT"]["ID_AREA_RESTRICTION"]: + check_geometry_outside( + imprt, + entity, + fields["the_geom_local"], + id_area=current_app.config["IMPORT"]["ID_AREA_RESTRICTION"], + ) + + @staticmethod + def import_data_to_destination(imprt: TImports) -> None: + module = db.session.execute( + sa.select(TModules).filter_by(module_code="IMPORT") + ).scalar_one() + name_source = "Import" + source = db.session.execute( + sa.select(TSources).filter_by(module=module, name_source=name_source) + ).scalar_one_or_none() + transient_table = imprt.destination.get_transient_table() + + destination_bib_fields = db.session.scalars( + sa.select(BibFields).where( + BibFields.destination == imprt.destination, BibFields.dest_field != None + ) + ).all() + fields = {field.name_field: field for field in destination_bib_fields} + + # Construct the exact list of required fields to copy from transient table to synthese + # This list contains generated fields, and selected fields (including multi fields). + insert_fields = { + fields["datetime_min"], + fields["datetime_max"], + fields["the_geom_4326"], + fields["the_geom_local"], + fields["the_geom_point"], + fields["id_area_attachment"], # XXX sure? + } + if imprt.fieldmapping.get( + "unique_id_sinp_generate", + current_app.config["IMPORT"]["DEFAULT_GENERATE_MISSING_UUID"], + ): + insert_fields |= {fields["unique_id_sinp"]} + if imprt.fieldmapping.get("altitudes_generate", False): + insert_fields |= {fields["altitude_min"], fields["altitude_max"]} + + for field_name, source_field in imprt.fieldmapping.items(): + if field_name not in fields: # not a destination field + continue + field = fields[field_name] + if field.multi: + if not set(source_field).isdisjoint(imprt.columns): + insert_fields |= {field} + else: + if source_field in imprt.columns: + insert_fields |= {field} + + insert_fields -= {fields["unique_dataset_id"]} # Column only used for filling `id_dataset` + + select_stmt = ( + sa.select( + *[transient_table.c[field.dest_field] for field in insert_fields], + sa.literal(source.id_source), + sa.literal(source.module.id_module), + sa.literal(imprt.id_dataset), + sa.literal(imprt.id_import), + sa.literal("I"), + ) + .where(transient_table.c.id_import == imprt.id_import) + .where(transient_table.c.valid == True) + ) + names = [field.dest_field for field in insert_fields] + [ + "id_source", + "id_module", + "id_dataset", + "id_import", + "last_action", + ] + insert_stmt = sa.insert(Synthese).from_select( + names=names, + select=select_stmt, + ) + db.session.execute(insert_stmt) + + # TODO: Improve this + imprt.statistics = { + "taxa_count": ( + db.session.scalar( + sa.select(func.count(distinct(Synthese.cd_nom))).where( + Synthese.id_import == imprt.id_import + ) + ) + ), + } + + @staticmethod + def remove_data_from_destination(imprt: TImports) -> None: + with start_sentry_child(op="task", description="clean imported data"): + db.session.execute(sa.delete(Synthese).where(Synthese.id_import == imprt.id_import)) + + @staticmethod + def report_plot(imprt: TImports) -> StandaloneEmbedJson: + return taxon_distribution_plot(imprt) + + @staticmethod + def compute_bounding_box(imprt: TImports): + return compute_bounding_box(imprt, "observation", "the_geom_4326") diff --git a/backend/geonature/core/gn_synthese/imports/geo.py b/backend/geonature/core/gn_synthese/imports/geo.py new file mode 100644 index 0000000000..043971b3fd --- /dev/null +++ b/backend/geonature/core/gn_synthese/imports/geo.py @@ -0,0 +1,87 @@ +from sqlalchemy.sql.expression import select, update, join +import sqlalchemy as sa + +from geoalchemy2.functions import ST_Transform, ST_Centroid +from geonature.core.imports.checks.sql.utils import report_erroneous_rows +from geonature.utils.env import db + +from ref_geo.models import LAreas, BibAreasTypes + + +def set_geom_columns_from_area_code( + imprt, entity, geom_4326_col, geom_local_col, code_column, area_type_filter +): + transient_table = imprt.destination.get_transient_table() + # Find area in CTE, then update corresponding column in statement + cte = ( + select( + transient_table.c.id_import, + transient_table.c.line_no, + LAreas.id_area, + LAreas.geom, + LAreas.geom_4326, + ) + .select_from( + join(transient_table, LAreas, code_column == LAreas.area_code).join(BibAreasTypes) + ) + .where(transient_table.c.id_import == imprt.id_import) + .where(transient_table.c[entity.validity_column] == True) + .where(transient_table.c[geom_4326_col] == None) # geom_4326 & local should be aligned + .where(area_type_filter) + .cte("cte") + ) + stmt = ( + update(transient_table) + .values( + { + transient_table.c.id_area_attachment: cte.c.id_area, + transient_table.c[geom_local_col]: cte.c.geom, + transient_table.c[geom_4326_col]: cte.c.geom_4326, + } + ) + .where(transient_table.c.id_import == cte.c.id_import) + .where(transient_table.c.line_no == cte.c.line_no) + ) + db.session.execute(stmt) + + +def set_geom_columns_from_area_codes( + imprt, + entity, + geom_4326_field, + geom_local_field, + codecommune_field=None, + codemaille_field=None, + codedepartement_field=None, +): + transient_table = imprt.destination.get_transient_table() + + for field, area_type_filter in [ + (codecommune_field, BibAreasTypes.type_code == "COM"), + (codedepartement_field, BibAreasTypes.type_code == "DEP"), + (codemaille_field, BibAreasTypes.type_code.in_(["M1", "M5", "M10"])), + ]: + if field is None: + continue + source_column = transient_table.c[field.source_field] + # Set geom from area of the given type and with matching area_code: + set_geom_columns_from_area_code( + imprt, + entity, + geom_4326_field.dest_field, + geom_local_field.dest_field, + source_column, + area_type_filter, + ) + # Mark rows with code specified but geom still empty as invalid: + report_erroneous_rows( + imprt, + entity, + error_type="INVALID_ATTACHMENT_CODE", + error_column=field.name_field, + whereclause=sa.and_( + transient_table.c[geom_4326_field.dest_field] == None, + transient_table.c[entity.validity_column] == True, + source_column != None, + ), + ) diff --git a/backend/geonature/core/gn_synthese/imports/plot.py b/backend/geonature/core/gn_synthese/imports/plot.py new file mode 100644 index 0000000000..75e6a116bb --- /dev/null +++ b/backend/geonature/core/gn_synthese/imports/plot.py @@ -0,0 +1,166 @@ +import numpy as np +import sqlalchemy as sa +from apptax.taxonomie.models import Taxref +from bokeh.embed import json_item +from bokeh.embed.standalone import StandaloneEmbedJson +from bokeh.layouts import column +from bokeh.models import ( + AnnularWedge, + ColumnDataSource, + CustomJS, + Legend, + LegendItem, + Range1d, + Select, +) +from bokeh.palettes import Plasma256, Turbo256, linear_palette +from bokeh.plotting import figure +from geonature.core.gn_synthese.models import Synthese +from geonature.utils.env import db + + +def taxon_distribution_plot(imprt) -> StandaloneEmbedJson: + """ + Generate a plot of the taxonomic distribution (for each rank) based on the import. + The following ranks are used: + - group1_inpn + - group2_inpn + - group3_inpn + - sous_famille + - tribu + - classe + - ordre + - famille + - phylum + - regne + + Parameters + ---------- + imprt : TImports + The import object to generate the plot from. + + Returns + ------- + dict + Returns a dict containing data required to generate the plot + """ + taxon_ranks = "regne phylum classe ordre famille sous_famille tribu group1_inpn group2_inpn group3_inpn".split() + figures = [] + + # Generate the plot for each rank + for rank in taxon_ranks: + # Generate the query to retrieve the count for each value taken by the rank + c_rank_taxref = getattr(Taxref, rank) + query = ( + sa.select( + sa.func.count(sa.distinct(Synthese.cd_nom)).label("count"), + c_rank_taxref.label("rank_value"), + ) + .select_from(Synthese) + .outerjoin(Taxref, Taxref.cd_nom == Synthese.cd_nom) + .where(Synthese.id_import == imprt.id_import) + .group_by(c_rank_taxref) + ) + data = np.asarray( + [r if r[1] != "" else (r[0], "Non-assigné") for r in db.session.execute(query).all()] + ) + + # if data is empty + if not data.size: + continue + + # Extract the rank values and counts + rank_values, counts = data[:, 1], data[:, 0].astype(int) + + # Get angles (in radians) where start each section of the pie chart + angles = np.cumsum( + [2 * np.pi * (count / sum(counts)) for i, count in enumerate(counts)] + ).tolist() + + # Generate the color palette + palette = ( + linear_palette(Turbo256, len(rank_values)) + if len(rank_values) > 5 + else linear_palette(Plasma256, len(rank_values)) + ) + colors = {value: palette[ix] for ix, value in enumerate(rank_values)} + + # Store the data in a Bokeh data structure + browsers_source = ColumnDataSource( + dict( + start=[0] + angles[:-1], + end=angles, + colors=[colors[rank_value] for rank_value in rank_values], + countvalue=counts, + rankvalue=rank_values, + ) + ) + # Create the Figure object + fig = figure( + x_range=Range1d(start=-3, end=3), + y_range=Range1d(start=-3, end=3), + title=f"Distribution des taxons selon {rank}", + tooltips=[("Number", "@countvalue"), (rank, "@rankvalue")], + toolbar_location=None, + ) + # Add the Pie chart + glyph = AnnularWedge( + x=0, + y=0, + inner_radius=0.9, + outer_radius=1.8, + start_angle="start", + end_angle="end", + line_color="white", + line_width=3, + fill_color="colors", + ) + r = fig.add_glyph(browsers_source, glyph) + + # Add the legend + legend = Legend(location="top_center") + for i, name in enumerate(colors): + legend.items.append(LegendItem(label=name, renderers=[r], index=i)) + fig.add_layout(legend, "below") + fig.legend.ncols = 3 if len(colors) < 10 else 5 + + # ERASE the grid and axis + fig.grid.visible = False + fig.axis.visible = False + fig.title.text_font_size = "16pt" + + # Hide the unselected rank plot + if rank != "regne": + fig.visible = False + + # Add the plot to the list of figures + figures.append(fig) + + if not figures: + return {} + + # Generate the layout with the plots and the rank selector + plot_area = column(figures) + + select_plot = Select( + title="Critère", + value=0, # Default is "regne" + options=[(ix, rank) for ix, rank in enumerate(taxon_ranks)], + width=fig.width, + ) + + # Update the visibility of the plots when the taxonomic rank selector changes + select_plot.js_on_change( + "value", + CustomJS( + args=dict(s=select_plot, col=plot_area), + code=""" + for (const plot of col.children) { + plot.visible = false + } + col.children[s.value].visible = true + """, + ), + ) + column_fig = column(plot_area, select_plot, sizing_mode="scale_width") + return json_item(column_fig) diff --git a/backend/geonature/core/gn_synthese/models.py b/backend/geonature/core/gn_synthese/models.py index 76619542e3..9a572c9a75 100644 --- a/backend/geonature/core/gn_synthese/models.py +++ b/backend/geonature/core/gn_synthese/models.py @@ -40,6 +40,7 @@ from utils_flask_sqla_geo.mixins import GeoFeatureCollectionMixin from pypn_habref_api.models import Habref from apptax.taxonomie.models import Taxref +from geonature.core.imports.models import TImports as Import from ref_geo.models import LAreas from geonature.core.gn_meta.models import TDatasets, TAcquisitionFramework @@ -264,6 +265,7 @@ class Synthese(DB.Model): id_source = DB.Column(DB.Integer, ForeignKey(TSources.id_source), nullable=False) source = relationship(TSources) id_module = DB.Column(DB.Integer, ForeignKey(TModules.id_module)) + id_import = db.Column(db.Integer, ForeignKey(Import.id_import), nullable=True) module = DB.relationship(TModules) entity_source_pk_value = DB.Column(DB.Unicode) id_dataset = DB.Column(DB.Integer, ForeignKey(TDatasets.id_dataset)) @@ -583,6 +585,7 @@ class VSyntheseForWebApp(DB.Model): unique_id_sinp = DB.Column(UUID(as_uuid=True)) unique_id_sinp_grp = DB.Column(UUID(as_uuid=True)) id_source = DB.Column(DB.Integer, nullable=False) + id_import = DB.Column(DB.Integer, nullable=True) id_module = DB.Column(DB.Integer) entity_source_pk_value = DB.Column(DB.Integer) id_dataset = DB.Column(DB.Integer) diff --git a/backend/geonature/core/gn_synthese/module.py b/backend/geonature/core/gn_synthese/module.py new file mode 100644 index 0000000000..9d9576f5e2 --- /dev/null +++ b/backend/geonature/core/gn_synthese/module.py @@ -0,0 +1,13 @@ +from geonature.core.gn_commons.models import TModules + +from .imports.actions import SyntheseImportActions + + +class SyntheseModule(TModules): + __mapper_args__ = {"polymorphic_identity": "synthese"} + __import_actions__ = SyntheseImportActions + + def generate_input_url_for_dataset(self, dataset): + return f"/import/synthese/process/upload?datasetId={dataset.id_dataset}" + + generate_input_url_for_dataset.label = "Importer des occurrences de taxons" diff --git a/backend/geonature/core/gn_synthese/routes.py b/backend/geonature/core/gn_synthese/routes.py index abe0c51276..f9880af195 100644 --- a/backend/geonature/core/gn_synthese/routes.py +++ b/backend/geonature/core/gn_synthese/routes.py @@ -15,7 +15,7 @@ jsonify, g, ) -from geonature.core.gn_synthese.schemas import SyntheseSchema +from geonature.core.gn_synthese.schemas import ReportSchema, SyntheseSchema from geonature.core.gn_synthese.synthese_config import MANDATORY_COLUMNS from pypnusershub.db.models import User from pypnnomenclature.models import BibNomenclaturesTypes, TNomenclatures @@ -40,6 +40,7 @@ from geonature.core.gn_meta.models import TDatasets from geonature.core.notifications.utils import dispatch_notifications +import geonature.core.gn_synthese.module from geonature.core.gn_synthese.models import ( BibReportsTypes, CorAreaSynthese, @@ -957,6 +958,82 @@ def general_stats(permissions): return data +@routes.route("/taxon_stats/", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE") +@json_resp +def taxon_stats(scope, cd_ref): + """Return stats for a specific taxon""" + + area_type = request.args.get("area_type") + + if not area_type: + raise BadRequest("Missing area_type parameter") + + # Ensure area_type is valid + valid_area_types = ( + db.session.query(BibAreasTypes.type_code) + .distinct() + .filter(BibAreasTypes.type_code == area_type) + .scalar() + ) + if not valid_area_types: + raise BadRequest("Invalid area_type") + + # Subquery to fetch areas based on area_type + areas_subquery = ( + select([LAreas.id_area]) + .where(LAreas.id_type == BibAreasTypes.id_type) + .where(BibAreasTypes.type_code == area_type) + .alias("areas") + ) + + taxref_cd_nom_list = db.session.scalars(select(Taxref.cd_nom).where(Taxref.cd_ref == cd_ref)) + + # Main query to fetch stats + query = ( + select( + [ + func.count(distinct(Synthese.id_synthese)).label("observation_count"), + func.count(distinct(Synthese.observers)).label("observer_count"), + func.count(distinct(areas_subquery.c.id_area)).label("area_count"), + func.min(Synthese.altitude_min).label("altitude_min"), + func.max(Synthese.altitude_max).label("altitude_max"), + func.min(Synthese.date_min).label("date_min"), + func.max(Synthese.date_max).label("date_max"), + ] + ) + .select_from( + sa.join( + Synthese, + CorAreaSynthese, + Synthese.id_synthese == CorAreaSynthese.id_synthese, + ) + .join(areas_subquery, CorAreaSynthese.id_area == areas_subquery.c.id_area) + .join(LAreas, CorAreaSynthese.id_area == LAreas.id_area) + .join(BibAreasTypes, LAreas.id_type == BibAreasTypes.id_type) + ) + .where(Synthese.cd_nom.in_(taxref_cd_nom_list)) + ) + + synthese_query_obj = SyntheseQuery(Synthese, query, {}) + synthese_query_obj.filter_query_with_cruved(g.current_user, scope) + result = DB.session.execute(synthese_query_obj.query) + synthese_stats = result.fetchone() + + data = { + "cd_ref": cd_ref, + "observation_count": synthese_stats["observation_count"], + "observer_count": synthese_stats["observer_count"], + "area_count": synthese_stats["area_count"], + "altitude_min": synthese_stats["altitude_min"], + "altitude_max": synthese_stats["altitude_max"], + "date_min": synthese_stats["date_min"], + "date_max": synthese_stats["date_max"], + } + + return data + + @routes.route("/taxons_tree", methods=["GET"]) @login_required @json_resp @@ -1414,53 +1491,146 @@ def update_content_report(id_report): @routes.route("/reports", methods=["GET"]) @permissions_required("R", module_code="SYNTHESE") -def list_reports(permissions): +def list_all_reports(permissions): + # Parameters type_name = request.args.get("type") - id_synthese = request.args.get("idSynthese") + orderby = request.args.get("orderby", "creation_date") sort = request.args.get("sort") - # VERIFY ID SYNTHESE + page = int(request.args.get("page", 1)) + per_page = int(request.args.get("per_page", 10)) + my_reports = request.args.get("my_reports", "false").lower() == "true" + + # Start query + query = ( + sa.select(TReport, User.nom_complet) + .join(User, TReport.id_role == User.id_role) + .options( + joinedload(TReport.report_type).load_only( + BibReportsTypes.type, BibReportsTypes.id_type + ), + joinedload(TReport.synthese).load_only( + Synthese.cd_nom, + Synthese.nom_cite, + Synthese.observers, + Synthese.date_min, + Synthese.date_max, + ), + joinedload(TReport.user).load_only(User.nom_role, User.prenom_role), + ) + ) + # Verify and filter by type + if type_name: + type_exists = db.session.scalar( + sa.exists(BibReportsTypes).where(BibReportsTypes.type == type_name).select() + ) + if not type_exists: + raise BadRequest("This report type does not exist") + query = query.where(TReport.report_type.has(BibReportsTypes.type == type_name)) + + # Filter by id_role for 'pin' type only or if my_reports is true + if type_name == "pin" or my_reports: + query = query.where(TReport.id_role == g.current_user.id_role) + + # On vérifie les permissions en lecture sur la synthese + synthese_query = select(Synthese.id_synthese).select_from(Synthese) + synthese_query_obj = SyntheseQuery(Synthese, synthese_query, {}) + synthese_query_obj.filter_query_with_cruved(g.current_user, permissions) + ids_synthese = db.session.scalars(synthese_query_obj.query).all() + query = query.where(TReport.id_synthese.in_(ids_synthese)) + + SORT_COLUMNS = { + "user.nom_complet": User.nom_complet, + "content": TReport.content, + "creation_date": TReport.creation_date, + } + + # Determine the sorting + if orderby in SORT_COLUMNS: + sort_column = SORT_COLUMNS[orderby] + if sort == "desc": + query = query.order_by(desc(sort_column)) + else: + query = query.order_by(asc(sort_column)) + else: + raise BadRequest("Bad orderby") + + # Pagination + total = db.session.scalar( + select(func.count("*")) + .select_from(TReport) + .where(TReport.report_type.has(BibReportsTypes.type == type_name)) + ) + paginated_results = db.paginate(query, page=page, per_page=per_page) + + result = [] + + for report in paginated_results.items: + report_dict = { + "id_report": report.id_report, + "id_synthese": report.id_synthese, + "id_role": report.id_role, + "report_type": { + "type": report.report_type.type, + "id_type": report.report_type.id_type, + }, + "content": report.content, + "deleted": report.deleted, + "creation_date": report.creation_date, + "user": {"nom_complet": report.user.nom_complet}, + "synthese": { + "cd_nom": report.synthese.cd_nom, + "nom_cite": report.synthese.nom_cite, + "observers": report.synthese.observers, + "date_min": report.synthese.date_min, + "date_max": report.synthese.date_max, + }, + } + result.append(report_dict) + + response = { + "total_filtered": paginated_results.total, + "total": total, + "pages": paginated_results.pages, + "current_page": page, + "per_page": per_page, + "items": result, + } + return jsonify(response) + + +@routes.route("/reports/", methods=["GET"]) +@permissions_required("R", module_code="SYNTHESE") +def list_reports(permissions, id_synthese): + type_name = request.args.get("type") + synthese = db.get_or_404(Synthese, id_synthese) if not synthese.has_instance_permission(permissions): raise Forbidden - # START REQUEST - req = TReport.query.where(TReport.id_synthese == id_synthese) - # SORT - if sort == "asc": - req = req.order_by(asc(TReport.creation_date)) - if sort == "desc": - req = req.order_by(desc(TReport.creation_date)) - # VERIFY AND SET TYPE - type_exists = BibReportsTypes.query.filter_by(type=type_name).one_or_none() - # type param is not required to get all - if type_name and not type_exists: - raise BadRequest("This report type does not exist") + + query = sa.select(TReport).where(TReport.id_synthese == id_synthese) + + # Verify and filter by type if type_name: - req = req.where(TReport.report_type.has(BibReportsTypes.type == type_name)) - # filter by id_role for pin type only - if type_name and type_name == "pin": - req = req.where(TReport.id_role == g.current_user.id_role) - req = req.options( + type_exists = db.session.scalar( + sa.exists(BibReportsTypes).where(BibReportsTypes.type == type_name).select() + ) + if not type_exists: + raise BadRequest("This report type does not exist") + query = query.where(TReport.report_type.has(BibReportsTypes.type == type_name)) + + # Filter by id_role for 'pin' type only + if type_name == "pin": + query = query.where(TReport.id_role == g.current_user.id_role) + + # Join the User table + query = query.options( joinedload(TReport.user).load_only(User.nom_role, User.prenom_role), joinedload(TReport.report_type), ) - result = [ - report.as_dict( - fields=[ - "id_report", - "id_synthese", - "id_role", - "report_type.type", - "report_type.id_type", - "content", - "deleted", - "creation_date", - "user.nom_role", - "user.prenom_role", - ] - ) - for report in req.all() - ] - return jsonify(result) + + return ReportSchema(many=True, only=["+user.nom_role", "+user.prenom_role"]).dump( + db.session.scalars(query).all() + ) @routes.route("/reports/", methods=["DELETE"]) diff --git a/backend/geonature/core/gn_synthese/synthese_config.py b/backend/geonature/core/gn_synthese/synthese_config.py index a3129d8d1b..17d776062f 100644 --- a/backend/geonature/core/gn_synthese/synthese_config.py +++ b/backend/geonature/core/gn_synthese/synthese_config.py @@ -91,7 +91,7 @@ ] # Mandatory columns for the frontend in Synthese API -MANDATORY_COLUMNS = ["id_synthese", "entity_source_pk_value", "url_source", "cd_nom"] +MANDATORY_COLUMNS = ["id_synthese", "entity_source_pk_value", "url_source", "cd_nom", "id_dataset"] # CONFIG MAP-LIST DEFAULT_LIST_COLUMN = [ diff --git a/backend/geonature/core/gn_synthese/utils/blurring.py b/backend/geonature/core/gn_synthese/utils/blurring.py index 51aa3f9a36..d05e7b005e 100644 --- a/backend/geonature/core/gn_synthese/utils/blurring.py +++ b/backend/geonature/core/gn_synthese/utils/blurring.py @@ -10,6 +10,8 @@ from geonature.core.sensitivity.models import cor_sensitivity_area_type from geonature.core.gn_synthese.utils.query_select_sqla import SyntheseQuery +from geonature.utils.env import db + def split_blurring_precise_permissions(permissions): """ @@ -25,18 +27,16 @@ def build_sensitive_unsensitive_filters(): """ Return where clauses for sensitive and non-sensitive observations. """ - non_sensitive_nomenc = ( - TNomenclatures.query.with_entities(TNomenclatures.id_nomenclature) - .filter( - TNomenclatures.nomenclature_type.has(BibNomenclaturesTypes.mnemonique == "SENSIBILITE") + non_sensitive_nomenc = db.session.scalar( + sa.select(TNomenclatures.id_nomenclature).where( + TNomenclatures.nomenclature_type.has(BibNomenclaturesTypes.mnemonique == "SENSIBILITE"), + TNomenclatures.cd_nomenclature == "0", ) - .filter(TNomenclatures.cd_nomenclature == "0") - .one() ) return ( - Synthese.id_nomenclature_sensitivity != non_sensitive_nomenc.id_nomenclature, - Synthese.id_nomenclature_sensitivity == non_sensitive_nomenc.id_nomenclature, + Synthese.id_nomenclature_sensitivity != non_sensitive_nomenc, + Synthese.id_nomenclature_sensitivity == non_sensitive_nomenc, ) @@ -76,6 +76,7 @@ def build_blurred_precise_geom_queries( CorAreaSyntheseAlias = aliased(CorAreaSynthese) LAreasAlias = aliased(LAreas) BibAreasTypesAlias = aliased(BibAreasTypes) + # TODO@LAreas.geom_4326 geom = LAreasAlias.geom.st_transform(4326).label("geom") # In SyntheseQuery below : # - query_joins parameter is needed to bypass diff --git a/backend/geonature/core/auth/__init__.py b/backend/geonature/core/imports/__init__.py similarity index 100% rename from backend/geonature/core/auth/__init__.py rename to backend/geonature/core/imports/__init__.py diff --git a/backend/geonature/core/imports/actions.py b/backend/geonature/core/imports/actions.py new file mode 100644 index 0000000000..e3384b2a20 --- /dev/null +++ b/backend/geonature/core/imports/actions.py @@ -0,0 +1,46 @@ +from geonature.core.imports.models import TImports + +from bokeh.embed.standalone import StandaloneEmbedJson + +import typing + + +class ImportStatisticsLabels(typing.TypedDict): + key: str + value: str + + +class ImportInputUrl(typing.TypedDict): + url: str + label: str + + +class ImportActions: + @staticmethod + def statistics_labels() -> typing.List[ImportStatisticsLabels]: + raise NotImplementedError + + # The output of this method is NEVER used + @staticmethod + def preprocess_transient_data(imprt: TImports, df) -> set: + raise NotImplementedError + + @staticmethod + def check_transient_data(task, logger, imprt: TImports) -> None: + raise NotImplementedError + + @staticmethod + def import_data_to_destination(imprt: TImports) -> None: + raise NotImplementedError + + @staticmethod + def remove_data_from_destination(imprt: TImports) -> None: + raise NotImplementedError + + @staticmethod + def report_plot(imprt: TImports) -> StandaloneEmbedJson: + raise NotImplementedError + + @staticmethod + def compute_bounding_box(imprt: TImports) -> None: + raise NotImplementedError diff --git a/backend/geonature/core/imports/admin.py b/backend/geonature/core/imports/admin.py new file mode 100644 index 0000000000..5f48485c8a --- /dev/null +++ b/backend/geonature/core/imports/admin.py @@ -0,0 +1,112 @@ +import json +from itertools import groupby +from pprint import pformat + +from markupsafe import Markup +from flask_admin.contrib.sqla import ModelView +from flask_admin.form import BaseForm +from wtforms.validators import StopValidation +from jsonschema.exceptions import ValidationError as JSONValidationError +from wtforms.fields import StringField + +from geonature.utils.env import db +from geonature.core.admin.admin import admin as geonature_admin, CruvedProtectedMixin + +from pypnnomenclature.models import TNomenclatures + +from geonature.core.imports.models import FieldMapping, ContentMapping + +from flask_admin.contrib.sqla.form import AdminModelConverter +from flask_admin.model.form import converts + + +class MappingView(CruvedProtectedMixin, ModelView): + module_code = "IMPORT" + object_code = "MAPPING" + + can_view_details = True + column_list = ( + "label", + "active", + "public", + ) + column_searchable_list = ("label",) + column_filters = ( + "active", + "public", + ) + form_columns = ( + "label", + "active", + "public", + "owners", + "values", + ) + column_details_list = ( + "label", + "active", + "public", + "owners", + "values", + ) + column_labels = { + "active": "Actif", + "owners": "Propriétaires", + "values": "Correspondances", + } + column_export_list = ( + "label", + "values", + ) + + +def FieldMappingValuesValidator(form, field): + try: + FieldMapping.validate_values(field.data) + except ValueError as e: + raise StopValidation(*e.args) + + +def ContentMappingValuesValidator(form, field): + try: + ContentMapping.validate_values(field.data) + except ValueError as e: + raise StopValidation(*e.args) + + +class FieldMappingView(MappingView): + form_args = { + "values": { + "validators": [FieldMappingValuesValidator], + }, + } + colmun_labels = { + "values": "Association", + } + column_formatters_detail = { + "values": lambda v, c, m, p: Markup("
%s
" % pformat(m.values)), + } + + +class ContentMappingView(MappingView): + form_args = { + "values": { + "validators": [ContentMappingValuesValidator], + }, + } + colmun_labels = { + "values": "Association", + } + column_formatters_detail = { + "values": lambda v, c, m, p: Markup("
%s
" % pformat(m.values)), + } + + +geonature_admin.add_view( + FieldMappingView(FieldMapping, db.session, name="Champs", category="Modèles d’import") +) +geonature_admin.add_view( + ContentMappingView( + ContentMapping, db.session, name="Nomenclatures", category="Modèles d’import" + ) +) diff --git a/backend/geonature/core/imports/blueprint.py b/backend/geonature/core/imports/blueprint.py new file mode 100644 index 0000000000..d2507096af --- /dev/null +++ b/backend/geonature/core/imports/blueprint.py @@ -0,0 +1,30 @@ +from flask import Blueprint, current_app, g + +from geonature.core.gn_commons.models import TModules + +from geonature.core.imports.models import Destination +import geonature.core.imports.admin # noqa: F401 + +blueprint = Blueprint("import", __name__, template_folder="templates") + + +@blueprint.url_value_preprocessor +def set_current_destination(endpoint, values): + if ( + current_app.url_map.is_endpoint_expecting(endpoint, "destination") + and "destination" in values + ): + g.destination = values["destination"] = Destination.query.filter( + Destination.code == values["destination"] + ).first_or_404() + + +from .routes import ( + imports, + mappings, + fields, +) +from .commands import fix_mappings + + +blueprint.cli.add_command(fix_mappings) diff --git a/backend/geonature/core/imports/checks/__init__.py b/backend/geonature/core/imports/checks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/geonature/core/imports/checks/dataframe/__init__.py b/backend/geonature/core/imports/checks/dataframe/__init__.py new file mode 100644 index 0000000000..a1a1a311bc --- /dev/null +++ b/backend/geonature/core/imports/checks/dataframe/__init__.py @@ -0,0 +1,4 @@ +from .core import * +from .geometry import check_geometry +from .cast import check_types +from .dates import concat_dates diff --git a/backend/geonature/core/imports/checks/dataframe/cast.py b/backend/geonature/core/imports/checks/dataframe/cast.py new file mode 100644 index 0000000000..692ad31689 --- /dev/null +++ b/backend/geonature/core/imports/checks/dataframe/cast.py @@ -0,0 +1,465 @@ +from typing import Any, Dict, Iterator, Optional, Set +import re +from uuid import UUID +from itertools import product +from datetime import datetime + +from geonature.core.imports.checks.errors import ImportCodeError +import pandas as pd +from sqlalchemy.sql import sqltypes +from sqlalchemy.dialects.postgresql import UUID as UUIDType + +from geonature.core.imports.models import BibFields, Entity +from .utils import dataframe_check + + +def convert_to_datetime(value_raw): + """ + Try to convert a date string to a datetime object. + If the input string does not match any of compatible formats, it will return + None. + + Parameters + ---------- + value_raw : str + The input string to convert + + Returns + ------- + converted_date : datetime or None + The converted datetime object or None if the conversion failed + """ + converted_date: datetime = None + + value = value_raw.strip() + value = re.sub("[ ]+", " ", value) + value = re.sub("[/.:]", "-", value) + date_formats = [ + "%Y-%m-%d", + "%d-%m-%Y", + ] + time_formats = [ + None, + "%H", + "%H-%M", + "%H-%M-%S", + "%H-%M-%S-%f", + "%Hh", + "%Hh%M", + "%Hh%Mm", + "%Hh%Mm%Ss", + ] + for date_format, time_format in product(date_formats, time_formats): + fmt = (date_format + " " + time_format) if time_format else date_format + try: + converted_date = datetime.strptime(value, fmt) + break # If successful conversion, will stop the loop + except ValueError: + continue + + if not converted_date: + try: + converted_date = datetime.fromisoformat(value_raw) + except: + pass + + return converted_date + + +def convert_to_uuid(value, version=4): + try: + return UUID(str(value), version=version).hex + except ValueError: + return None + + +def convert_to_integer(value): + try: + return int(value) + except ValueError: + return None + + +def check_datetime_field( + df: pd.DataFrame, source_field: str, target_field: str, required: bool +) -> Set[str]: + """ + Check if a column is a datetime and convert it to datetime type. + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check. + source_field : str + The name of the column to check. + target_field : str + The name of the column where to store the result. + required : bool + Whether the column is mandatory or not. + + Yields + ------ + dict + A dictionary containing an error code, the column name, and the invalid rows. + + Returns + ------- + set + Set containing the name of the target field. + + Notes + ----- + The error codes are: + - INVALID_DATE: the value is not of datetime type. + """ + datetime_col = df[source_field].apply(lambda x: convert_to_datetime(x) if pd.notnull(x) else x) + if required: + invalid_rows = df[datetime_col.isna()] + else: + # invalid rows are NaN rows which were not already set to NaN + invalid_rows = df[datetime_col.isna() & df[source_field].notna()] + df[target_field] = datetime_col + values_error = invalid_rows[source_field] + if len(invalid_rows) > 0: + yield dict( + error_code=ImportCodeError.INVALID_DATE, + invalid_rows=invalid_rows, + comment="Les dates suivantes ne sont pas au bon format: {}".format( + ", ".join(map(lambda x: str(x), values_error)) + ), + ) + return {target_field} + + +def check_uuid_field( + df: pd.DataFrame, source_field: str, target_field: str, required: bool +) -> Set[str]: + """ + Check if a column is a UUID and convert it to UUID type. + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check. + source_field : str + The name of the column to check. + target_field : str + The name of the column where to store the result. + required : bool + Whether the column is mandatory or not. + + Yields + ------ + dict + A dictionary containing an error code, the column name, and the invalid rows. + + Returns + ------- + set + Set containing the name of the target field. + + Notes + ----- + The error codes are: + - INVALID_UUID: the value is not a valid UUID. + """ + uuid_col = df[source_field].apply(lambda x: convert_to_uuid(x) if pd.notnull(x) else x) + if required: + invalid_rows = df[uuid_col.isna()] + else: + # invalid rows are NaN rows which were not already set to NaN + invalid_rows = df[uuid_col.isna() & df[source_field].notna()] + df[target_field] = uuid_col + values_error = invalid_rows[source_field] + if len(invalid_rows) > 0: + yield dict( + error_code=ImportCodeError.INVALID_UUID, + invalid_rows=invalid_rows, + comment="Les UUID suivantes ne sont pas au bon format: {}".format( + ", ".join(map(lambda x: str(x), values_error)) + ), + ) + return {target_field} + + +def check_integer_field( + df: pd.DataFrame, source_field: str, target_field: str, required: bool +) -> Set[str]: + """ + Check if a column is an integer and convert it to integer type. + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check. + source_field : str + The name of the column to check. + target_field : str + The name of the column where to store the result. + required : bool + Whether the column is mandatory or not. + + Yields + ------ + dict + A dictionary containing an error code, the column name, and the invalid rows. + + Returns + ------- + set + Set containing the name of the target field. + + Notes + ----- + The error codes are: + - INVALID_INTEGER: the value is not of integer type. + """ + integer_col = df[source_field].apply(lambda x: convert_to_integer(x) if pd.notnull(x) else x) + if required: + invalid_rows = df[integer_col.isna()] + else: + # invalid rows are NaN rows which were not already set to NaN + invalid_rows = df[integer_col.isna() & df[source_field].notna()] + df[target_field] = integer_col + values_error = invalid_rows[source_field] + if len(invalid_rows) > 0: + yield dict( + error_code=ImportCodeError.INVALID_INTEGER, + invalid_rows=invalid_rows, + comment="Les valeurs suivantes ne sont pas des entiers : {}".format( + ", ".join(map(lambda x: str(x), values_error)) + ), + ) + return {target_field} + + +def check_numeric_field( + df: pd.DataFrame, source_field: str, target_field: str, required: bool +) -> Set[str]: + """ + Check if column string values are numerics and convert it to numeric type. + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check. + source_field : str + The name of the column to check. + target_field : str + The name of the column where to store the result. + required : bool + Whether the column is mandatory or not. + + Yields + ------ + dict + A dictionary containing an error code, the column name, and the invalid rows. + + Returns + ------- + set + Set containing the name of the target field. + + Notes + ----- + The error codes are: + - INVALID_NUMERIC: the value is not of numeric type. + """ + + def to_numeric(x): + try: + return float(x) + except: + return None + + numeric_col = df[source_field].apply(lambda x: to_numeric(x) if pd.notnull(x) else x) + if required: + invalid_rows = df[numeric_col.isna()] + else: + # invalid rows are NaN rows which were not already set to NaN + invalid_rows = df[numeric_col.isna() & df[source_field].notna()] + df[target_field] = numeric_col + values_error = invalid_rows[source_field] + if len(invalid_rows) > 0: + yield dict( + error_code=ImportCodeError.INVALID_NUMERIC, + invalid_rows=invalid_rows, + comment="Les valeurs suivantes ne sont pas des nombres : {}".format( + ", ".join(map(lambda x: str(x), values_error)) + ), + ) + return {target_field} + + +def check_unicode_field( + df: pd.DataFrame, field: str, field_length: Optional[int] +) -> Iterator[Dict[str, Any]]: + """ + Check if column values have the right length. + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check. + field : str + The name of the column to check. + field_length : Optional[int] + The maximum length of the column. + + Yields + ------ + dict + A dictionary containing an error code, the column name, and the invalid rows. + Notes + ----- + The error codes are: + - INVALID_CHAR_LENGTH: the string is too long. + """ + if field_length is None: + return + length = df[field].apply(lambda x: len(x) if pd.notnull(x) else x) + invalid_rows = df[length > field_length] + if len(invalid_rows) > 0: + yield dict( + error_code=ImportCodeError.INVALID_CHAR_LENGTH, + invalid_rows=invalid_rows, + ) + + +def check_boolean_field(df, source_col, dest_col, required): + """ + Check a boolean field in a dataframe. + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check. + source_col : str + The name of the column to check. + dest_col : str + The name of the column where to store the result. + required : bool + Whether the column is mandatory or not. + + Yields + ------ + dict + A dictionary containing an error code and the rows with errors. + + Notes + ----- + The error codes are: + - MISSING_VALUE: the value is mandatory but it's missing (null). + - INVALID_BOOL: the value is not a boolean. + + """ + df[dest_col] = df[source_col].apply(int).apply(bool) + + if required: # FIXME: to remove as done in check_required_value + invalid_mask = df[dest_col].apply(lambda x: type(x) != bool and pd.isnull(x)) + yield dict(error_code=ImportCodeError.MISSING_VALUE, invalid_rows=df[invalid_mask]) + else: + invalid_mask = df[dest_col].apply(lambda x: type(x) != bool and (not pd.isnull(x))) + if invalid_mask.sum() > 0: + yield dict(error_code=ImportCodeError.INVALID_BOOL, invalid_rows=df[invalid_mask]) + return {dest_col} + + +def check_anytype_field( + df: pd.DataFrame, + field_type: sqltypes.TypeEngine, + source_col: str, + dest_col: str, + required: bool, +) -> Set[str]: + """ + Check a field in a dataframe according to its type. + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check. + field_type : sqlalchemy.TypeEngine + The type of the column to check. + source_col : str + The name of the column to check. + dest_col : str + The name of the column where to store the result. + required : bool + Whether the column is mandatory or not. + + Yields + ------ + dict + A dictionary containing an error code and the rows with errors. + + Returns + ------- + set + Set containing the name of columns updated in the dataframe. + """ + updated_cols = set() + if isinstance(field_type, sqltypes.DateTime): + updated_cols |= yield from check_datetime_field(df, source_col, dest_col, required) + elif isinstance(field_type, sqltypes.Integer): + updated_cols |= yield from check_integer_field(df, source_col, dest_col, required) + elif isinstance(field_type, UUIDType): + updated_cols |= yield from check_uuid_field(df, source_col, dest_col, required) + elif isinstance(field_type, sqltypes.String): + yield from check_unicode_field(df, dest_col, field_length=field_type.length) + elif isinstance(field_type, sqltypes.Boolean): + updated_cols |= yield from check_boolean_field(df, source_col, dest_col, required) + elif isinstance(field_type, sqltypes.Numeric): + updated_cols |= yield from check_numeric_field(df, source_col, dest_col, required) + else: + raise Exception( + "Unknown type {} for field {}".format(type(field_type), dest_col) + ) # pragma: no cover + return updated_cols + + +@dataframe_check +def check_types(entity: Entity, df: pd.DataFrame, fields: Dict[str, BibFields]) -> Set[str]: + """ + Check the types of columns in a dataframe based on the provided fields. + + Parameters + ---------- + entity : Entity + The entity to check. + df : pd.DataFrame + The dataframe to check. + fields : Dict[str, BibFields] + A dictionary mapping column names to their corresponding BibFields. + + Returns + ------- + Set[str] + Set containing the names of updated columns. + """ + updated_cols = set() + destination_table = entity.get_destination_table() + transient_table = entity.destination.get_transient_table() + for name, field in fields.items(): + if not field.dest_field: + continue + if field.source_column not in df: + continue + if field.mnemonique: # set from content mapping + continue + assert entity in [ef.entity for ef in field.entities] # FIXME + if field.dest_field in destination_table.c: + field_type = destination_table.c[field.dest_field].type + else: # we may require to convert some columns unused in final destination + field_type = transient_table.c[field.dest_field].type + updated_cols |= yield from map( + lambda error: {"column": name, **error}, + check_anytype_field( + df, + field_type=field_type, + source_col=field.source_column, + dest_col=field.dest_field, + required=False, + ), + ) + return updated_cols diff --git a/backend/geonature/core/imports/checks/dataframe/core.py b/backend/geonature/core/imports/checks/dataframe/core.py new file mode 100644 index 0000000000..f20c1f6b14 --- /dev/null +++ b/backend/geonature/core/imports/checks/dataframe/core.py @@ -0,0 +1,293 @@ +from typing import Dict, Optional, Set +from functools import reduce + +from geonature.core.imports.checks.errors import ImportCodeError +import numpy as np +import pandas as pd +import sqlalchemy as sa + +from geonature.utils.env import db +from geonature.core.gn_meta.models import TDatasets + +from geonature.core.imports.models import BibFields, TImports + +from .utils import dataframe_check, error_replace + + +__all__ = ["check_required_values", "check_counts", "check_datasets"] + + +@dataframe_check +@error_replace( + ImportCodeError.MISSING_VALUE, + {"WKT", "longitude", "latitude"}, + ImportCodeError.NO_GEOM, + "Champs géométriques", +) +def check_required_values(df: pd.DataFrame, fields: Dict[str, BibFields]): + """ + Check if required values are present in the dataframe. + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check. + fields : Dict[str, BibFields] + Dictionary of fields to check. + + Yields + ------ + dict + Dictionary containing the error code, the column name and the invalid rows. + + Notes + ----- + Field is mandatory if: ((field.mandatory AND NOT (ANY optional_cond is not NaN)) OR (ANY mandatory_cond is not NaN)) + <=> ((field.mandatory AND ALL optional_cond are NaN ) OR (ANY mandatory_cond is not NaN)) + """ + + for field_name, field in fields.items(): + # array of OR conditions + mandatory_conditions = [] + + if field.mandatory: + cond = pd.Series(True, index=df.index) + if field.optional_conditions: + for opt_field_name in field.optional_conditions: + opt_field = fields[opt_field_name] + if opt_field.source_column not in df: + continue + cond = cond & df[opt_field.source_column].isna() + mandatory_conditions.append(cond) + + if field.mandatory_conditions: + for mand_field_name in field.mandatory_conditions: + mand_field = fields[mand_field_name] + if mand_field.source_column not in df: + continue + mandatory_conditions.append(df[mand_field.source_column].notna()) + + if mandatory_conditions: + if field.source_column in df: + empty_rows = df[field.source_column].isna() + else: + empty_rows = pd.Series(True, index=df.index) + cond = reduce(lambda x, y: x | y, mandatory_conditions) # OR on all conditions + invalid_rows = df[empty_rows & cond] + if len(invalid_rows): + yield { + "error_code": ImportCodeError.MISSING_VALUE, + "column": field_name, + "invalid_rows": invalid_rows, + } + + +def _check_ordering(df: pd.DataFrame, min_field: str, max_field: str): + """ + Check if the values in the `min_field` are lower or equal to the values + in the `max_field` for all the rows of the dataframe `df`. + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check. + min_field : str + The name of the column containing the minimum values. + max_field : str + The name of the column containing the maximum values. + + Yields + ------ + dict + Dictionary containing the invalid rows. + + """ + ordered = df[min_field] <= df[max_field] + ordered = ordered.fillna(False) + invalid_rows = df[~ordered & df[min_field].notna() & df[max_field].notna()] + yield { + "invalid_rows": invalid_rows, + } + + +@dataframe_check +def check_counts( + df: pd.DataFrame, count_min_field: str, count_max_field: str, default_count: int = None +): + """ + Check if the value in the `count_min_field` is lower or equal to the value in the `count_max_field` + + | count_min_field | count_max_field | + | --------------- | --------------- | + | 0 | 2 | --> ok + | 2 | 0 | --> provoke an error + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check. + count_min_field : BibField + The field containing the minimum count. + count_max_field : BibField + The field containing the maximum count. + default_count : object, optional + The default count to use if a count is missing, by default None. + + Yields + ------ + dict + Dictionary containing the error code, the column name and the invalid rows. + + Returns + ------ + set + Set of columns updated. + + """ + count_min_col = count_min_field.dest_field + count_max_col = count_max_field.dest_field + updated_cols = {count_max_col} + if count_min_col in df: + df[count_min_col] = df[count_min_col].where( + df[count_min_col].notna(), + other=default_count, + ) + if count_max_col in df: + yield from map( + lambda error: { + "column": count_min_col, + "error_code": "COUNT_MIN_SUP_COUNT_MAX", + **error, + }, + _check_ordering(df, count_min_col, count_max_col), + ) + # Complete empty count_max cells + df[count_max_col] = df[count_max_col].where( + df[count_max_col].notna(), + other=df[count_min_col], + ) + else: + df[count_max_col] = df[count_min_col] + updated_cols.add(count_max_col) + else: + updated_cols.add(count_min_col) + if count_max_col in df: + df[count_max_col] = df[count_max_col].where( + df[count_max_col].notna(), + other=default_count, + ) + df[count_min_col] = df[count_max_col] + else: + df[count_min_col] = default_count + df[count_max_col] = default_count + return updated_cols + + +@dataframe_check +def check_datasets( + imprt: TImports, + df: pd.DataFrame, + uuid_field: BibFields, + id_field: BibFields, + module_code: str, + object_code: Optional[str] = None, +) -> Set[str]: + """ + Check if datasets exist and are authorized for the user and import. + + Parameters + ---------- + imprt : TImports + Import to check datasets for. + df : pd.DataFrame + Dataframe to check. + uuid_field : BibFields + Field containing dataset UUIDs. + id_field : BibFields + Field to fill with dataset IDs. + module_code : str + Module code to check datasets for. + object_code : Optional[str], optional + Object code to check datasets for, by default None. + + Yields + ------ + dict + Dictionary containing error code, column name and invalid rows. + + Returns + ------ + Set[str] + Set of columns updated. + + """ + updated_cols = set() + uuid_col = uuid_field.dest_field + id_col = id_field.dest_field + + if uuid_col in df: + has_uuid_mask = df[uuid_col].notnull() + uuid = df.loc[has_uuid_mask, uuid_col].unique().tolist() + + datasets = { + ds.unique_dataset_id.hex: ds + for ds in TDatasets.query.filter(TDatasets.unique_dataset_id.in_(uuid)) + .options(sa.orm.joinedload(TDatasets.nomenclature_data_origin)) + .options(sa.orm.raiseload("*")) + .all() + } + valid_ds_mask = df[uuid_col].isin(datasets.keys()) + invalid_ds_mask = has_uuid_mask & ~valid_ds_mask + if invalid_ds_mask.any(): + yield { + "error_code": ImportCodeError.DATASET_NOT_FOUND, + "column": uuid_field.name_field, + "invalid_rows": df[invalid_ds_mask], + } + + inactive_dataset = [uuid for uuid, ds in datasets.items() if not ds.active] + inactive_dataset_mask = df[uuid_col].isin(inactive_dataset) + if inactive_dataset_mask.any(): + yield { + "error_code": ImportCodeError.DATASET_NOT_ACTIVE, + "column": uuid_field.name_field, + "invalid_rows": df[inactive_dataset_mask], + } + + # Warning: we check only permissions of first author, but currently there it only one author per import. + authorized_datasets = { + ds.unique_dataset_id.hex: ds + for ds in db.session.execute( + TDatasets.filter_by_creatable( + user=imprt.authors[0], module_code=module_code, object_code=object_code + ) + .where(TDatasets.unique_dataset_id.in_(uuid)) + .options(sa.orm.raiseload("*")) + ) + .scalars() + .all() + } + authorized_ds_mask = df[uuid_col].isin(authorized_datasets.keys()) + unauthorized_ds_mask = valid_ds_mask & ~authorized_ds_mask + if unauthorized_ds_mask.any(): + yield { + "error_code": ImportCodeError.DATASET_NOT_AUTHORIZED, + "column": uuid_field.name_field, + "invalid_rows": df[unauthorized_ds_mask], + } + + if authorized_ds_mask.any(): + df.loc[authorized_ds_mask, id_col] = df[authorized_ds_mask][uuid_col].apply( + lambda uuid: authorized_datasets[uuid].id_dataset + ) + updated_cols = {id_col} + + else: + has_uuid_mask = pd.Series(False, index=df.index) + + if (~has_uuid_mask).any(): + # Set id_dataset from import for empty cells: + df.loc[~has_uuid_mask, id_col] = imprt.id_dataset + updated_cols = {id_col} + + return updated_cols diff --git a/backend/geonature/core/imports/checks/dataframe/dates.py b/backend/geonature/core/imports/checks/dataframe/dates.py new file mode 100644 index 0000000000..e2d54fce0b --- /dev/null +++ b/backend/geonature/core/imports/checks/dataframe/dates.py @@ -0,0 +1,80 @@ +import pandas as pd + + +def concat_dates( + df: pd.DataFrame, + datetime_min_col: str, + datetime_max_col: str, + date_min_col: str, + date_max_col: str = None, + hour_min_col: str = None, + hour_max_col: str = None, +): + """ + Concatenates date and time columns to form datetime columns. + + Parameters + ---------- + df : pandas.DataFrame + The input DataFrame. + datetime_min_col : str + The column name for the minimum datetime. + datetime_max_col : str + The column name for the maximum datetime. + date_min_col : str + The column name for the minimum date. + date_max_col : str, optional + The column name for the maximum date. + hour_min_col : str, optional + The column name for the minimum hour. + hour_max_col : str, optional + The column name for the maximum hour. + + """ + assert datetime_min_col + assert datetime_max_col + assert date_min_col # date_min is a required field + date_max_col = date_max_col if date_max_col else None + hour_min_col = hour_min_col if hour_min_col else None + hour_max_col = hour_max_col if hour_max_col else None + + date_min = df[date_min_col] + + if hour_min_col and hour_min_col in df: + hour_min = df[hour_min_col].where(df[hour_min_col].notna(), other="00:00:00") + + if hour_min_col and hour_min_col in df: + df[datetime_min_col] = date_min + " " + hour_min + else: + df[datetime_min_col] = date_min + + if date_max_col and date_max_col in df: + date_max = df[date_max_col].where(df[date_max_col].notna(), date_min) + else: + date_max = date_min + + if hour_max_col and hour_max_col in df: + if date_max_col and date_max_col in df: + # hour max is set to hour min if date max is none (because date max will be set to date min), else 00:00:00 + if hour_min_col and hour_min_col in df: + # if hour_max not set, use hour_min if same day (or date_max not set, so same day) + hour_max = df[hour_max_col].where( + df[hour_max_col].notna(), + other=hour_min.where(date_min == date_max, other="00:00:00"), + ) + else: + hour_max = df[hour_max_col].where(df[hour_max_col].notna(), other="00:00:00") + else: + if hour_min_col and hour_min_col in df: + hour_max = df[hour_max_col].where(df[hour_max_col].notna(), other=hour_min) + else: + hour_max = df[hour_max_col].where(df[hour_max_col].notna(), other="00:00:00") + + if hour_max_col and hour_max_col in df: + df[datetime_max_col] = date_max + " " + hour_max + elif hour_min_col and hour_min_col in df: + df[datetime_max_col] = date_max + " " + hour_min + else: + df[datetime_max_col] = date_max + + return {datetime_min_col, datetime_max_col} diff --git a/backend/geonature/core/imports/checks/dataframe/geometry.py b/backend/geonature/core/imports/checks/dataframe/geometry.py new file mode 100644 index 0000000000..1a42068628 --- /dev/null +++ b/backend/geonature/core/imports/checks/dataframe/geometry.py @@ -0,0 +1,258 @@ +from functools import partial + +from geonature.core.imports.checks.errors import ImportCodeError +from geonature.core.imports.models import BibFields +import sqlalchemy as sa +from geoalchemy2.functions import ST_Transform, ST_GeomFromWKB, ST_GeomFromText +import pandas as pd +from shapely import wkt +from shapely.geometry import Point, Polygon +from shapely.geometry.base import BaseGeometry +from shapely.ops import transform +from pyproj import CRS, Transformer +from ref_geo.models import LAreas + +from geonature.utils.env import db + +from .utils import dataframe_check + + +def get_srid_bounding_box(srid): + """ + Return the local bounding box for a given srid + """ + xmin, ymin, xmax, ymax = CRS.from_epsg(srid).area_of_use.bounds + bounding_polygon_4326 = Polygon([(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]) + projection = Transformer.from_crs(CRS(4326), CRS(int(srid)), always_xy=True) + return transform(projection.transform, bounding_polygon_4326) + + +def wkt_to_geometry(value): + try: + return wkt.loads(value) + except Exception: + return None + + +def xy_to_geometry(x, y): + try: + return Point(float(x.replace(",", ".")), float(y.replace(",", "."))) + except Exception: + return None + + +def check_bound(p, bounding_box: Polygon): + return p.within(bounding_box) + + +def check_geometry_inside_l_areas(geometry: BaseGeometry, id_area: int, geom_srid: int): + """ + Same as `check_wkt_inside_l_areas` except we use a shapely geometry. + """ + return check_wkt_inside_area_id(wkt=geometry.wkt, id_area=id_area, wkt_srid=geom_srid) + + +def check_wkt_inside_area_id(wkt: str, id_area: int, wkt_srid: int): + """ + Checks if the provided wkt is inside the area defined + by id_area. + + Parameters + ---------- + wkt : str + geometry to check if inside the area + id_area : int + id to get the area in ref_geo.l_areas + wkt_srid : str + srid of the provided wkt + """ + local_srid = db.session.execute(sa.func.Find_SRID("ref_geo", "l_areas", "geom")).scalar() + + return db.session.scalar( + sa.exists(LAreas) + .where( + LAreas.id_area == id_area, + LAreas.geom.ST_Contains(ST_Transform(ST_GeomFromText(wkt, wkt_srid), local_srid)), + ) + .select() + ) + + +@dataframe_check +def check_geometry( + df: pd.DataFrame, + file_srid: int, + geom_4326_field: BibFields, + geom_local_field: BibFields, + wkt_field: BibFields = None, + latitude_field: BibFields = None, + longitude_field: BibFields = None, + codecommune_field: BibFields = None, + codemaille_field: BibFields = None, + codedepartement_field: BibFields = None, + id_area: int = None, +): + """ + + What this check do: + - check there is at least a wkt, a x/y or a code defined for each row + (report NO-GEOM if there are not, or MULTIPLE_ATTACHMENT_TYPE_CODE if several are defined) + - set geom_local or geom_4326 or both (depending of file_srid) from wkt or x/y + - check wkt validity + - check x/y validity + - check wkt & x/y bounding box + What this check does not do (done later in SQL): + - set geom_4326 & geom_local from code + - verify code validity + - set geom_4326 from geom_local, or reciprocally, depending of file_srid + - set geom_point + - check geom validity (ST_IsValid) + FIXME: area from code are never checked in bounding box! + + Parameters + ---------- + df : pandas.DataFrame + The dataframe to check + file_srid : int + The srid of the file + geom_4326_field : BibFields + The column in the dataframe that contains geometries in SRID 4326 + geom_local_field : BibFields + The column in the dataframe that contains geometries in the SRID of the area + wkt_field : BibFields, optional + The column in the dataframe that contains geometries' WKT + latitude_field : BibFields, optional + The column in the dataframe that contains latitudes + longitude_field : BibFields, optional + The column in the dataframe that contains longitudes + codecommune_field : BibFields, optional + The column in the dataframe that contains commune codes + codemaille_field : BibFields, optional + The column in the dataframe that contains maille codes + codedepartement_field : BibFields, optional + The column in the dataframe that contains departement codes + id_area : int, optional + The id of the area to check if the geometry is inside (Not Implemented) + + """ + + local_srid = db.session.execute(sa.func.Find_SRID("ref_geo", "l_areas", "geom")).scalar() + file_srid_bounding_box = get_srid_bounding_box(file_srid) + + wkt_col = wkt_field.source_field if wkt_field else None + latitude_col = latitude_field.source_field if latitude_field else None + longitude_col = longitude_field.source_field if longitude_field else None + codecommune_col = codecommune_field.source_field if codecommune_field else None + codemaille_col = codemaille_field.source_field if codemaille_field else None + codedepartement_col = codedepartement_field.source_field if codedepartement_field else None + + geom = pd.Series(name="geom", index=df.index, dtype="object") + + if wkt_col and wkt_col in df: + wkt_mask = df[wkt_col].notnull() + if wkt_mask.any(): + geom.loc[wkt_mask] = df[wkt_mask][wkt_col].apply(wkt_to_geometry) + invalid_wkt = geom[wkt_mask & geom.isnull()] + if not invalid_wkt.empty: + yield { + "error_code": ImportCodeError.INVALID_WKT, + "column": "WKT", + "invalid_rows": invalid_wkt, + } + else: + wkt_mask = pd.Series(False, index=df.index) + if latitude_col and latitude_col in df and longitude_col and longitude_col in df: + # take xy when no wkt and xy are not null + xy_mask = df[latitude_col].notnull() & df[longitude_col].notnull() + if xy_mask.any(): + geom.loc[xy_mask] = df[xy_mask].apply( + lambda row: xy_to_geometry(row[longitude_col], row[latitude_col]), axis=1 + ) + invalid_xy = df[xy_mask & geom.isnull()] + if not invalid_xy.empty: + yield { + "error_code": ImportCodeError.INVALID_GEOMETRY, + "column": "longitude", + "invalid_rows": invalid_xy, + } + else: + xy_mask = pd.Series(False, index=df.index) + + # Check multiple geo-referencement + multiple_georef = df[wkt_mask & xy_mask] + if len(multiple_georef): + geom[wkt_mask & xy_mask] = None + yield { + "error_code": ImportCodeError.MULTIPLE_ATTACHMENT_TYPE_CODE, + "column": "Champs géométriques", + "invalid_rows": multiple_georef, + } + + # Check out-of-bound geo-referencement + for mask, column in [(wkt_mask, "WKT"), (xy_mask, "longitude")]: + bound = geom[mask & geom.notnull()].apply( + partial(check_bound, bounding_box=file_srid_bounding_box) + ) + out_of_bound = df[mask & ~bound] + if len(out_of_bound): + geom.loc[mask & ~bound] = None + yield { + "error_code": ImportCodeError.GEOMETRY_OUT_OF_BOX, + "column": column, + "invalid_rows": out_of_bound, + } + + if codecommune_col and codecommune_col in df: + codecommune_mask = df[codecommune_col].notnull() + else: + codecommune_mask = pd.Series(False, index=df.index) + if codemaille_col and codemaille_col in df: + codemaille_mask = df[codemaille_col].notnull() + else: + codemaille_mask = pd.Series(False, index=df.index) + if codedepartement_col and codedepartement_col in df: + codedepartement_mask = df[codedepartement_col].notnull() + else: + codedepartement_mask = pd.Series(False, index=df.index) + + # Check for multiple code when no wkt or xy + multiple_code = df[ + ~wkt_mask + & ~xy_mask + & ( + (codecommune_mask & codemaille_mask) + | (codecommune_mask & codedepartement_mask) + | (codemaille_mask & codedepartement_mask) + ) + ] + if len(multiple_code): + yield { + "error_code": ImportCodeError.MULTIPLE_CODE_ATTACHMENT, + "column": "Champs géométriques", + "invalid_rows": multiple_code, + } + + if file_srid == 4326: + geom_4326_col = geom_4326_field.dest_field + df[geom_4326_col] = geom[geom.notna()].apply( + lambda g: ST_GeomFromWKB(g.wkb, file_srid), + ) + # geom_local will be defined in SQL + return {geom_4326_col} + elif file_srid == local_srid: + geom_local_col = geom_local_field.dest_field + df[geom_local_col] = geom[geom.notna()].apply( + lambda g: ST_GeomFromWKB(g.wkb, file_srid), + ) + # geom_4326 will be defined in SQL + return {geom_local_col} + else: + geom_4326_col = geom_4326_field.dest_field + geom_local_col = geom_local_field.dest_field + df[geom_4326_col] = geom[geom.notna()].apply( + lambda g: ST_Transform(ST_GeomFromWKB(g.wkb, file_srid), 4326), + ) + df[geom_local_col] = geom[geom.notna()].apply( + lambda g: ST_Transform(ST_GeomFromWKB(g.wkb, file_srid), local_srid), + ) + return {geom_4326_col, geom_local_col} diff --git a/backend/geonature/core/imports/checks/dataframe/utils.py b/backend/geonature/core/imports/checks/dataframe/utils.py new file mode 100644 index 0000000000..4257442559 --- /dev/null +++ b/backend/geonature/core/imports/checks/dataframe/utils.py @@ -0,0 +1,171 @@ +from functools import wraps +from inspect import signature + +from sqlalchemy import func +from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.dialects.postgresql import insert as pg_insert + +from geonature.utils.env import db + +from geonature.core.imports.models import ImportUserError, ImportUserErrorType +from geonature.core.imports.utils import generated_fields + + +def dataframe_check(check_function): + """ + Decorator for check functions. + Check functions must yield errors, and return updated_cols + (or None if no column have been modified). + """ + + parameters = signature(check_function).parameters + pass_import = "imprt" in parameters + pass_entity = "entity" in parameters + + @wraps(check_function) + def wrapper(imprt, entity, df, *args, **kwargs): + updated_cols = set() + params = [] + if pass_import: + params.append(imprt) + if pass_entity: + params.append(entity) + errors = check_function(*params, df, *args, **kwargs) + try: + while True: + error = next(errors) + updated_cols |= report_error(imprt, entity, df, error) or set() + except StopIteration as e: + updated_cols |= e.value or set() + return updated_cols + + return wrapper + + +def error_replace(old_code, old_columns, new_code, new_column=None): + """ + For rows which trigger old_code error on all old_columns, these errors are replaced + by new_code error on new_column. + Usage example: + @dataframe_check + @error_replace(ImportCodeError.MISSING_VALUE, {"WKT","latitude","longitude"}, ImportCodeError.NO_GEOM, "Champs géométriques") + def check_required_values: + … + => MISSING_VALUE on WKT, latitude and longitude are replaced by NO-GEOM on "Champs géométrique" + If new_code is None, error is deleted + """ + + def _error_replace(check_function): + @wraps(check_function) + def __error_replace(*args, **kwargs): + matching_errors = [] + errors_gen = check_function(*args, **kwargs) + try: + while True: + error = next(errors_gen) + if error["error_code"] != old_code: + yield error + continue + if error["column"] not in old_columns: + yield error + continue + matching_errors.append(error) + except StopIteration as e: + if matching_errors: + matching_indexes = list( + map(lambda e: set(e["invalid_rows"].index), matching_errors) + ) + commons_indexes = set.intersection(*matching_indexes) + if commons_indexes and new_code is not None: + # Yield replacing error + yield { + "error_code": new_code, + "column": new_column, + "invalid_rows": matching_errors[0]["invalid_rows"].loc[ + list(commons_indexes) + ], + } + for error in matching_errors: + indexes = set(error["invalid_rows"].index) - commons_indexes + if indexes: + # Yield old error but without rows where new error have been yield + yield { + "error_code": error["error_code"], + "column": error["column"], + "invalid_rows": error["invalid_rows"].loc[list(indexes)], + } + return e.value + + return __error_replace + + return _error_replace + + +def report_error(imprt, entity, df, error): + """ + Reports an error found in the dataframe, updates the validity column and insert + the error in the `t_user_errors` table. + + Parameters + ---------- + imprt : Import + The import entity. + entity : Entity + The entity to check. + df : pandas.DataFrame + The dataframe containing the data. + error : dict + The error to report. It should have the following keys: + - invalid_rows : DataFrame + The rows with errors. + - error_code : str + The name of the error code. + - column : str + The column with errors. + - comment : str, optional + A comment to add to the error. + + Returns + ------- + set + set containing the name of the entity validity column. + + Raises + ------ + Exception + If the error code is not found. + """ + if error["invalid_rows"].empty: + return + try: + error_type = ImportUserErrorType.query.filter_by(name=error["error_code"]).one() + except NoResultFound: + raise Exception(f"Error code '{error['error_code']}' not found.") + invalid_rows = error["invalid_rows"] + df.loc[invalid_rows.index, entity.validity_column] = False + # df['gn_invalid_reason'][invalid_rows.index.intersection(df['gn_invalid_reason'].isnull())] = \ + # f'{error_type.name}' # FIXME comment + ordered_invalid_rows = sorted(invalid_rows["line_no"]) + column = generated_fields.get(error["column"], error["column"]) + column = imprt.fieldmapping.get(column, column) + # If an error for same import, same column and of the same type already exists, + # we concat existing erroneous rows with current rows. + stmt = pg_insert(ImportUserError).values( + { + "id_import": imprt.id_import, + "id_error": error_type.pk, + "id_entity": entity.id_entity, + "column_error": column, + "id_rows": ordered_invalid_rows, + "comment": error.get("comment"), + } + ) + stmt = stmt.on_conflict_do_update( + index_elements=("id_import", "id_entity", "id_error", "column_error"), + index_where=ImportUserError.id_entity.isnot(None), + set_={ + "id_rows": func.array_cat(ImportUserError.rows, stmt.excluded["id_rows"]), + }, + ) + db.session.execute(stmt) + return {entity.validity_column} diff --git a/backend/geonature/core/imports/checks/errors.py b/backend/geonature/core/imports/checks/errors.py new file mode 100644 index 0000000000..f8e7449c7e --- /dev/null +++ b/backend/geonature/core/imports/checks/errors.py @@ -0,0 +1,211 @@ +class ImportCodeError: + """ + List of all the possible errors returned during the import process. + + Attributes + ---------- + DATASET_NOT_FOUND : str + The referenced dataset was not found + DATASET_NOT_AUTHORIZED : str + The dataset is not authorized to the current user + DATASET_NOT_ACTIVE : str + The dataset is inactive + MULTIPLE_ATTACHMENT_TYPE_CODE : str + Multiple attachments of the same type are not allowed + MULTIPLE_CODE_ATTACHMENT : str + Multiple attachments (commune, maille, departement) with the same code were given. + INVALID_DATE : str + The date is not valid + INVALID_UUID : str + The uuid is not valid + INVALID_INTEGER : str + The integer is not valid + INVALID_NUMERIC : str + The numeric is not valid + INVALID_WKT : str + The WKT string is not valid + INVALID_GEOMETRY : str + The geometry is not valid + INVALID_BOOL : str + The boolean is not valid + INVALID_ATTACHMENT_CODE : str + The code given does not exists in the desitination referential + INVALID_CHAR_LENGTH : str + The character length is not valid + DATE_MIN_TOO_HIGH : str + The date min is too high + DATE_MAX_TOO_LOW : str + The date max is too low + DATE_MAX_TOO_HIGH : str + The date max is too high + DATE_MIN_TOO_LOW : str + The date min is too low + ALTI_MIN_SUP_ALTI_MAX : str + The altitude min is superior to the altitude max + DATE_MIN_SUP_DATE_MAX : str + The date min is superior to the date max + DEPTH_MIN_SUP_ALTI_MAX : str + The depth min is superior to the altitude max + ORPHAN_ROW : str + The row could not be attached to an other entity # FIXME: clarify + DUPLICATE_ROWS : str + One rows appears more than once + DUPLICATE_UUID : str + A uuid value is duplicated + EXISTING_UUID: str + A uuid value already exists in the destination table + SKIP_EXISTING_UUID: str + A uuid value already exists in the destination table and should be skipped + MISSING_VALUE : str + A required value is missing (see `mandatory` column in `gn_imports.bib_fields` table) + MISSING_GEOM : str + The geometry is missing + GEOMETRY_OUTSIDE : str + The geometry is outside the polygon in the GeoNature configuration (`INSTANCE_BOUNDING_BOX`) + NO_GEOM : str + No geometry given (wherever WKT or latitude/longitude) + GEOMETRY_OUT_OF_BOX : str + The geometry is outside the perimeter of the instance geography # FIXME: clarify (confusion with GEOMETRY_OUTSIDE) + ERRONEOUS_PARENT_ENTITY : str + The parent entity is not valid + NO_PARENT_ENTITY : str + The parent entity is not found + DUPLICATE_ENTITY_SOURCE_PK : str + The entity source primary key is duplicated + COUNT_MIN_SUP_COUNT_MAX : str + The count min is superior to the count max + INVALID_NOMENCLATURE : str + The nomenclature is invalid + INVALID_EXISTING_PROOF_VALUE : str + The existing proof value is invalid + CONDITIONAL_MANDATORY_FIELD_ERROR : str + Some conditional mandatory fields are missing #FIXME: clarify + INVALID_NOMENCLATURE_WARNING : str + The nomenclature is invalid + UNKNOWN_ERROR : str + An unknown error occurred + INVALID_STATUT_SOURCE_VALUE : str + The statut source value is invalid + CONDITIONAL_INVALID_DATA : str + The conditional data is invalid + INVALID_URL_PROOF : str + The url proof is invalid + ROW_HAVE_TOO_MUCH_COLUMN : str + A row have too much column + ROW_HAVE_LESS_COLUMN : str + A row have less column + EMPTY_ROW : str + A row is empty + HEADER_SAME_COLUMN_NAME : str + The header have same column name + EMPTY_FILE : str + The file is empty + NO_FILE_SENDED : str + No file was sent + ERROR_WHILE_LOADING_FILE : str + An error occurred while loading the file + FILE_FORMAT_ERROR : str + The file format is not valid + FILE_EXTENSION_ERROR : str + The file extension is not valid + FILE_OVERSIZE : str + The file is too big + FILE_NAME_TOO_LONG : str + The file name is too long + FILE_WITH_NO_DATA : str + The file have no data + INCOHERENT_DATA : str + An entity data is different in multiple rows + CD_HAB_NOT_FOUND : str + The habitat code is not found + CD_NOM_NOT_FOUND : str + The cd_nom is not found in the instance TaxRef + + + """ + + # Dataset error + DATASET_NOT_FOUND = "DATASET_NOT_FOUND" + DATASET_NOT_AUTHORIZED = "DATASET_NOT_AUTHORIZED" + DATASET_NOT_ACTIVE = "DATASET_NOT_ACTIVE" + MULTIPLE_ATTACHMENT_TYPE_CODE = "MULTIPLE_ATTACHMENT_TYPE_CODE" + MULTIPLE_CODE_ATTACHMENT = "MULTIPLE_CODE_ATTACHMENT" + + # Invalid type error + INVALID_DATE = "INVALID_DATE" + INVALID_UUID = "INVALID_UUID" + INVALID_INTEGER = "INVALID_INTEGER" + INVALID_NUMERIC = "INVALID_NUMERIC" + INVALID_WKT = "INVALID_WKT" + INVALID_GEOMETRY = "INVALID_GEOMETRY" + INVALID_BOOL = "INVALID_BOOL" + INVALID_ATTACHMENT_CODE = "INVALID_ATTACHMENT_CODE" + INVALID_CHAR_LENGTH = "INVALID_CHAR_LENGTH" + + # Date error + DATE_MIN_TOO_HIGH = "DATE_MIN_TOO_HIGH" + DATE_MAX_TOO_LOW = "DATE_MAX_TOO_LOW" + DATE_MAX_TOO_HIGH = "DATE_MAX_TOO_HIGH" + DATE_MIN_TOO_LOW = "DATE_MIN_TOO_LOW" + + # Min value > max value errors + DATE_MIN_SUP_DATE_MAX = "DATE_MIN_SUP_DATE_MAX" + DEPTH_MIN_SUP_ALTI_MAX = "DEPTH_MIN_SUP_ALTI_MAX" + ALTI_MIN_SUP_ALTI_MAX = "ALTI_MIN_SUP_ALTI_MAX" + + # Line with no child entity associated to a parent + ORPHAN_ROW = "ORPHAN_ROW" + DUPLICATE_ROWS = "DUPLICATE_ROWS" + DUPLICATE_UUID = "DUPLICATE_UUID" + EXISTING_UUID = "EXISTING_UUID" + SKIP_EXISTING_UUID = "SKIP_EXISTING_UUID" + + # Missing value when required + MISSING_VALUE = "MISSING_VALUE" + + # Geometry + MISSING_GEOM = "MISSING_GEOM" + GEOMETRY_OUTSIDE = "GEOMETRY_OUTSIDE" + NO_GEOM = "NO-GEOM" + GEOMETRY_OUT_OF_BOX = "GEOMETRY_OUT_OF_BOX" + + # Check between child and parent entities + ERRONEOUS_PARENT_ENTITY = "ERRONEOUS_PARENT_ENTITY" + NO_PARENT_ENTITY = "NO_PARENT_ENTITY" + DUPLICATE_ENTITY_SOURCE_PK = "DUPLICATE_ENTITY_SOURCE_PK" + COUNT_MIN_SUP_COUNT_MAX = "COUNT_MIN_SUP_COUNT_MAX" + + # Nomenclature + INVALID_NOMENCLATURE = "INVALID_NOMENCLATURE" + INVALID_EXISTING_PROOF_VALUE = "INVALID_EXISTING_PROOF_VALUE" + INVALID_NOMENCLATURE_WARNING = "INVALID_NOMENCLATURE_WARNING" + + CONDITIONAL_MANDATORY_FIELD_ERROR = ( + "CONDITIONAL_MANDATORY_FIELD_ERROR" # FIXME : weird name and confusing where it is used + ) + + UNKNOWN_ERROR = "UNKNOWN_ERROR" + INVALID_STATUT_SOURCE_VALUE = "INVALID_STATUT_SOURCE_VALUE" + CONDITIONAL_INVALID_DATA = "CONDITIONAL_INVALID_DATA" + INVALID_URL_PROOF = "INVALID_URL_PROOF" + + # Source File related errors + ROW_HAVE_TOO_MUCH_COLUMN = "ROW_HAVE_TOO_MUCH_COLUMN" + ROW_HAVE_LESS_COLUMN = "ROW_HAVE_LESS_COLUMN" + EMPTY_ROW = "EMPTY_ROW" + HEADER_SAME_COLUMN_NAME = "HEADER_SAME_COLUMN_NAME" + EMPTY_FILE = "EMPTY_FILE" + NO_FILE_SENDED = "NO_FILE_SENDED" + ERROR_WHILE_LOADING_FILE = "ERROR_WHILE_LOADING_FILE" + FILE_FORMAT_ERROR = "FILE_FORMAT_ERROR" + FILE_EXTENSION_ERROR = "FILE_EXTENSION_ERROR" + FILE_OVERSIZE = "FILE_OVERSIZE" + FILE_NAME_TOO_LONG = "FILE_NAME_TOO_LONG" + FILE_WITH_NO_DATA = "FILE_WITH_NO_DATA" + + # Duplicate entry with different data error + INCOHERENT_DATA = "INCOHERENT_DATA" + + # Referential error + CD_HAB_NOT_FOUND = "CD_HAB_NOT_FOUND" + CD_NOM_NOT_FOUND = "CD_NOM_NOT_FOUND" diff --git a/backend/geonature/core/imports/checks/sql/__init__.py b/backend/geonature/core/imports/checks/sql/__init__.py new file mode 100644 index 0000000000..841dd5ac78 --- /dev/null +++ b/backend/geonature/core/imports/checks/sql/__init__.py @@ -0,0 +1,5 @@ +from .core import * +from .nomenclature import * +from .geo import * +from .extra import * +from .parent import * diff --git a/backend/geonature/core/imports/checks/sql/core.py b/backend/geonature/core/imports/checks/sql/core.py new file mode 100644 index 0000000000..c6fe31d26b --- /dev/null +++ b/backend/geonature/core/imports/checks/sql/core.py @@ -0,0 +1,118 @@ +from geonature.core.imports.checks.errors import ImportCodeError +from geonature.core.imports.checks.sql.utils import report_erroneous_rows +import sqlalchemy as sa + +from geonature.utils.env import db + +from geonature.core.imports.models import ( + Entity, + EntityField, + BibFields, +) + + +__all__ = ["init_rows_validity", "check_orphan_rows"] + + +def init_rows_validity(imprt): + """ + Validity columns are three-states: + - None: the row does not contains data for the given entity + - False: the row contains data for the given entity, but data are erroneous + - True: the row contains data for the given entity, and data are valid + """ + transient_table = imprt.destination.get_transient_table() + entities = ( + Entity.query.filter_by(destination=imprt.destination).order_by(sa.desc(Entity.order)).all() + ) + # Set validity=NULL (not parcicipating in the entity) for all rows + db.session.execute( + sa.update(transient_table) + .where(transient_table.c.id_import == imprt.id_import) + .values({entity.validity_column: None for entity in entities}) + ) + # Multi-entity fields are ignored for entity identification, but this is not an issue + # as rows with multi-entity field only will raise an ORPHAN_ROW error + selected_fields_names = [] + for field_name, source_field in imprt.fieldmapping.items(): + if type(source_field) == list: + selected_fields_names.extend(set(source_field) & set(imprt.columns)) + elif source_field in imprt.columns: + selected_fields_names.append(field_name) + for entity in entities: + # Select fields associated to this entity *and only to this entity* + fields = ( + db.session.query(BibFields) + .where(BibFields.name_field.in_(selected_fields_names)) + .where(BibFields.entities.any(EntityField.entity == entity)) + .where(~BibFields.entities.any(EntityField.entity != entity)) + .all() + ) + db.session.execute( + sa.update(transient_table) + .where(transient_table.c.id_import == imprt.id_import) + .where( + sa.or_(*[transient_table.c[field.source_column].isnot(None) for field in fields]) + ) + .values({entity.validity_column: True}) + ) + + +def check_orphan_rows(imprt): + transient_table = imprt.destination.get_transient_table() + # TODO: handle multi-source fields + # This is actually not a big issue as multi-source fields are unlikely to also be multi-entity fields. + selected_fields_names = [] + for field_name, source_field in imprt.fieldmapping.items(): + if type(source_field) == list: + selected_fields_names.extend(set(source_field) & set(imprt.columns)) + elif source_field in imprt.columns: + selected_fields_names.append(field_name) + # Select fields associated to multiple entities + AllEntityField = sa.orm.aliased(EntityField) + fields = ( + db.session.query(BibFields) + .join(EntityField) + .join(Entity) + .order_by(Entity.order) # errors are associated to the first Entity + .filter(BibFields.name_field.in_(selected_fields_names)) + .join(AllEntityField, AllEntityField.id_field == BibFields.id_field) + .group_by(BibFields.id_field, EntityField.id_field, Entity.id_entity) + .having(sa.func.count(AllEntityField.id_entity) > 1) + .all() + ) + for field in fields: + report_erroneous_rows( + imprt, + entity=None, # OK because ORPHAN_ROW has only WARNING level + error_type=ImportCodeError.ORPHAN_ROW, + error_column=field.name_field, + whereclause=sa.and_( + transient_table.c[field.source_field].isnot(None), + *[transient_table.c[col].is_(None) for col in imprt.destination.validity_columns], + ), + ) + + +def check_mandatory_field(imprt, entity, field): + transient_table = imprt.destination.get_transient_table() + source_field = transient_table.c[field.source_column] + whereclause = sa.and_( + transient_table.c[entity.validity_column].isnot(None), + source_field.is_(None), + ) + report_erroneous_rows( + imprt, + entity=entity, + error_type=ImportCodeError.MISSING_VALUE, + error_column=field.name_field, + whereclause=whereclause, + ) + + +# Currently not used as done during dataframe checks +def check_mandatory_fields(imprt, entity, fields): + for field in fields.values(): + if not field.mandatory or not field.dest_field: + continue + check_mandatory_field(imprt, entity, field) diff --git a/backend/geonature/core/imports/checks/sql/extra.py b/backend/geonature/core/imports/checks/sql/extra.py new file mode 100644 index 0000000000..80959e5332 --- /dev/null +++ b/backend/geonature/core/imports/checks/sql/extra.py @@ -0,0 +1,683 @@ +from datetime import date +from typing import Any, Optional + +from flask import current_app +from geonature.core.imports.checks.errors import ImportCodeError +from geonature.core.imports.models import BibFields, Entity, TImports +from sqlalchemy import func +from sqlalchemy.sql.expression import select, update, join +from sqlalchemy.sql import column +from sqlalchemy.orm import aliased +import sqlalchemy as sa + +from geonature.utils.env import db + +from geonature.core.imports.checks.sql.utils import ( + get_duplicates_query, + report_erroneous_rows, +) + +from apptax.taxonomie.models import Taxref, cor_nom_liste +from pypn_habref_api.models import Habref + + +def check_referential( + imprt: TImports, + entity: Entity, + field: BibFields, + reference_field: sa.Column, + error_type: str, + reference_table: Optional[sa.Table] = None, +) -> None: + """ + Check the referential integrity of a column in the transient table. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + field : BibFields + The field to check. + reference_field : BibFields + The reference field to check. + error_type : str + The type of error encountered. + reference_table : Optional[sa.Table], optional + The reference table to check. If not provided, it will be inferred from the reference_field. + """ + transient_table = imprt.destination.get_transient_table() + dest_field = transient_table.c[field.dest_field] + if reference_table is None: + reference_table = reference_field.class_ + # We outerjoin the referential, and select rows where there is a value in synthese field + # but no value in referential, which means no value in the referential matched synthese field. + cte = ( + select(transient_table.c.line_no) + .select_from( + join( + transient_table, + reference_table, + dest_field == reference_field, + isouter=True, + ) + ) + .where(transient_table.c.id_import == imprt.id_import) + .where(dest_field != None) + .where(reference_field == None) + .cte("invalid_ref") + ) + report_erroneous_rows( + imprt, + entity, + error_type=error_type, + error_column=field.name_field, + whereclause=transient_table.c.line_no == cte.c.line_no, + ) + + +def check_cd_nom( + imprt: TImports, entity: Entity, field: BibFields, list_id: Optional[int] = None +) -> None: + """ + Check the existence of a cd_nom in the Taxref referential. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + field : BibFields + The field to check. + list_id : Optional[int], optional + The list to filter on, by default None. + + """ + # Filter out on a taxhub list if provided + if list_id is not None: + reference_table = join( + Taxref, + cor_nom_liste, + sa.and_(cor_nom_liste.c.id_liste == list_id, cor_nom_liste.c.cd_nom == Taxref.cd_nom), + ) + else: + reference_table = Taxref + check_referential( + imprt, + entity, + field, + Taxref.cd_nom, + ImportCodeError.CD_NOM_NOT_FOUND, + reference_table=reference_table, + ) + + +def check_cd_hab(imprt: TImports, entity: Entity, field: BibFields) -> None: + """ + Check the existence of a cd_hab in the Habref referential. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + field : BibFields + The field to check. + + """ + check_referential(imprt, entity, field, Habref.cd_hab, ImportCodeError.CD_HAB_NOT_FOUND) + + +def generate_altitudes( + imprt: TImports, + geom_local_field: BibFields, + alt_min_field: BibFields, + alt_max_field: BibFields, +) -> None: + """ + Generate the altitudes based on geomatries, and given altitues in an import. + + Parameters + ---------- + imprt : TImports + The import to generate altitudes for. + geom_local_field : BibFields + The field representing the geometry in the destination import's transient table. + alt_min_field : BibFields + The field representing the minimum altitude in the destination import's transient table. + alt_max_field : BibFields + The field representing the maximum altitude in the destination import's transient table. + + """ + transient_table = imprt.destination.get_transient_table() + geom_col = geom_local_field.dest_field + altitudes = ( + select( + column("altitude_min"), + column("altitude_max"), + ) + .select_from(func.ref_geo.fct_get_altitude_intersection(transient_table.c[geom_col])) + .lateral("altitudes") + ) + cte = ( + select( + transient_table.c.id_import, + transient_table.c.line_no, + altitudes.c.altitude_min, + altitudes.c.altitude_max, + ) + .where(transient_table.c.id_import == imprt.id_import) + .where(transient_table.c[geom_col] != None) + .where( + sa.or_( + transient_table.c[alt_min_field.source_field] == None, + transient_table.c[alt_max_field.source_field] == None, + ) + ) + .cte("cte") + ) + stmt = ( + update(transient_table) + .where(transient_table.c.id_import == cte.c.id_import) + .where(transient_table.c.line_no == cte.c.line_no) + .values( + { + transient_table.c[alt_min_field.dest_field]: sa.case( + ( + transient_table.c[alt_min_field.source_field] == None, + cte.c.altitude_min, + ), + else_=transient_table.c[alt_min_field.dest_field], + ), + transient_table.c[alt_max_field.dest_field]: sa.case( + ( + transient_table.c[alt_max_field.source_field] == None, + cte.c.altitude_max, + ), + else_=transient_table.c[alt_max_field.dest_field], + ), + } + ) + ) + db.session.execute(stmt) + + +def check_duplicate_uuid(imprt: TImports, entity: Entity, uuid_field: BibFields): + """ + Check if there is already a record with the same uuid in the transient table. Include an error in the report for each entry with a uuid dupplicated. + + Parameters + ---------- + imprt : Import + The import to check. + entity : Entity + The entity to check. + uuid_field : BibFields + The field to check. + """ + transient_table = imprt.destination.get_transient_table() + uuid_col = transient_table.c[uuid_field.dest_field] + duplicates = get_duplicates_query( + imprt, + uuid_col, + whereclause=sa.and_( + transient_table.c[entity.validity_column].isnot(None), + uuid_col != None, + ), + ) + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.DUPLICATE_UUID, + error_column=uuid_field.name_field, + whereclause=(transient_table.c.line_no == duplicates.c.lines), + ) + + +def check_existing_uuid( + imprt: TImports, + entity: Entity, + uuid_field: BibFields, + whereclause: Any = sa.true(), + skip=False, +): + """ + Check if there is already a record with the same uuid in the destination table. + Include an error in the report for each existing uuid in the destination table. + Parameters + ---------- + imprt : Import + The import to check. + entity : Entity + The entity to check. + uuid_field : BibFields + The field to check. + whereclause : BooleanClause + The WHERE clause to apply to the check. + skip: Boolean + Raise SKIP_EXISTING_UUID instead of EXISTING_UUID and set row validity to None (do not import) + """ + transient_table = imprt.destination.get_transient_table() + dest_table = entity.get_destination_table() + error_type = "SKIP_EXISTING_UUID" if skip else "EXISTING_UUID" + report_erroneous_rows( + imprt, + entity, + error_type=error_type, + error_column=uuid_field.name_field, + whereclause=sa.and_( + transient_table.c[uuid_field.dest_field] == dest_table.c[uuid_field.dest_field], + transient_table.c[entity.validity_column].is_(True), + whereclause, + ), + level_validity_mapping={"ERROR": False, "WARNING": None}, + ) + + +def generate_missing_uuid_for_id_origin( + imprt: TImports, + uuid_field: BibFields, + id_origin_field: BibFields, +): + """ + Update records in the transient table where the uuid is None + with a new UUID. + Generate UUID in transient table when there are no UUID yet, but + there are a id_origin. + Ensure rows with same id_origin get the same UUID. + + Parameters + ---------- + imprt : TImports + The import to check. + uuid_field : BibFields + The field to check. + id_origin_field : BibFields + Field used to generate the UUID + """ + transient_table = imprt.destination.get_transient_table() + cte_generated_uuid = ( + sa.select( + transient_table.c[id_origin_field.source_field].label("id_source"), + func.uuid_generate_v4().label("uuid"), + ) + .group_by(transient_table.c[id_origin_field.source_field]) + .cte("cte_generated_uuid") + ) + + stmt = ( + update(transient_table) + .values( + { + transient_table.c[uuid_field.dest_field]: cte_generated_uuid.c.uuid, + } + ) + .where( + transient_table.c.id_import == imprt.id_import, + transient_table.c[id_origin_field.source_field] == cte_generated_uuid.c.id_source, + transient_table.c[uuid_field.source_field].is_(None), + ) + ) + db.session.execute(stmt) + + +def generate_missing_uuid( + imprt: TImports, + entity: Entity, + uuid_field: BibFields, + whereclause: Any = None, +): + """ + Update records in the transient table where the UUID is None + with a new UUID. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + uuid_field : BibFields + The field to check. + """ + + transient_table = imprt.destination.get_transient_table() + stmt = ( + update(transient_table) + .values( + { + transient_table.c[uuid_field.dest_field]: func.uuid_generate_v4(), + } + ) + .where( + transient_table.c.id_import == imprt.id_import, + transient_table.c[entity.validity_column].is_not(None), + transient_table.c[uuid_field.source_field].is_(None), + ) + ) + if whereclause is not None: + stmt = stmt.where(whereclause) + db.session.execute(stmt) + + +def check_duplicate_source_pk( + imprt: TImports, + entity: Entity, + field: BibFields, +) -> None: + """ + Check for duplicate source primary keys in the transient table of an import. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + field : BibFields + The field to check. + """ + transient_table = imprt.destination.get_transient_table() + dest_col = transient_table.c[field.dest_column] + duplicates = get_duplicates_query( + imprt, + dest_col, + whereclause=sa.and_( + transient_table.c[entity.validity_column].isnot(None), + dest_col != None, + ), + ) + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.DUPLICATE_ENTITY_SOURCE_PK, + error_column=field.name_field, + whereclause=(transient_table.c.line_no == duplicates.c.lines), + ) + + +def check_dates( + imprt: TImports, + entity: Entity, + date_min_field: BibFields = None, + date_max_field: BibFields = None, +) -> None: + """ + Check the validity of dates in the transient table of an import. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : TEntity + The entity to check. + date_min_field : BibFields, optional + The field representing the minimum date. + date_max_field : BibFields, optional + The field representing the maximum date. + + """ + transient_table = imprt.destination.get_transient_table() + today = date.today() + if date_min_field: + date_min_dest_col = transient_table.c[date_min_field.dest_field] + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.DATE_MIN_TOO_HIGH, + error_column=date_min_field.name_field, + whereclause=(date_min_dest_col > today), + ) + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.DATE_MIN_TOO_LOW, + error_column=date_min_field.name_field, + whereclause=(date_min_dest_col < date(1900, 1, 1)), + ) + if date_max_field: + date_max_dest_col = transient_table.c[date_max_field.dest_field] + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.DATE_MAX_TOO_HIGH, + error_column=date_max_field.name_field, + whereclause=sa.and_( + date_max_dest_col > today, + date_min_dest_col <= today, + ), + ) + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.DATE_MAX_TOO_LOW, + error_column=date_max_field.name_field, + whereclause=(date_max_dest_col < date(1900, 1, 1)), + ) + if date_min_field and date_max_field: + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.DATE_MIN_SUP_DATE_MAX, + error_column=date_min_field.name_field, + whereclause=(date_min_dest_col > date_max_dest_col), + ) + + +def check_altitudes( + imprt: TImports, + entity: Entity, + alti_min_field: BibFields = None, + alti_max_field: BibFields = None, +) -> None: + """ + Check the validity of altitudes in the transient table of an import. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : TEntity + The entity to check. + alti_min_field : BibFields, optional + The field representing the minimum altitude. + alti_max_field : BibFields, optional + The field representing the maximum altitude. + + """ + transient_table = imprt.destination.get_transient_table() + if alti_min_field: + alti_min_name_field = alti_min_field.name_field + alti_min_dest_col = transient_table.c[alti_min_field.dest_field] + + if alti_max_field: + alti_max_dest_col = transient_table.c[alti_max_field.dest_field] + + if alti_min_field and alti_max_field: + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.ALTI_MIN_SUP_ALTI_MAX, + error_column=alti_min_name_field, + whereclause=(alti_min_dest_col > alti_max_dest_col), + ) + + +def check_depths( + imprt: TImports, + entity: Entity, + depth_min_field: BibFields = None, + depth_max_field: BibFields = None, +) -> None: + """ + Check the validity of depths in the transient table of an import. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : TEntity + The entity to check. + depth_min_field : BibFields, optional + The field representing the minimum depth. + depth_max_field : BibFields, optional + The field representing the maximum depth. + + """ + transient_table = imprt.destination.get_transient_table() + if depth_min_field: + depth_min_name_field = depth_min_field.name_field + depth_min_dest_col = transient_table.c[depth_min_field.dest_field] + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.INVALID_INTEGER, + error_column=depth_min_name_field, + whereclause=(depth_min_dest_col < 0), + ) + + if depth_max_field: + depth_max_name_field = depth_max_field.name_field + depth_max_dest_col = transient_table.c[depth_max_field.dest_field] + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.INVALID_INTEGER, + error_column=depth_max_name_field, + whereclause=(depth_max_dest_col < 0), + ) + + if depth_min_field and depth_max_field: + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.DEPTH_MIN_SUP_ALTI_MAX, # Yes, there is a typo in db... Should be "DEPTH_MIN_SUP_DEPTH_MAX" + error_column=depth_min_name_field, + whereclause=(depth_min_dest_col > depth_max_dest_col), + ) + + +def check_digital_proof_urls(imprt, entity, digital_proof_field): + """ + Checks for valid URLs in a given column of a transient table. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : TEntity + The entity to check. + digital_proof_field : TField + The field containing the URLs to check. + """ + transient_table = imprt.destination.get_transient_table() + digital_proof_dest_col = transient_table.c[digital_proof_field.dest_field] + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.INVALID_URL_PROOF, + error_column=digital_proof_field.name_field, + whereclause=( + sa.and_( + digital_proof_dest_col is not None, + digital_proof_dest_col.op("!~")( + r"^(?:(?:https?|ftp):\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$" + ), + ) + ), + ) + + +def check_entity_data_consistency(imprt, entity, fields, grouping_field): + """ + Checks for rows with the same uuid, but different contents, + in the same entity. Used mainely for parent entities. + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + fields : BibFields + The fields to check. + grouping_field : BibFields + The field to group identical rows. + """ + transient_table = imprt.destination.get_transient_table() + grouping_col = transient_table.c[grouping_field.source_field] + + # get duplicates rows in the transient_table + duplicates = get_duplicates_query( + imprt, + grouping_col, + whereclause=sa.and_( + transient_table.c[entity.validity_column].is_not(None), + grouping_col != None, + ), + ) + + columns = [getattr(transient_table.c, field.source_field) for field in fields.values()] + + # hash the content of the entity to check for differences without + # comparing each columns + hashedRows = ( + select( + transient_table.c.line_no.label("lines"), + grouping_col.label("grouping_col"), + func.md5(func.concat(*columns)).label("hashed"), + ) + .where(transient_table.c.line_no == duplicates.c.lines) + .where(transient_table.c.id_import == imprt.id_import) + .alias("hashedRows") + ) + + # get the rows with differences + + erroneous = ( + select(hashedRows.c.grouping_col.label("grouping_col")) + .group_by(hashedRows.c.grouping_col) + .having(func.count(func.distinct(hashedRows.c.hashed)) > 1) + ) + + # note: rows are unidentified (None) instead of being marked as invalid (False) in order to avoid running checks + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.INCOHERENT_DATA, + error_column=grouping_field.name_field, # FIXME + whereclause=(grouping_col == erroneous.c.grouping_col), + level_validity_mapping={"ERROR": None}, + ) + + +def disable_duplicated_rows(imprt, entity, fields, grouping_field): + """ + When several rows have the same value in grouping field (typically UUID) field, + first one is untouched but following rows have validity set to None (do not import). + """ + transient_table = imprt.destination.get_transient_table() + grouping_col = transient_table.c[grouping_field.source_field] + + duplicates = ( + select( + transient_table.c.line_no, + func.row_number() + .over(partition_by=grouping_col, order_by=transient_table.c.line_no) + .label("row_number"), + ) + .where(transient_table.c.id_import == imprt.id_import) + .where(grouping_col.isnot(None)) + .where(transient_table.c[entity.validity_column].is_(True)) + .cte() + ) + + db.session.execute( + sa.update(transient_table) + .where(transient_table.c.id_import == imprt.id_import) + .where(transient_table.c.line_no == duplicates.c.line_no) + .where(duplicates.c.row_number > 1) # keep first row + .values({entity.validity_column: None}) + ) diff --git a/backend/geonature/core/imports/checks/sql/geo.py b/backend/geonature/core/imports/checks/sql/geo.py new file mode 100644 index 0000000000..65bb75e25f --- /dev/null +++ b/backend/geonature/core/imports/checks/sql/geo.py @@ -0,0 +1,190 @@ +from geonature.core.imports.checks.errors import ImportCodeError +from geonature.core.imports.models import BibFields, Entity, TImports +from sqlalchemy.sql.expression import select, update, join +import sqlalchemy as sa +from geoalchemy2.functions import ( + ST_Transform, + ST_IsValid, + ST_Centroid, +) +from geonature.utils.env import db + +from geonature.core.imports.checks.sql.utils import report_erroneous_rows + +from ref_geo.models import LAreas + + +__all__ = [ + "set_geom_point", + "convert_geom_columns", + "check_is_valid_geometry", + "check_geometry_outside", +] + + +def set_geom_point( + imprt: TImports, + entity: Entity, + geom_4326_field: BibFields, + geom_point_field: BibFields, +) -> None: + """ + Set the_geom_point as the centroid of the geometry in the transient table of an import. + + Parameters + ---------- + imprt : TImports + The import to update. + entity : Entity + The entity to update. + geom_4326_field : BibFields + Field containing the geometry in the transient table. + geom_point_field : BibFields + Field to store the centroid of the geometry in the transient table. + + Returns + ------- + None + """ + transient_table = imprt.destination.get_transient_table() + # Set the_geom_point: + stmt = ( + update(transient_table) + .where( + transient_table.c.id_import == imprt.id_import, + transient_table.c[entity.validity_column] == True, + ) + .values( + { + geom_point_field.dest_field: ST_Centroid( + transient_table.c[geom_4326_field.dest_field] + ), + } + ) + ) + db.session.execute(stmt) + + +def convert_geom_columns( + imprt: TImports, + entity: Entity, + geom_4326_field: BibFields, + geom_local_field: BibFields, +) -> None: + """ + Convert the geometry from the file SRID to the local SRID in the transient table of an import. + + Parameters + ---------- + imprt : TImports + The import to update. + entity : Entity + The entity to update. + geom_4326_field : BibFields + Field representing the geometry in the transient table in SRID 4326. + geom_local_field : BibFields + Field representing the geometry in the transient table in the local SRID. + """ + file_srid = imprt.srid + local_srid = db.session.execute(sa.func.Find_SRID("ref_geo", "l_areas", "geom")).scalar() + dest_srid = None + if file_srid == local_srid: + # dataframe check defined geom_local, we must use it to define geom_4326 + source_col = geom_local_field.dest_field + dest_col = geom_4326_field.dest_field + dest_srid = 4326 + elif file_srid == 4326: + # dataframe check defined geom_4326, we must use it to define geom_local + source_col = geom_4326_field.dest_field + dest_col = geom_local_field.dest_field + dest_srid = local_srid + else: + # dataframe check has already defined geom_4326 and geom_local + pass + if dest_srid: + transient_table = imprt.destination.get_transient_table() + stmt = ( + update(transient_table) + .where( + transient_table.c.id_import == imprt.id_import, + transient_table.c[entity.validity_column] == True, + transient_table.c[source_col] != None, + ) + .values( + { + dest_col: ST_Transform(transient_table.c[source_col], dest_srid), + } + ) + ) + db.session.execute(stmt) + + +def check_is_valid_geometry( + imprt: TImports, + entity: Entity, + wkt_field: BibFields, + geom_field: BibFields, +) -> None: + """ + Check if the geometry is valid in the transient table of an import. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + wkt_field : BibFields + Field containing the source WKT of the geometry. + geom_field : BibFields + Field containing the geometry from the WKT in `wkt_field` to be validated. + + """ + # It is useless to check valid WKT when created from X/Y + transient_table = imprt.destination.get_transient_table() + where_clause = sa.and_( + transient_table.c[wkt_field.source_field] != None, + sa.not_(ST_IsValid(transient_table.c[geom_field.dest_field])), + ) + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.INVALID_GEOMETRY, + error_column="WKT", + whereclause=where_clause, + ) + + +def check_geometry_outside( + imprt: TImports, + entity: Entity, + geom_local_field: BibFields, + id_area: int, +) -> None: + """ + For an import, check if one or more geometries in the transient table are outside a defined area. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + geom_local_field : BibFields + Field containing the geometry in the local SRID of the area. + id_area : int + The id of the area to check if the geometry is inside. + + """ + transient_table = imprt.destination.get_transient_table() + area = db.session.execute(sa.select(LAreas).where(LAreas.id_area == id_area)).scalar_one() + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.GEOMETRY_OUTSIDE, + error_column="Champs géométriques", + whereclause=sa.and_( + transient_table.c[entity.validity_column] == True, + transient_table.c[geom_local_field.dest_field].ST_Disjoint(area.geom), + ), + ) diff --git a/backend/geonature/core/imports/checks/sql/nomenclature.py b/backend/geonature/core/imports/checks/sql/nomenclature.py new file mode 100644 index 0000000000..83b2b8e32c --- /dev/null +++ b/backend/geonature/core/imports/checks/sql/nomenclature.py @@ -0,0 +1,301 @@ +from typing import Mapping, Optional +from geonature.core.imports.checks.errors import ImportCodeError +from sqlalchemy.sql.expression import select, update +from sqlalchemy.sql import column +import sqlalchemy as sa + +from geonature.core.gn_meta.models import TDatasets + +from geonature.utils.env import db + +from geonature.core.imports.models import BibFields, Entity, TImports +from geonature.core.imports.checks.sql.utils import report_erroneous_rows + +from pypnnomenclature.models import TNomenclatures, BibNomenclaturesTypes + + +__all__ = [ + "do_nomenclatures_mapping", + "check_nomenclature_exist_proof", + "check_nomenclature_blurring", + "check_nomenclature_source_status", + "check_nomenclature_technique_collect", +] + + +def do_nomenclatures_mapping( + imprt: TImports, + entity: Entity, + fields: Mapping[str, BibFields], + fill_with_defaults: bool = False, +) -> None: + """ + Set nomenclatures using content mapping. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + fields : Mapping[str, BibFields] + Mapping of field names to BibFields objects. + fill_with_defaults : bool, optional + If True, fill empty user fields with default nomenclatures. + + Notes + ----- + See the following link for explanation on empty fields and default nomenclature handling: + https://github.com/PnX-SI/gn_module_import/issues/68#issuecomment-1384267087 + """ + + transient_table = imprt.destination.get_transient_table() + # Set nomenclatures using content mapping + for field in filter(lambda field: field.mnemonique != None, fields.values()): + source_col = transient_table.c[field.source_field] + dest_col = transient_table.c[field.dest_field] + # This CTE return the list of source value / cd_nomenclature for a given nomenclature type + cte = ( + select( + sa.func.nullif(column("key"), "").label("value"), # replace "" by NULL + column("value").label("cd_nomenclature"), + ) + .select_from(sa.func.JSON_EACH_TEXT(TImports.contentmapping[field.mnemonique])) + .where(TImports.id_import == imprt.id_import) + .cte("cte") + ) + # This statement join the cte results with nomenclatures + # in order to set the id_nomenclature + stmt = ( + update(transient_table) + .where( + transient_table.c.id_import == imprt.id_import, + source_col.isnot_distinct_from(cte.c.value), # to ensure NULL == NULL is True + TNomenclatures.cd_nomenclature == cte.c.cd_nomenclature, + BibNomenclaturesTypes.mnemonique == field.mnemonique, + TNomenclatures.id_type == BibNomenclaturesTypes.id_type, + ) + .values({field.dest_field: TNomenclatures.id_nomenclature}) + ) + db.session.execute(stmt) + erroneous_conds = [dest_col == None] + if fill_with_defaults: + # Set default nomenclature for empty user fields + stmt = ( + update(transient_table) + .where( + transient_table.c.id_import == imprt.id_import, + source_col == None, + dest_col == None, + ) # empty source_col may be have been completed by mapping + .values( + { + field.dest_field: getattr( + sa.func, entity.destination_table_schema + ).get_default_nomenclature_value( + field.mnemonique, + ) + } + ) + ) + db.session.execute(stmt) + # Do not report invalid nomenclature when source_col is NULL: if dest_col is NULL, + # it is because there are no default nomenclature. This is the same as server + # default value getting default nomenclature which may be NULL (unexisting). + erroneous_conds.append(source_col != None) + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.INVALID_NOMENCLATURE, + error_column=field.name_field, + whereclause=sa.and_(*erroneous_conds), + ) + + +def check_nomenclature_exist_proof( + imprt: TImports, + entity: Entity, + nomenclature_field: BibFields, + digital_proof_field: Optional[BibFields], + non_digital_proof_field: Optional[BibFields], +) -> None: + """ + Check the existence of a nomenclature proof in the transient table. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + nomenclature_field : BibFields + The field representing the nomenclature to check. + digital_proof_field : Optional[BibFields] + The field for digital proof, if any. + non_digital_proof_field : Optional[BibFields] + The field for non-digital proof, if any. + """ + transient_table = imprt.destination.get_transient_table() + + if digital_proof_field is None and non_digital_proof_field is None: + return + + oui_nomenclature = db.session.execute( + sa.select(TNomenclatures).where( + TNomenclatures.mnemonique == "Oui", + TNomenclatures.nomenclature_type.has( + BibNomenclaturesTypes.mnemonique == nomenclature_field.mnemonique + ), + ) + ).scalar_one() + + oui_filter = ( + transient_table.c[nomenclature_field.dest_field] == oui_nomenclature.id_nomenclature + ) + proof_set_filters = [] + if digital_proof_field is not None: + proof_set_filters.append( + transient_table.c[digital_proof_field.dest_field] != None, + ) + if non_digital_proof_field is not None: + proof_set_filters.append( + transient_table.c[non_digital_proof_field.dest_field] != None, + ) + + proof_set_filter = sa.or_(*proof_set_filters) if proof_set_filters else sa.false() + + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.INVALID_EXISTING_PROOF_VALUE, + error_column=nomenclature_field.name_field, + whereclause=sa.or_( + sa.and_(oui_filter, ~proof_set_filter), + sa.and_(~oui_filter, proof_set_filter), + ), + ) + + +def check_nomenclature_blurring( + imprt, entity, blurring_field, id_dataset_field, uuid_dataset_field +): + """ + Raise an error if blurring not set. + Required if the dataset is private. + """ + transient_table = imprt.destination.get_transient_table() + id_nomenclature_private = db.session.scalar( + select(TNomenclatures.id_nomenclature).where( + TNomenclatures.nomenclature_type.has(BibNomenclaturesTypes.mnemonique == "DS_PUBLIQUE"), + TNomenclatures.mnemonique == "Privée", + ) + ) + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.CONDITIONAL_MANDATORY_FIELD_ERROR, + error_column=blurring_field.name_field, + whereclause=sa.and_( + sa.or_( + transient_table.c[id_dataset_field.name_field] == TDatasets.id_dataset, + transient_table.c[uuid_dataset_field.name_field] == TDatasets.unique_dataset_id, + ), + TDatasets.id_nomenclature_data_origin == id_nomenclature_private, + transient_table.c[blurring_field.dest_field] == None, + ), + ) + + +def check_nomenclature_source_status( + imprt: TImports, entity: Entity, source_status_field: BibFields, ref_biblio_field: BibFields +) -> None: + """ + Check the nomenclature source status and raise an error if the status is "Lit" (Literature) + whereas the reference biblio field is empty. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + source_status_field : BibFields + The field representing the source status. + ref_biblio_field : BibFields + The field representing the reference bibliography. + + Notes + ----- + The error codes are: + - CONDITIONAL_MANDATORY_FIELD_ERROR: the field is mandatory and not set. + """ + transient_table = imprt.destination.get_transient_table() + + litterature_nomenclature = db.session.execute( + sa.select(TNomenclatures).where( + TNomenclatures.nomenclature_type.has( + BibNomenclaturesTypes.mnemonique == "STATUT_SOURCE" + ), + TNomenclatures.cd_nomenclature == "Li", + ) + ).scalar_one() + + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.CONDITIONAL_MANDATORY_FIELD_ERROR, + error_column=source_status_field.name_field, + whereclause=sa.and_( + transient_table.c[source_status_field.dest_field] + == litterature_nomenclature.id_nomenclature, + transient_table.c[ref_biblio_field.dest_field] == None, + ), + ) + + +def check_nomenclature_technique_collect( + imprt: TImports, + entity: Entity, + source_status_field: BibFields, + technical_precision_field: BibFields, +) -> None: + """ + Check the nomenclature source status and raise an error if the status is "Autre, préciser" + whereas technical precision field is empty. + + Parameters + ---------- + imprt : TImports + The import to check. + entity : Entity + The entity to check. + source_status_field : BibFields + The field representing the source status. + technical_precision_field : BibFields + The field representing the technical precision. + + Notes + ----- + The error codes are: + - CONDITIONAL_MANDATORY_FIELD_ERROR: the field is mandatory and not set. + """ + transient_table = imprt.destination.get_transient_table() + other = db.session.execute( + sa.select(TNomenclatures).where( + TNomenclatures.nomenclature_type.has( + BibNomenclaturesTypes.mnemonique == "TECHNIQUE_COLLECT_HAB" + ), + TNomenclatures.cd_nomenclature == "10", + ) + ).scalar_one() + + report_erroneous_rows( + imprt, + entity, + error_type=ImportCodeError.CONDITIONAL_MANDATORY_FIELD_ERROR, + error_column=source_status_field.name_field, + whereclause=sa.and_( + transient_table.c[source_status_field.dest_field] == other.id_nomenclature, + transient_table.c[technical_precision_field.dest_field] == None, + ), + ) diff --git a/backend/geonature/core/imports/checks/sql/parent.py b/backend/geonature/core/imports/checks/sql/parent.py new file mode 100644 index 0000000000..aa2427c98b --- /dev/null +++ b/backend/geonature/core/imports/checks/sql/parent.py @@ -0,0 +1,199 @@ +from typing import List +from geonature.core.imports.checks.errors import ImportCodeError +from geonature.core.imports.models import BibFields, Entity, TImports +import sqlalchemy as sa +from sqlalchemy.orm import aliased + +from geonature.utils.env import db +from geonature.core.imports.checks.sql.utils import report_erroneous_rows + +__all__ = [ + "set_id_parent_from_destination", + "set_parent_line_no", + "check_no_parent_entity", + "check_erroneous_parent_entities", +] + + +def set_id_parent_from_destination( + imprt: TImports, + parent_entity: Entity, + child_entity: Entity, + id_field: BibFields, + fields: List[BibFields], +) -> None: + """ + Complete the id_parent column in the transient table of an import when the parent already exists in the destination table. + + Parameters + ---------- + imprt : TImports + The import to update. + parent_entity : Entity + The entity of the parent. + child_entity : Entity + The entity of the child. + id_field : BibFields + The field containing the id of the parent. + fields : List[BibFields] + The fields to use for matching the child with its parent in the destination table. + """ + transient_table = imprt.destination.get_transient_table() + parent_destination = parent_entity.get_destination_table() + for field in fields: + if field is None: + continue + db.session.execute( + sa.update(transient_table) + .where( + transient_table.c.id_import == imprt.id_import, + transient_table.c[child_entity.validity_column].isnot(None), + ) + # We need to complete the id_parent only for child not on the same row than a parent + .where(transient_table.c[parent_entity.validity_column].is_(None)) + # finding parent row: + .where(transient_table.c[field.dest_column] == parent_destination.c[field.dest_column]) + .values({id_field.dest_column: parent_destination.c[id_field.dest_column]}) + ) + + +def set_parent_line_no( + imprt: TImports, + parent_entity: Entity, + child_entity: Entity, + id_parent: BibFields, + parent_line_no: BibFields, + fields: List[BibFields], +) -> None: + """ + Set parent_line_no on child entities when: + - no parent entity on same line + - parent entity is valid + - looking for parent entity through each given field in fields + + Parameters + ---------- + imprt : TImports + The import to update. + parent_entity : Entity + The entity of the parent. + child_entity : Entity + The entity of the child. + id_parent : BibFields + The field containing the id of the parent. + parent_line_no : BibFields + The field containing the line number of the parent. + fields : List[BibFields] + The fields to use for matching the child with its parent in the destination table. + """ + transient_child = imprt.destination.get_transient_table() + transient_parent = aliased(transient_child, name="transient_parent") + for field in fields: + if field is None: + continue + db.session.execute( + sa.update(transient_child) + .where( + transient_child.c.id_import == imprt.id_import, + transient_child.c[child_entity.validity_column].isnot(None), + ) + # We need to complete the parent_line_no only for child not on the same row than a parent + .where(transient_child.c[parent_entity.validity_column].is_(None)) + # finding parent row: + .where( + transient_parent.c.id_import == imprt.id_import, + transient_parent.c[parent_entity.validity_column].isnot(None), + transient_parent.c[field.dest_column] == transient_child.c[field.dest_column], + ) + .values({parent_line_no: transient_parent.c.line_no}) + ) + + +def check_no_parent_entity( + imprt: TImports, + parent_entity: Entity, + child_entity: Entity, + id_parent: BibFields, + parent_line_no: BibFields, +) -> None: + """ + Station may be referenced: + - on the same line (station_validity is not None) + - by id_parent (parent already exists in destination) + - by parent_line_no (new parent from another line of the imported file - see set_parent_line_no) + + Parameters + ---------- + imprt : TImports + The import to check. + parent_entity : Entity + The entity of the parent. + child_entity : Entity + The entity of the child. + id_parent : BibFields + The field containing the id of the parent. + parent_line_no : BibFields + The field containing the line number of the parent. + """ + transient_table = imprt.destination.get_transient_table() + report_erroneous_rows( + imprt, + child_entity, + error_type=ImportCodeError.NO_PARENT_ENTITY, + error_column=id_parent, + whereclause=sa.and_( + # Complains for missing parent only for valid child, as parent may be missing + # because of erroneous uuid required to find the parent. + transient_table.c[child_entity.validity_column].is_(True), + transient_table.c[parent_entity.validity_column].is_(None), # no parent on same line + transient_table.c[id_parent].is_(None), # no parent in destination + transient_table.c[parent_line_no].is_(None), # no parent on another line + ), + ) + + +def check_erroneous_parent_entities( + imprt: TImports, parent_entity: Entity, child_entity: Entity, parent_line_no: BibFields +) -> None: + """ + Check for erroneous (not valid) parent entities in the transient table of an import. + + Parameters + ---------- + imprt : TImports + The import to check. + parent_entity : Entity + The entity of the parent. + child_entity : Entity + The entity of the child. + parent_line_no : BibFields + The field containing the line number of the parent. + + Notes + ----- + # Note: if child entity reference parent entity by id_parent, this means the parent + # entity is already in destination table so obviously valid. + + The error codes are: + - ERRONEOUS_PARENT_ENTITY: the parent on the same line is not valid. + """ + transient_child = imprt.destination.get_transient_table() + transient_parent = aliased(transient_child) + report_erroneous_rows( + imprt, + child_entity, + error_type=ImportCodeError.ERRONEOUS_PARENT_ENTITY, + error_column="", + whereclause=sa.and_( + transient_child.c[child_entity.validity_column].isnot(None), + sa.or_( + # parent is on the same line + transient_child.c[parent_entity.validity_column].is_(False), + sa.and_( # parent is on another line referenced by parent_line_no + transient_parent.c.id_import == transient_child.c.id_import, + transient_parent.c.line_no == transient_child.c[parent_line_no], + transient_parent.c[parent_entity.validity_column].is_(False), + ), + ), + ), + ) diff --git a/backend/geonature/core/imports/checks/sql/utils.py b/backend/geonature/core/imports/checks/sql/utils.py new file mode 100644 index 0000000000..e07168b001 --- /dev/null +++ b/backend/geonature/core/imports/checks/sql/utils.py @@ -0,0 +1,124 @@ +from sqlalchemy import func +from sqlalchemy.sql.expression import select, update, insert, literal +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import array_agg, aggregate_order_by + +from geonature.utils.env import db + +from geonature.core.imports.models import ( + ImportUserError, + ImportUserErrorType, +) +from geonature.core.imports.utils import generated_fields +import pandas as pd + + +__all__ = ["get_duplicates_query", "report_erroneous_rows"] + + +def get_duplicates_query(imprt, dest_field, whereclause=sa.true()): + transient_table = imprt.destination.get_transient_table() + whereclause = sa.and_( + transient_table.c.id_import == imprt.id_import, + whereclause, + ) + partitions = ( + select( + array_agg(transient_table.c.line_no) + .over( + partition_by=dest_field, + ) + .label("duplicate_lines") + ) + .where(whereclause) + .alias("partitions") + ) + duplicates = ( + select([func.unnest(partitions.c.duplicate_lines).label("lines")]) + .where(func.array_length(partitions.c.duplicate_lines, 1) > 1) + .distinct("lines") + .alias("duplicates") + ) + return duplicates + + +def report_erroneous_rows( + imprt, + entity, + error_type, + error_column, + whereclause, + level_validity_mapping={"ERROR": False}, +): + """ + This function report errors where whereclause in true. + But the function also set validity column to False for errors with ERROR level. + Warning: level of error "ERROR", the entity must be defined + + level_validity_mapping may be used to override default behavior: + - level does not exist in dict: row validity is untouched + - level exists in dict: row validity is set accordingly: + - False: row is marked as erroneous + - None: row is marked as should not be imported + """ + transient_table = imprt.destination.get_transient_table() + error_type = ImportUserErrorType.query.filter_by(name=error_type).one() + error_column = generated_fields.get(error_column, error_column) + error_column = imprt.fieldmapping.get(error_column, error_column) + if error_type.level in level_validity_mapping: + assert entity is not None + cte = ( + update(transient_table) + .values( + { + transient_table.c[entity.validity_column]: level_validity_mapping[ + error_type.level + ], + } + ) + .where(transient_table.c.id_import == imprt.id_import) + .where(whereclause) + .returning(transient_table.c.line_no) + .cte("cte") + ) + else: + cte = ( + select(transient_table.c.line_no) + .where(transient_table.c.id_import == imprt.id_import) + .where(whereclause) + .cte("cte") + ) + + insert_args = { + ImportUserError.id_import: literal(imprt.id_import).label("id_import"), + ImportUserError.id_type: literal(error_type.pk).label("id_type"), + ImportUserError.rows: array_agg(aggregate_order_by(cte.c.line_no, cte.c.line_no)).label( + "rows" + ), + ImportUserError.column: literal(error_column).label("error_column"), + } + + if entity is not None: + insert_args.update( + { + ImportUserError.id_entity: literal(entity.id_entity).label("id_entity"), + } + ) + + # Create the final insert statement + error_select = select(insert_args.values()).alias("error") + stmt = insert(ImportUserError).from_select( + names=insert_args.keys(), + select=(select(error_select).where(error_select.c.rows != None)), + ) + db.session.execute(stmt) + + +def print_transient_table(imprt, columns=None): + trans_table = imprt.destination.get_transient_table() + res = db.session.execute( + sa.select(*([trans_table.c[col] for col in columns] if columns else [trans_table])) + .where(imprt.id_import == trans_table.c.id_import) + .order_by(trans_table.c.line_no) + ).all() + print(pd.DataFrame(res, columns=columns).to_string()) diff --git a/backend/geonature/core/imports/commands.py b/backend/geonature/core/imports/commands.py new file mode 100644 index 0000000000..ff48d5ddef --- /dev/null +++ b/backend/geonature/core/imports/commands.py @@ -0,0 +1,124 @@ +import click + +from flask.cli import with_appcontext +import sqlalchemy as sa + +from geonature.utils.env import db + +from .models import FieldMapping + + +synthese_fieldmappings = { + "unique_id_sinp": "uuid_perm_sinp", + "entity_source_pk_value": "id_synthese", + "unique_id_sinp_grp": "uuid_perm_grp_sinp", + "unique_id_sinp_generate": "", + "meta_create_date": "date_creation", + "meta_v_taxref": "", + "meta_update_date": "date_modification", + "date_min": "date_debut", + "date_max": "date_fin", + "hour_min": "heure_debut", + "hour_max": "heure_fin", + "altitude_min": "alti_min", + "altitude_max": "alti_max", + "depth_min": "prof_min", + "depth_max": "prof_max", + "altitudes_generate": "", + "longitude": "", + "latitude": "", + "observers": "observateurs", + "comment_description": "comment_occurrence", + "id_nomenclature_info_geo_type": "type_info_geo", + "id_nomenclature_grp_typ": "type_regroupement", + "grp_method": "methode_regroupement", + "nom_cite": "nom_cite", + "cd_nom": "cd_nom", + "id_nomenclature_obs_technique": "technique_observation", + "id_nomenclature_bio_status": "biologique_statut", + "id_nomenclature_bio_condition": "etat_biologique", + "id_nomenclature_biogeo_status": "biogeographique_statut", + "id_nomenclature_behaviour": "comportement", + "id_nomenclature_naturalness": "naturalite", + "comment_context": "comment_releve", + "id_nomenclature_sensitivity": "niveau_sensibilite", + "id_nomenclature_diffusion_level": "niveau_precision_diffusion", + "id_nomenclature_blurring": "floutage_dee", + "id_nomenclature_life_stage": "stade_vie", + "id_nomenclature_sex": "sexe", + "id_nomenclature_type_count": "type_denombrement", + "id_nomenclature_obj_count": "objet_denombrement", + "count_min": "nombre_min", + "count_max": "nombre_max", + "id_nomenclature_determination_method": "methode_determination", + "determiner": "determinateur", + "id_digitiser": "", + "id_nomenclature_exist_proof": "preuve_existante", + "digital_proof": "preuve_numerique_url", + "non_digital_proof": "preuve_non_numerique", + "id_nomenclature_valid_status": "niveau_validation", + "validator": "validateur", + "meta_validation_date": "date_validation", + "validation_comment": "comment_validation", + "id_nomenclature_geo_object_nature": "nature_objet_geo", + "id_nomenclature_observation_status": "statut_observation", + "id_nomenclature_source_status": "statut_source", + "reference_biblio": "reference_biblio", + "cd_hab": "cd_habref", + "WKT": "geometrie_wkt_4326", + "place_name": "nom_lieu", + "precision": "precision_geographique", + "the_geom_point": "", + "the_geom_local": "", + "the_geom_4326": "", + "codecommune": "", + "codemaille": "", + "codedepartement": "", +} +dee_fieldmappings = { + "altitude_max": "altmax", + "altitude_min": "altmin", + "cd_nom": "cdnom", + "codecommune": "cdcom", + "codedepartement": "cddept", + "codemaille": "cdm10", + "count_max": "denombrementmax", + "count_min": "denombrementmin", + "WKT": "geometrie", + "unique_id_sinp": "permid", + "entity_source_pk_value": "permid", + "unique_id_sinp_grp": "permidgrp", + "date_min": "datedebut", + "date_max": "datefin", + "id_nomenclature_geo_object_nature": "natobjgeo", + "nom_cite": "nomcite", + "id_nomenclature_obj_count": "objdenbr", + "comment_context": "obsctx", + "comment_description": "obsdescr", + "id_nomenclature_obs_meth": "obsmeth", + "id_nomenclature_bio_condition": "ocetatbio", + "id_nomenclature_determination_method": "ocmethdet", + "id_nomenclature_naturalness": "ocnat", + "id_nomenclature_sex": "ocsex", + "id_nomenclature_life_stage": "ocstade", + "id_nomenclature_bio_status": "ocstatbio", + "id_nomenclature_exist_proof": "preuveoui", + "non_digital_proof": "Preuvnonum", + "digital_proof": "Preuvnum", + "id_nomenclature_observation_status": "statobs", + "id_nomenclature_source_status": "statsource", + "id_nomenclature_type_count": "typdenbr", + "id_nomenclature_grp_typ": "typgrp", +} + + +@click.command() +@with_appcontext +def fix_mappings(): + for label, values in [ + ("Synthese GeoNature", synthese_fieldmappings), + ("Format DEE (champs 10 char)", dee_fieldmappings), + ]: + mapping = db.session.execute(sa.select(FieldMapping).filter_by(label=label)).scalar_one() + mapping.values = values + db.session.commit() diff --git a/backend/geonature/core/imports/config_schema.py b/backend/geonature/core/imports/config_schema.py new file mode 100644 index 0000000000..2a77982e3f --- /dev/null +++ b/backend/geonature/core/imports/config_schema.py @@ -0,0 +1,139 @@ +""" + Spécification du schéma toml des paramètres de configurations +""" + +from marshmallow import Schema, fields +from marshmallow.validate import OneOf + +DEFAULT_LIST_COLUMN = [ + { + "prop": "format_source_file", + "name": "Format", + "show": False, + "filter": False, + }, + { + "prop": "full_file_name", + "name": "Fichier", + "show": True, + "filter": True, + }, + { + "prop": "dataset.dataset_name", + "name": "Jeu de données", + "show": True, + "filter": False, + }, + { + "prop": "statistics_rows", + "name": "Lignes importées", + "show": True, + "filter": False, + }, + { + "prop": "date_create_import", + "name": "Debut import", + "show": True, + "filter": True, + }, + { + "prop": "authors_name", + "name": "Auteur", + "show": True, + "filter": False, + }, +] + + +UPLOAD_DIRECTORY = "upload" + + +IMPORTS_SCHEMA_NAME = "gn_imports" + +PREFIX = "gn_" + +SRID = [{"name": "WGS84", "code": 4326}, {"name": "Lambert93", "code": 2154}] + +ENCODAGE = ["UTF-8"] + + +MAX_FILE_SIZE = 1000 + +ALLOWED_EXTENSIONS = [".csv"] + +DEFAULT_COUNT_VALUE = 1 + +ALLOW_VALUE_MAPPING = True + + +# If VALUE MAPPING is not allowed, you must specify the DEFAULT_VALUE_MAPPING_ID +DEFAULT_VALUE_MAPPING_ID = 3 + +INSTANCE_BOUNDING_BOX = [-5.0, 41, 10, 51.15] + +ALLOW_FIELD_MAPPING = True +DEFAULT_FIELD_MAPPING_ID = 1 +# Parameter to define if the checkbox allowing to change display mode is displayed or not. +DISPLAY_CHECK_BOX_MAPPED_FIELD = True + +# Parameter to define the rank shown in the doughnut chart in the import report +# must be in ['regne', 'phylum', 'classe', 'ordre', 'famille', 'sous_famille', 'tribu', 'group1_inpn', 'group2_inpn'] +DEFAULT_RANK = "regne" + + +class ImportConfigSchema(Schema): + LIST_COLUMNS_FRONTEND = fields.List(fields.Dict, load_default=DEFAULT_LIST_COLUMN) + PREFIX = fields.String(load_default=PREFIX) + SRID = fields.List(fields.Dict, load_default=SRID) + ENCODAGE = fields.List(fields.String, load_default=ENCODAGE) + MAX_FILE_SIZE = fields.Integer(load_default=MAX_FILE_SIZE) + MAX_ENCODING_DETECTION_DURATION = fields.Integer(load_default=2.0) + ALLOWED_EXTENSIONS = fields.List(fields.String, load_default=ALLOWED_EXTENSIONS) + DEFAULT_COUNT_VALUE = fields.Integer(load_default=DEFAULT_COUNT_VALUE) + ALLOW_VALUE_MAPPING = fields.Boolean(load_default=ALLOW_VALUE_MAPPING) + DEFAULT_VALUE_MAPPING_ID = fields.Integer( + load_default=DEFAULT_VALUE_MAPPING_ID + ) # FIXME: unused + FILL_MISSING_NOMENCLATURE_WITH_DEFAULT_VALUE = fields.Boolean(load_default=True) + DISPLAY_MAPPED_VALUES = fields.Boolean(load_default=True) # FIXME: unused + INSTANCE_BOUNDING_BOX = fields.List( + fields.Float, load_default=INSTANCE_BOUNDING_BOX + ) # FIXME: unused + ENABLE_BOUNDING_BOX_CHECK = fields.Boolean(load_default=True) # FIXME : unused + # When setting PER_DATASET_UUID_CHECK=True (used for import in synthese): + # - Replace the unicity constraint on unique_id_sinp with an unicity constraint on (unique_id_sinp,id_dataset). + # - Disable per-row dataset import by setting display=False in gn_imports.bib_fields + # for the id_dataset field belonging to synthese destination. + PER_DATASET_UUID_CHECK = fields.Boolean(load_default=False) + ALLOW_FIELD_MAPPING = fields.Boolean(load_default=ALLOW_FIELD_MAPPING) # FIXME: unused + DEFAULT_FIELD_MAPPING_ID = fields.Integer( + load_default=DEFAULT_FIELD_MAPPING_ID + ) # FIXME: unused + DISPLAY_CHECK_BOX_MAPPED_FIELD = fields.Boolean(load_default=True) + CHECK_PRIVATE_JDD_BLURING = fields.Boolean(load_default=True) + CHECK_REF_BIBLIO_LITTERATURE = fields.Boolean(load_default=True) + CHECK_EXIST_PROOF = fields.Boolean(load_default=True) + DEFAULT_GENERATE_MISSING_UUID = fields.Boolean(load_default=True) + DEFAULT_RANK = fields.String( + load_default=DEFAULT_RANK, + validate=OneOf( + [ + "regne", + "phylum", + "classe", + "ordre", + "famille", + "sous_famille", + "tribu", + "group1_inpn", + "group2_inpn", + ] + ), + ) + ID_AREA_RESTRICTION = fields.Integer(load_default=None) + ID_LIST_TAXA_RESTRICTION = fields.Integer(load_default=None) + MODULE_URL = fields.String(load_default="/import") + DATAFRAME_BATCH_SIZE = fields.Integer(load_default=10000) + EXPORT_REPORT_PDF_FILENAME = fields.String( + load_default="import_{id_import}_{date_create_import}_report.pdf" + ) diff --git a/backend/geonature/core/imports/logs.py b/backend/geonature/core/imports/logs.py new file mode 100644 index 0000000000..12e964911b --- /dev/null +++ b/backend/geonature/core/imports/logs.py @@ -0,0 +1,4 @@ +import logging + + +logger = logging.getLogger("geonature.core.imports") diff --git a/backend/geonature/core/imports/models.py b/backend/geonature/core/imports/models.py new file mode 100644 index 0000000000..ac724e94b1 --- /dev/null +++ b/backend/geonature/core/imports/models.py @@ -0,0 +1,732 @@ +from datetime import datetime +from collections.abc import Mapping +import re +from typing import Iterable, List, Optional +from packaging import version + +from flask import g +import sqlalchemy as sa +from sqlalchemy import func, ForeignKey, Table +from sqlalchemy.orm import relationship, deferred, joinedload +from sqlalchemy.types import ARRAY +from sqlalchemy.dialects.postgresql import JSON +from sqlalchemy.ext.mutable import MutableDict +from sqlalchemy.orm import column_property +from jsonschema.exceptions import ValidationError as JSONValidationError +from jsonschema import validate as validate_json +from celery.result import AsyncResult +import flask_sqlalchemy + +if version.parse(flask_sqlalchemy.__version__) >= version.parse("3"): + from flask_sqlalchemy.query import Query +else: # retro-compatibility Flask-SQLAlchemy 2 + from flask_sqlalchemy import BaseQuery as Query + +from utils_flask_sqla.models import qfilter +from utils_flask_sqla.serializers import serializable + +from geonature.utils.env import db +from geonature.utils.celery import celery_app +from geonature.core.gn_permissions.tools import get_scopes_by_action +from geonature.core.gn_commons.models import TModules +from geonature.core.gn_meta.models import TDatasets +from pypnnomenclature.models import BibNomenclaturesTypes +from pypnusershub.db.models import User + + +class ImportModule(TModules): + __mapper_args__ = { + "polymorphic_identity": "import", + } + + def generate_module_url_for_source(self, source): + id_import = re.search(r"^Import\(id=(?P\d+)\)$", source.name_source).group("id") + destination = db.session.scalars( + db.select(Destination.code) + .where(Destination.id_destination == TImports.id_destination) + .where(TImports.id_import == id_import) + ).one_or_none() + return f"/import/{destination}/{id_import}/report" + + +""" +Erreurs +======= + +ImportErrorType = un type d’erreur, avec sa description +ImportErrorType.category = la catégorie auquelle est rattaché ce type d’erreur + ex: le type d’erreur « date invalide » est rattaché à la catégorie « erreur de format » + note: c’est un champs texte libre, il n’y a pas de modèle ImportErrorCategory +ImportError = occurance d’un genre d’erreur, associé à une ou plusieurs ligne d’un import précis +""" + + +@serializable +class ImportUserErrorType(db.Model): + __tablename__ = "bib_errors_types" + __table_args__ = {"schema": "gn_imports"} + + pk = db.Column("id_error", db.Integer, primary_key=True) + category = db.Column("error_type", db.Unicode, nullable=False) + name = db.Column(db.Unicode, nullable=False, unique=True) + description = db.Column(db.Unicode) + level = db.Column("error_level", db.Unicode) + + def __str__(self): + return f"" + + +@serializable +class ImportUserError(db.Model): + __tablename__ = "t_user_errors" + __table_args__ = {"schema": "gn_imports"} + + pk = db.Column("id_user_error", db.Integer, primary_key=True) + id_import = db.Column( + db.Integer, + db.ForeignKey("gn_imports.t_imports.id_import", onupdate="CASCADE", ondelete="CASCADE"), + ) + imprt = db.relationship("TImports", back_populates="errors") + id_type = db.Column( + "id_error", + db.Integer, + db.ForeignKey(ImportUserErrorType.pk, onupdate="CASCADE", ondelete="CASCADE"), + ) + type = db.relationship("ImportUserErrorType") + column = db.Column("column_error", db.Unicode) + rows = db.Column("id_rows", db.ARRAY(db.Integer)) + comment = db.Column(db.UnicodeText) + id_entity = db.Column( + db.Integer, + db.ForeignKey("gn_imports.bib_entities.id_entity", onupdate="CASCADE", ondelete="CASCADE"), + ) + entity = db.relationship("Entity") + + def __str__(self): + return f"" + + +@serializable +class Destination(db.Model): + __tablename__ = "bib_destinations" + __table_args__ = {"schema": "gn_imports"} + + id_destination = db.Column(db.Integer, primary_key=True, autoincrement=True) + id_module = db.Column(db.Integer, ForeignKey(TModules.id_module), nullable=True) + code = db.Column(db.String(64), unique=True) + label = db.Column(db.String(128)) + table_name = db.Column(db.String(64)) + + module = relationship(TModules, backref="destination") + entities = relationship("Entity", back_populates="destination") + + def get_transient_table(self): + return Table( + self.table_name, + db.metadata, + autoload=True, + autoload_with=db.session.connection(), + schema="gn_imports", + ) + + @property + def validity_columns(self): + return [entity.validity_column for entity in self.entities] + + @property + def statistics_labels(self): + return self.actions.statistics_labels() + + @property + def actions(self): + try: + return self.module.__import_actions__ + except AttributeError as exc: + """ + This error is likely to occurs when you have some imports to a destination + for which the corresponding module is missing in the venv. + As a result, sqlalchemy fail to find the proper polymorphic identity, + and fallback on TModules which does not have __import_actions__ property. + """ + raise AttributeError(f"Is your module of type '{self.module.type}' installed?") from exc + + @staticmethod + def allowed_destinations( + user: Optional[User] = None, action_code: str = "C" + ) -> List["Destination"]: + """ + Return a list of allowed destinations for a given user and an action. + + Parameters + ---------- + user : User, optional + The user to filter destinations for. If not provided, the current_user is used. + action : str + The action to filter destinations for. Possible values are 'C', 'R', 'U', 'V', 'E', 'D'. + + Returns + ------- + allowed_destination : List of Destination + List of allowed destinations for the given user. + """ + # If no user is provided, use the current user + if not user: + user = g.current_user + + # Retrieve all destinations + all_destination = db.session.scalars(sa.select(Destination)).all() + return [dest for dest in all_destination if dest.has_instance_permission(user, action_code)] + + @qfilter + def filter_by_role(cls, user: Optional[User] = None, action_code: str = "C", **kwargs): + """ + Filter Destination by role. + + Parameters + ---------- + user : User, optional + The user to filter destinations for. If not provided, the current_user is used. + + Returns + ------- + sqlalchemy.sql.elements.BinaryExpression + A filter criterion for the ``id_destination`` column of the ``Destination`` table. + """ + allowed_destination = Destination.allowed_destinations(user=user, action_code=action_code) + return Destination.id_destination.in_(map(lambda x: x.id_destination, allowed_destination)) + + def has_instance_permission(self, user: Optional[User] = None, action_code: str = "C"): + """ + Check if a user has the permissions to do an action on this destination. + + Parameters + ---------- + user : User, optional + The user to check the permission for. If not provided, the current_user is used. + action_code : str + The action to check the permission for. Possible values are 'C', 'R', 'U', 'V', 'E', 'D'. + + Returns + ------- + bool + True if the user has the right to do the action on this destination, False otherwise. + """ + if not user: + user = g.current_user + + max_scope = get_scopes_by_action(id_role=user.id_role, module_code=self.module.module_code)[ + action_code + ] + return max_scope > 0 + + +@serializable +class BibThemes(db.Model): + __tablename__ = "bib_themes" + __table_args__ = {"schema": "gn_imports"} + + id_theme = db.Column(db.Integer, primary_key=True) + name_theme = db.Column(db.Unicode, nullable=False) + fr_label_theme = db.Column(db.Unicode, nullable=False) + eng_label_theme = db.Column(db.Unicode, nullable=True) + desc_theme = db.Column(db.Unicode, nullable=True) + order_theme = db.Column(db.Integer, nullable=False) + + +@serializable +class EntityField(db.Model): + __tablename__ = "cor_entity_field" + __table_args__ = {"schema": "gn_imports"} + + id_entity = db.Column( + db.Integer, db.ForeignKey("gn_imports.bib_entities.id_entity"), primary_key=True + ) + entity = relationship("Entity", back_populates="fields") + id_field = db.Column( + db.Integer, db.ForeignKey("gn_imports.bib_fields.id_field"), primary_key=True + ) + field = relationship("BibFields", back_populates="entities") + + desc_field = db.Column(db.Unicode, nullable=True) + id_theme = db.Column(db.Integer, db.ForeignKey(BibThemes.id_theme), nullable=False) + theme = relationship(BibThemes) + order_field = db.Column(db.Integer, nullable=False) + comment = db.Column(db.Unicode) + + +@serializable +class Entity(db.Model): + __tablename__ = "bib_entities" + __table_args__ = {"schema": "gn_imports"} + + id_entity = db.Column(db.Integer, primary_key=True, autoincrement=True) + id_destination = db.Column(db.Integer, ForeignKey(Destination.id_destination)) + destination = relationship(Destination, back_populates="entities") + code = db.Column(db.String(16)) + label = db.Column(db.String(64)) + order = db.Column(db.Integer) + validity_column = db.Column(db.String(64)) + destination_table_schema = db.Column(db.String(63)) + destination_table_name = db.Column(db.String(63)) + id_unique_column = db.Column( + db.Integer, db.ForeignKey("gn_imports.bib_fields.id_field"), primary_key=True + ) + id_parent = db.Column(db.Integer, ForeignKey("gn_imports.bib_entities.id_entity")) + + parent = relationship("Entity", back_populates="childs", remote_side=[id_entity]) + childs = relationship("Entity", back_populates="parent") + fields = relationship("EntityField", back_populates="entity") + unique_column = relationship("BibFields") + + def get_destination_table(self): + return Table( + self.destination_table_name, + db.metadata, + autoload=True, + autoload_with=db.session.connection(), + schema=self.destination_table_schema, + ) + + +class InstancePermissionMixin: + def get_instance_permissions(self, scopes, user=None): + if user is None: + user = g.current_user + if isinstance(scopes, Mapping): + return { + key: self.has_instance_permission(scope, user=user) for key, scope in scopes.items() + } + else: + return [self.has_instance_permission(scope, user=user) for scope in scopes] + + +cor_role_import = db.Table( + "cor_role_import", + db.Column("id_role", db.Integer, db.ForeignKey(User.id_role), primary_key=True), + db.Column( + "id_import", + db.Integer, + db.ForeignKey("gn_imports.t_imports.id_import"), + primary_key=True, + ), + schema="gn_imports", +) + + +@serializable( + fields=[ + "authors.nom_complet", + "dataset.dataset_name", + "dataset.active", + "destination.code", + "destination.label", + "destination.statistics_labels", + "destination.module", + ] +) +class TImports(InstancePermissionMixin, db.Model): + __tablename__ = "t_imports" + __table_args__ = {"schema": "gn_imports"} + # https://docs.python.org/3/library/codecs.html + # https://chardet.readthedocs.io/en/latest/supported-encodings.html + # TODO: move in configuration file + AVAILABLE_ENCODINGS = { + "utf-8", + "iso-8859-1", + "iso-8859-15", + } + AVAILABLE_FORMATS = ["csv", "geojson"] + AVAILABLE_SEPARATORS = [",", ";"] + + id_import = db.Column(db.Integer, primary_key=True, autoincrement=True) + id_destination = db.Column(db.Integer, ForeignKey(Destination.id_destination)) + destination = relationship(Destination) + format_source_file = db.Column(db.Unicode, nullable=True) + srid = db.Column(db.Integer, nullable=True) + separator = db.Column(db.Unicode, nullable=True) + detected_separator = db.Column(db.Unicode, nullable=True) + encoding = db.Column(db.Unicode, nullable=True) + detected_encoding = db.Column(db.Unicode, nullable=True) + # import_table = db.Column(db.Unicode, nullable=True) + full_file_name = db.Column(db.Unicode, nullable=True) + id_dataset = db.Column(db.Integer, ForeignKey("gn_meta.t_datasets.id_dataset"), nullable=True) + date_create_import = db.Column(db.DateTime, default=datetime.now) + date_update_import = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) + date_end_import = db.Column(db.DateTime, nullable=True) + source_count = db.Column(db.Integer, nullable=True) + erroneous_rows = deferred(db.Column(ARRAY(db.Integer), nullable=True)) + statistics = db.Column( + MutableDict.as_mutable(JSON), nullable=False, server_default="'{}'::jsonb" + ) + date_min_data = db.Column(db.DateTime, nullable=True) + date_max_data = db.Column(db.DateTime, nullable=True) + uuid_autogenerated = db.Column(db.Boolean) + altitude_autogenerated = db.Column(db.Boolean) + authors = db.relationship( + User, + lazy="joined", + secondary=cor_role_import, + ) + loaded = db.Column(db.Boolean, nullable=False, default=False) + processed = db.Column(db.Boolean, nullable=False, default=False) + dataset = db.relationship(TDatasets, lazy="joined") + source_file = deferred(db.Column(db.LargeBinary)) + columns = db.Column(ARRAY(db.Unicode)) + # keys are target names, values are source names + fieldmapping = db.Column(MutableDict.as_mutable(JSON)) + contentmapping = db.Column(MutableDict.as_mutable(JSON)) + task_id = db.Column(sa.String(155)) + + errors = db.relationship( + "ImportUserError", + back_populates="imprt", + order_by="ImportUserError.id_type", # TODO order by type.category + cascade="all, delete-orphan", + ) + + @property + def cruved(self): + + scopes_by_action = get_scopes_by_action(module_code="IMPORT", object_code="IMPORT") + return { + action: self.has_instance_permission(scope) + for action, scope in scopes_by_action.items() + } + + errors_count = column_property(func.array_length(erroneous_rows, 1)) + + @property + def task_progress(self): + if self.task_id is None: + return None + result = AsyncResult(self.task_id, app=celery_app) + if result.state in ["PENDING", "STARTED"]: + return 0 + elif result.state == "PROGRESS": + return result.result["progress"] + elif result.state == "SUCCESS": + return None + else: + return -1 + + def has_instance_permission(self, scope, user=None, action_code="C"): + + if user is None: + user = g.current_user + + if not self.destination.has_instance_permission(user, action_code) and action_code != "R": + return False + + if scope == 0: # pragma: no cover (should not happen as already checked by the decorator) + return False + elif scope == 1: # self + return user.id_role in [author.id_role for author in self.authors] + elif scope == 2: # organism + return user.id_role in [author.id_role for author in self.authors] or ( + user.id_organisme is not None + and user.id_organisme in [author.id_organisme for author in self.authors] + ) + elif scope == 3: # all + return True + + @staticmethod + def filter_by_scope(scope, user=None, **kwargs): + if user is None: + user = g.current_user + if scope == 0: + return sa.false() + elif scope in (1, 2): + filters = [User.id_role == user.id_role] + if scope == 2 and user.id_organisme is not None: + filters += [User.id_organisme == user.id_organisme] + return TImports.authors.any(sa.or_(*filters)) + elif scope == 3: + return sa.true() + else: + raise Exception(f"Unexpected scope {scope}") + + def as_dict(self, import_as_dict): + import_as_dict["authors_name"] = "; ".join([author.nom_complet for author in self.authors]) + if self.detected_encoding: + import_as_dict["available_encodings"] = sorted( + TImports.AVAILABLE_ENCODINGS + | { + self.detected_encoding, + } + ) + else: + import_as_dict["available_encodings"] = sorted(TImports.AVAILABLE_ENCODINGS) + import_as_dict["available_formats"] = TImports.AVAILABLE_FORMATS + import_as_dict["available_separators"] = TImports.AVAILABLE_SEPARATORS + if self.full_file_name and "." in self.full_file_name: + extension = self.full_file_name.rsplit(".", 1)[-1] + if extension in TImports.AVAILABLE_FORMATS: + import_as_dict["detected_format"] = extension + return import_as_dict + + +@serializable +class BibFields(db.Model): + __tablename__ = "bib_fields" + __table_args__ = {"schema": "gn_imports"} + + id_field = db.Column(db.Integer, primary_key=True) + id_destination = db.Column(db.Integer, ForeignKey(Destination.id_destination)) + destination = relationship(Destination) + name_field = db.Column(db.Unicode, nullable=False, unique=True) + source_field = db.Column(db.Unicode, unique=True) + dest_field = db.Column(db.Unicode, unique=True) + fr_label = db.Column(db.Unicode, nullable=False) + eng_label = db.Column(db.Unicode, nullable=True) + type_field = db.Column(db.Unicode, nullable=True) + mandatory = db.Column(db.Boolean, nullable=False) + autogenerated = db.Column(db.Boolean, nullable=False) + mnemonique = db.Column(db.Unicode, db.ForeignKey(BibNomenclaturesTypes.mnemonique)) + nomenclature_type = relationship("BibNomenclaturesTypes") + display = db.Column(db.Boolean, nullable=False) + multi = db.Column(db.Boolean) + optional_conditions = db.Column(db.ARRAY(db.Unicode), nullable=True) + mandatory_conditions = db.Column(db.ARRAY(db.Unicode), nullable=True) + + entities = relationship("EntityField", back_populates="field") + + @property + def source_column(self): + return self.source_field if self.source_field else self.dest_field + + @property + def dest_column(self): + return self.dest_field if self.dest_field else self.source_field + + def __str__(self): + return self.fr_label + + +cor_role_mapping = db.Table( + "cor_role_mapping", + db.Column("id_role", db.Integer, db.ForeignKey(User.id_role), primary_key=True), + db.Column( + "id_mapping", + db.Integer, + db.ForeignKey("gn_imports.t_mappings.id"), + primary_key=True, + ), + schema="gn_imports", +) + + +class MappingTemplate(db.Model): + __tablename__ = "t_mappings" + __table_args__ = {"schema": "gn_imports"} + + id = db.Column(db.Integer, primary_key=True) + id_destination = db.Column(db.Integer, ForeignKey(Destination.id_destination)) + destination = relationship(Destination) + label = db.Column(db.Unicode(255), nullable=False) + type = db.Column(db.Unicode(10), nullable=False) + active = db.Column(db.Boolean, nullable=False, default=True, server_default="true") + public = db.Column(db.Boolean, nullable=False, default=False, server_default="false") + + @property + def cruved(self): + scopes_by_action = get_scopes_by_action(module_code="IMPORT", object_code="MAPPING") + return { + action: self.has_instance_permission(scope) + for action, scope in scopes_by_action.items() + } + + __mapper_args__ = { + "polymorphic_on": type, + } + + owners = relationship( + User, + lazy="joined", + secondary=cor_role_mapping, + ) + + def has_instance_permission(self, scope: int, user=None): + if user is None: + user = g.current_user + if scope == 0: + return False + elif scope in (1, 2): + return user in self.owners or ( + scope == 2 + and user.id_organisme is not None + and user.id_organisme in [owner.id_organisme for owner in self.owners] + ) + elif scope == 3: + return True + + @staticmethod + def filter_by_scope(scope, user=None): + if user is None: + user = g.current_user + if scope == 0: + return sa.false() + elif scope in (1, 2): + filters = [ + MappingTemplate.public == True, + MappingTemplate.owners.any(id_role=user.id_role), + ] + if scope == 2 and user.id_organisme is not None: + filters.append(MappingTemplate.owners.any(id_organisme=user.id_organisme)) + return sa.or_(*filters) + elif scope == 3: + return sa.true() + else: + raise Exception(f"Unexpected scope {scope}") + + +def optional_conditions_to_jsonschema(name_field: str, optional_conditions: Iterable[str]) -> dict: + """ + Convert optional conditions into a JSON schema. + + Parameters + ---------- + name_field : str + The name of the field. + optional_conditions : Iterable[str] + The optional conditions. + + Returns + ------- + dict + The JSON schema. + + Notes + ----- + The JSON schema is created to ensure that if any of the optional conditions is not provided, + the name_field is required. + """ + assert isinstance(optional_conditions, list) + assert len(optional_conditions) > 0 + return { + "anyOf": [ + { + "if": { + "not": { + "properties": { + field_opt: {"type": "string"} for field_opt in optional_conditions + } + } + }, + "then": {"required": [name_field]}, + } + ] + } + + +@serializable +class FieldMapping(MappingTemplate): + __tablename__ = "t_fieldmappings" + __table_args__ = {"schema": "gn_imports"} + + id = db.Column(db.Integer, ForeignKey(MappingTemplate.id), primary_key=True) + values = db.Column(MutableDict.as_mutable(JSON)) + + __mapper_args__ = { + "polymorphic_identity": "FIELD", + } + + @staticmethod + def validate_values(values): + fields = ( + BibFields.query.filter_by(destination=g.destination, display=True) + .with_entities( + BibFields.name_field, + BibFields.autogenerated, + BibFields.mandatory, + BibFields.multi, + BibFields.optional_conditions, + BibFields.mandatory_conditions, + ) + .all() + ) + + schema = { + "type": "object", + "properties": { + field.name_field: { + "type": ( + "boolean" if field.autogenerated else ("array" if field.multi else "string") + ), + } + for field in fields + }, + "required": [ + field.name_field + for field in fields + if field.mandatory and not field.optional_conditions + ], + "dependentRequired": { + field.name_field: field.mandatory_conditions + for field in fields + if field.mandatory_conditions + }, + "additionalProperties": False, + } + optional_conditions = [ + optional_conditions_to_jsonschema(field.name_field, field.optional_conditions) + for field in fields + if field.optional_conditions + ] + if optional_conditions: + schema["allOf"] = optional_conditions + + try: + validate_json(values, schema) + except JSONValidationError as e: + raise ValueError(e.message) + + +@serializable +class ContentMapping(MappingTemplate): + __tablename__ = "t_contentmappings" + __table_args__ = {"schema": "gn_imports"} + + id = db.Column(db.Integer, ForeignKey(MappingTemplate.id), primary_key=True) + values = db.Column(MutableDict.as_mutable(JSON)) + + __mapper_args__ = { + "polymorphic_identity": "CONTENT", + } + + @staticmethod + def validate_values(values): + nomenclature_fields = ( + BibFields.query.filter( + BibFields.destination == g.destination, BibFields.nomenclature_type != None + ) + .options( + joinedload(BibFields.nomenclature_type).joinedload( + BibNomenclaturesTypes.nomenclatures + ), + ) + .all() + ) + properties = {} + for nomenclature_field in nomenclature_fields: + cd_nomenclatures = [ + nomenclature.cd_nomenclature + for nomenclature in nomenclature_field.nomenclature_type.nomenclatures + ] + properties[nomenclature_field.mnemonique] = { + "type": "object", + "patternProperties": { + "^.*$": { + "type": "string", + "enum": cd_nomenclatures, + }, + }, + } + schema = { + "type": "object", + "properties": properties, + "additionalProperties": False, + } + try: + validate_json(values, schema) + except JSONValidationError as e: + raise ValueError(e.message) diff --git a/backend/geonature/core/imports/routes/__init__.py b/backend/geonature/core/imports/routes/__init__.py new file mode 100644 index 0000000000..4c223cff1c --- /dev/null +++ b/backend/geonature/core/imports/routes/__init__.py @@ -0,0 +1,49 @@ +from geonature.core.gn_permissions.decorators import login_required + +from geonature.core.imports.models import Destination +from sqlalchemy.orm import joinedload +from geonature.core.imports.schemas import DestinationSchema +from geonature.core.imports.blueprint import blueprint +from geonature.utils.env import db + +import sqlalchemy as sa +from flask import g + + +@blueprint.route("/destinations/", methods=["GET"], defaults={"action_code": None}) +@blueprint.route("/destinations/", methods=["GET"]) +@login_required +def list_all_destinations(action_code): + """ + Return the list of all destinations. If an action code is provided, only the destinations + that the user has permission (based on the action_code) to access are returned. + + Parameters: + ---------- + action_code : str + The action code to filter destinations. Possible values are 'C', 'R', 'U', 'V', 'E', 'D'. + + Returns: + ------- + destinations : List of Destination + List of all destinations. + """ + + schema = DestinationSchema() + query = sa.select(Destination) + if action_code: + query = query.where(Destination.filter_by_role(g.current_user, action_code)) + destinations = db.session.execute(query).scalars().all() + return schema.dump(destinations, many=True) + + +@blueprint.route("/destination/", methods=["GET"]) +@login_required +def get_destination(destinationCode): + schema = DestinationSchema(only=["module"]) + destination = db.session.execute( + db.select(Destination) + .options(joinedload("module")) + .where(Destination.code == destinationCode) + ).scalar_one_or_none() + return schema.dump(destination) diff --git a/backend/geonature/core/imports/routes/fields.py b/backend/geonature/core/imports/routes/fields.py new file mode 100644 index 0000000000..c1ad4b52fe --- /dev/null +++ b/backend/geonature/core/imports/routes/fields.py @@ -0,0 +1,121 @@ +from itertools import groupby + +from flask import jsonify +from sqlalchemy.orm import joinedload, contains_eager, selectinload +import sqlalchemy as sa +from geonature.core.gn_permissions import decorators as permissions +from pypnnomenclature.models import BibNomenclaturesTypes + +from geonature.core.imports.models import ( + Entity, + EntityField, + BibFields, + BibThemes, +) + +from geonature.core.imports.blueprint import blueprint +from geonature.utils.env import db + + +@blueprint.route("//fields", methods=["GET"]) +@permissions.check_cruved_scope("C", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def get_fields(scope, destination): + """ + .. :quickref: Import; Get synthesis fields. + + Get all synthesis fields + Use in field mapping steps + You can find a jsonschema of the returned data in the associated test. + """ + data = [] + entities = db.session.scalars( + sa.select(Entity).filter_by(destination=destination).order_by(Entity.order) + ).all() + for entity in entities: + entity_fields = db.session.scalars( + sa.select(EntityField) + .where( + EntityField.entity == entity, + EntityField.field.has( + BibFields.display == True, + ), + ) + .join(BibThemes) + .order_by(BibThemes.order_theme, EntityField.order_field) + .options(selectinload(EntityField.field), selectinload(EntityField.theme)) + ).all() + themes = [] + for id_theme, efs in groupby(entity_fields, lambda ef: ef.theme.id_theme): + efs = list(efs) + themes.append( + { + "theme": efs[0].theme.as_dict( + fields=[ + "id_theme", + "name_theme", + "fr_label_theme", + "eng_label_theme", + "desc_theme", + ], + ), + # Front retro-compat: we flatten entityfield and field + "fields": [ + ef.as_dict( + fields=[ + "desc_field", + "comment", + ], + ) + | ef.field.as_dict( + fields=[ + "id_field", + "name_field", + "fr_label", + "eng_label", + "mandatory", + "autogenerated", + "multi", + "mandatory_conditions", + "optional_conditions", + ] + ) + for ef in efs + ], + } + ) + data.append( + { + "entity": entity.as_dict(fields=["label"]), + "themes": themes, + } + ) + return jsonify(data) + + +@blueprint.route("//nomenclatures", methods=["GET"]) +def get_nomenclatures(destination): + nomenclature_fields = ( + db.session.scalars( + sa.select(BibFields) + .where(BibFields.destination == destination, BibFields.nomenclature_type != None) + .options( + joinedload(BibFields.nomenclature_type).joinedload( + BibNomenclaturesTypes.nomenclatures + ), + ) + ) + .unique() + .all() + ) + return jsonify( + { + field.nomenclature_type.mnemonique: { + "nomenclature_type": field.nomenclature_type.as_dict(), + "nomenclatures": { + nomenclature.cd_nomenclature: nomenclature.as_dict() + for nomenclature in field.nomenclature_type.nomenclatures + }, + } + for field in nomenclature_fields + } + ) diff --git a/backend/geonature/core/imports/routes/imports.py b/backend/geonature/core/imports/routes/imports.py new file mode 100644 index 0000000000..12284a1e3f --- /dev/null +++ b/backend/geonature/core/imports/routes/imports.py @@ -0,0 +1,733 @@ +import codecs +from io import BytesIO, StringIO, TextIOWrapper +import csv +import json +import unicodedata + +from flask import request, current_app, jsonify, g, stream_with_context, send_file +from flask_login import current_user +from werkzeug.exceptions import Conflict, BadRequest, Forbidden, Gone, NotFound + +# url_quote was deprecated in werkzeug 3.0 https://stackoverflow.com/a/77222063/5807438 +from urllib.parse import quote as url_quote +from sqlalchemy import or_, func, desc, select, delete +from sqlalchemy.inspection import inspect +from sqlalchemy.orm import joinedload, Load, load_only, undefer, contains_eager +from sqlalchemy.orm.attributes import set_committed_value +from sqlalchemy.sql.expression import collate, exists + + +from geonature.utils.env import db +from geonature.utils.sentry import start_sentry_child +from geonature.core.gn_commons.models import TModules +from geonature.core.gn_permissions import decorators as permissions +from geonature.core.gn_permissions.decorators import login_required +from geonature.core.gn_permissions.tools import get_scopes_by_action +from geonature.core.gn_meta.models import TDatasets + +from pypnnomenclature.models import TNomenclatures + +from geonature.core.imports.models import ( + Destination, + Entity, + EntityField, + TImports, + ImportUserError, + BibFields, + FieldMapping, + ContentMapping, +) +from pypnusershub.db.models import User +from geonature.core.imports.blueprint import blueprint +from geonature.core.imports.utils import ( + ImportStep, + detect_encoding, + detect_separator, + insert_import_data_in_transient_table, + get_file_size, + clean_import, + generate_pdf_from_template, +) +from geonature.core.imports.tasks import do_import_checks, do_import_in_destination + +IMPORTS_PER_PAGE = 15 + + +@blueprint.url_value_preprocessor +def resolve_import(endpoint, values): + if current_app.url_map.is_endpoint_expecting(endpoint, "import_id"): + import_id = values.pop("import_id") + if import_id is not None: + imprt = TImports.query.options( + joinedload(TImports.destination).joinedload(Destination.module) + ).get_or_404(import_id) + if imprt.destination != values.pop("destination"): + raise NotFound + else: + imprt = None + values["imprt"] = imprt + + +@blueprint.route("/imports/", methods=["GET"]) +@blueprint.route("//imports/", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def get_import_list(scope, destination=None): + """ + .. :quickref: Import; Get all imports. + + Get all imports to which logged-in user has access. + """ + page = request.args.get("page", default=1, type=int) + limit = request.args.get("limit", default=IMPORTS_PER_PAGE, type=int) + search = request.args.get("search", default=None, type=str) + sort = request.args.get("sort", default="date_create_import", type=str) + sort_dir = request.args.get("sort_dir", default="desc", type=str) + filters = [] + if search: + filters.append(TImports.full_file_name.ilike(f"%{search}%")) + filters.append( + TImports.dataset.has( + func.lower(TDatasets.dataset_name).contains(func.lower(search)), + ) + ) + filters.append( + TImports.authors.any( + or_( + User.prenom_role.ilike(f"%{search}%"), + User.nom_role.ilike(f"%{search}%"), + ), + ) + ) + filters.append( + TImports.authors.any( + func.lower(User.nom_role).contains(func.lower(search)), + ) + ) + try: + order_by = get_foreign_key_attr(TImports, sort) + order_by = order_by() if callable(order_by) else order_by + except AttributeError: + raise BadRequest(f"Import field '{sort}' does not exist.") + if sort_dir == "desc": + order_by = desc(order_by) + + query = ( + select(TImports) + .options( + contains_eager(TImports.dataset), + contains_eager(TImports.authors), + contains_eager(TImports.destination).contains_eager(Destination.module), + ) + .join(TImports.dataset, isouter=True) + .join(TImports.authors, isouter=True) + .join(Destination) + .join(TModules) + .where(TImports.filter_by_scope(scope=scope)) + .where(or_(*filters) if len(filters) > 0 else True) + .order_by(order_by) + ) + + if destination: + query = query.where(TImports.destination == destination) + + imports = db.paginate(query, page=page, error_out=False, max_per_page=limit) + + data = { + "imports": [imprt.as_dict() for imprt in imports.items], + "count": imports.total, + "limit": limit, + "offset": page - 1, + } + return jsonify(data) + + +@blueprint.route("//imports//", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def get_one_import(scope, imprt): + """ + .. :quickref: Import; Get an import. + + Get an import. + """ + # check that the user has read permission to this particular import instance: + if not imprt.has_instance_permission(scope, action_code="R"): + raise Forbidden + return jsonify(imprt.as_dict()) + + +@blueprint.route("//imports/upload", defaults={"import_id": None}, methods=["POST"]) +@blueprint.route("//imports//upload", methods=["PUT"]) +@permissions.check_cruved_scope("C", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def upload_file(scope, imprt, destination=None): # destination is set when imprt is None + """ + .. :quickref: Import; Add an import or update an existing import. + + Add an import or update an existing import. + + :form file: file to import + :form int datasetId: dataset ID to which import data + """ + if imprt: + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.dataset.active: + raise Forbidden("Le jeu de données est fermé.") + destination = imprt.destination + else: + assert destination + author = g.current_user + f = request.files["file"] + size = get_file_size(f) + # value in config file is in Mo + max_file_size = current_app.config["IMPORT"]["MAX_FILE_SIZE"] * 1024 * 1024 + if size > max_file_size: + raise BadRequest( + description=f"File too big ({size} > {max_file_size})." + ) # FIXME better error signaling? + if size == 0: + raise BadRequest(description="Impossible to upload empty files") + if imprt is None: + try: + dataset_id = int(request.form["datasetId"]) + except ValueError: + raise BadRequest(description="'datasetId' must be an integer.") + dataset = db.session.get(TDatasets, dataset_id) + if dataset is None: + raise BadRequest(description=f"Dataset '{dataset_id}' does not exist.") + ds_scope = get_scopes_by_action( + module_code=destination.module.module_code, + object_code="ALL", # TODO object_code should be configurable by destination + )["C"] + if not dataset.has_instance_permission(ds_scope): + raise Forbidden(description="Vous n’avez pas les permissions sur ce jeu de données.") + if not dataset.active: + raise Forbidden("Le jeu de données est fermé.") + imprt = TImports(destination=destination, dataset=dataset) + imprt.authors.append(author) + db.session.add(imprt) + else: + clean_import(imprt, ImportStep.UPLOAD) + with start_sentry_child(op="task", description="detect encoding"): + imprt.detected_encoding = detect_encoding(f) + with start_sentry_child(op="task", description="detect separator"): + imprt.detected_separator = detect_separator( + f, + encoding=imprt.encoding or imprt.detected_encoding, + ) + imprt.source_file = f.read() + imprt.full_file_name = f.filename + + db.session.commit() + return jsonify(imprt.as_dict()) + + +@blueprint.route("//imports//decode", methods=["POST"]) +@permissions.check_cruved_scope("C", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def decode_file(scope, imprt): + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.dataset.active: + raise Forbidden("Le jeu de données est fermé.") + if imprt.source_file is None: + raise BadRequest(description="A file must be first uploaded.") + if "encoding" not in request.json: + raise BadRequest(description="Missing encoding.") + encoding = request.json["encoding"] + try: + codecs.lookup(encoding) + except LookupError: + raise BadRequest(description="Unknown encoding.") + imprt.encoding = encoding + if "format" not in request.json: + raise BadRequest(description="Missing format.") + if request.json["format"] not in TImports.AVAILABLE_FORMATS: + raise BadRequest(description="Unknown format.") + imprt.format_source_file = request.json["format"] + if "srid" not in request.json: + raise BadRequest(description="Missing srid.") + try: + imprt.srid = int(request.json["srid"]) + except ValueError: + raise BadRequest(description="SRID must be an integer.") + if "separator" not in request.json: + raise BadRequest(description="Missing separator") + if request.json["separator"] not in TImports.AVAILABLE_SEPARATORS: + raise BadRequest(description="Unknown separator") + imprt.separator = request.json["separator"] + + clean_import(imprt, ImportStep.DECODE) + + db.session.commit() # commit parameters + + decode = request.args.get("decode", 1) + try: + decode = int(decode) + except ValueError: + raise BadRequest(description="decode parameter must but an int") + if decode: + csvfile = TextIOWrapper(BytesIO(imprt.source_file), encoding=imprt.encoding) + csvreader = csv.reader(csvfile, delimiter=imprt.separator) + try: + columns = next(csvreader) + while True: # read full file to ensure that no encoding errors occur + next(csvreader) + except UnicodeError: + raise BadRequest( + description="Erreur d’encodage lors de la lecture du fichier source. " + "Avez-vous sélectionné le bon encodage de votre fichier ?" + ) + except StopIteration: + pass + duplicates = set([col for col in columns if columns.count(col) > 1]) + if duplicates: + raise BadRequest(f"Duplicates column names: {duplicates}") + imprt.columns = columns + db.session.commit() + + return jsonify(imprt.as_dict()) + + +@blueprint.route("//imports//fieldmapping", methods=["POST"]) +@permissions.check_cruved_scope("C", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def set_import_field_mapping(scope, imprt): + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.dataset.active: + raise Forbidden("Le jeu de données est fermé.") + try: + FieldMapping.validate_values(request.json) + except ValueError as e: + raise BadRequest(*e.args) + imprt.fieldmapping = request.json + clean_import(imprt, ImportStep.LOAD) + db.session.commit() + return jsonify(imprt.as_dict()) + + +@blueprint.route("//imports//load", methods=["POST"]) +@permissions.check_cruved_scope("C", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def load_import(scope, imprt): + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.dataset.active: + raise Forbidden("Le jeu de données est fermé.") + if imprt.source_file is None: + raise BadRequest(description="A file must be first uploaded.") + if imprt.fieldmapping is None: + raise BadRequest(description="File fields must be first mapped.") + clean_import(imprt, ImportStep.LOAD) + with start_sentry_child(op="task", description="insert data in db"): + line_no = insert_import_data_in_transient_table(imprt) + if not line_no: + raise BadRequest("File with 0 lines.") + imprt.source_count = line_no + imprt.loaded = True + db.session.commit() + return jsonify(imprt.as_dict()) + + +@blueprint.route("//imports//columns", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def get_import_columns_name(scope, imprt): + """ + .. :quickref: Import; + + Return all the columns of the file of an import + """ + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.columns: + raise Conflict(description="Data have not been decoded.") + return jsonify(imprt.columns) + + +@blueprint.route("//imports//values", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def get_import_values(scope, imprt): + """ + .. :quickref: Import; + + Return all values present in imported file for nomenclated fields + """ + # check that the user has read permission to this particular import instance: + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.loaded: + raise Conflict(description="Data have not been loaded") + nomenclated_fields = db.session.scalars( + select(BibFields) + .where(BibFields.mnemonique != None, BibFields.destination == imprt.destination) + .options(joinedload(BibFields.nomenclature_type)) + .order_by(BibFields.id_field) + ).all() + # Note: response format is validated with jsonschema in tests + transient_table = imprt.destination.get_transient_table() + response = {} + for field in nomenclated_fields: + if field.name_field not in imprt.fieldmapping: + # this nomenclated field is not mapped + continue + source = imprt.fieldmapping[field.name_field] + if source not in imprt.columns: + # the file do not contain this field expected by the mapping + continue + # TODO: vérifier que l’on a pas trop de valeurs différentes ? + column = field.source_column + values = [ + value + for value, in db.session.execute( + select(transient_table.c[column]) + .where(transient_table.c.id_import == imprt.id_import) + .distinct(transient_table.c[column]) + ).fetchall() + ] + set_committed_value( + field.nomenclature_type, + "nomenclatures", + TNomenclatures.query.filter_by(nomenclature_type=field.nomenclature_type).order_by( + collate(TNomenclatures.cd_nomenclature, "fr_numeric") + ), + ) + response[field.name_field] = { + "nomenclature_type": field.nomenclature_type.as_dict(), + "nomenclatures": [n.as_dict() for n in field.nomenclature_type.nomenclatures], + "values": values, + } + return jsonify(response) + + +@blueprint.route("//imports//contentmapping", methods=["POST"]) +@permissions.check_cruved_scope("C", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def set_import_content_mapping(scope, imprt): + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.dataset.active: + raise Forbidden("Le jeu de données est fermé.") + try: + ContentMapping.validate_values(request.json) + except ValueError as e: + raise BadRequest(*e.args) + imprt.contentmapping = request.json + clean_import(imprt, ImportStep.PREPARE) + db.session.commit() + return jsonify(imprt.as_dict()) + + +@blueprint.route("//imports//prepare", methods=["POST"]) +@permissions.check_cruved_scope("C", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def prepare_import(scope, imprt): + """ + Prepare data to be imported: apply all checks and transformations. + """ + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.dataset.active: + raise Forbidden("Le jeu de données est fermé.") + + # Check preconditions to execute this action + if not imprt.loaded: + raise Conflict("Field data must have been loaded before executing this action.") + + # Remove previous errors + clean_import(imprt, ImportStep.PREPARE) + + # Run background import checks + sig = do_import_checks.s(imprt.id_import) + task = sig.freeze() + imprt.task_id = task.task_id + db.session.commit() + sig.delay() + + return jsonify(imprt.as_dict()) + + +@blueprint.route("//imports//preview_valid_data", methods=["GET"]) +@permissions.check_cruved_scope("C", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def preview_valid_data(scope, imprt): + """ + Preview valid data for a given import. + + Parameters + ---------- + scope : int + The scope of the (C, "IMPORT", "IMPORT") permission for the current user. + imprt : geonature.core.imports.models.TImports + The import object. + Returns + ------- + flask.wrappers.Response + A JSON response containing valid data, entities, columns, and data statistics. + Raises + ------ + Forbidden + If the current user has no sufficient permission given the scope and the import object. + Conflict + If the import is not processed, i.e. it has not been prepared yet. + """ + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.processed: + raise Conflict("Import must have been prepared before executing this action.") + + data = { + "valid_bbox": imprt.destination.actions.compute_bounding_box(imprt), + "entities": [], + } + + # Retrieve data for each entity from entries in the transient table which are related to the import + transient_table = imprt.destination.get_transient_table() + entities = db.session.scalars( + select(Entity).filter_by(destination=imprt.destination).order_by(Entity.order) + ).all() + + for entity in entities: + fields = ( + db.session.scalars( + select(BibFields).where( + BibFields.entities.any(EntityField.entity == entity), + BibFields.dest_field != None, + BibFields.name_field.in_(imprt.fieldmapping.keys()), + ) + ) + .unique() + .all() + ) + columns = [{"prop": field.dest_column, "name": field.name_field} for field in fields] + columns_to_count_unique_entities = [ + transient_table.c[field.dest_column] for field in fields + ] + + valid_data = db.session.execute( + select(*[transient_table.c[field.dest_field] for field in fields]) + .distinct() + .where( + transient_table.c.id_import == imprt.id_import, + transient_table.c[entity.validity_column] == True, + ) + .limit(100) + ).all() + + n_valid_data = db.session.execute( + select(func.count(func.distinct(*columns_to_count_unique_entities))) + .select_from(transient_table) + .where( + transient_table.c.id_import == imprt.id_import, + transient_table.c[entity.validity_column] == True, + ) + ).scalar() + + n_invalid_data = db.session.execute( + select(func.count(func.distinct(*columns_to_count_unique_entities))) + .select_from(transient_table) + .where( + transient_table.c.id_import == imprt.id_import, + transient_table.c[entity.validity_column] == False, + ) + ).scalar() + data["entities"].append( + { + "entity": entity.as_dict(), + "columns": columns, + "valid_data": valid_data, + "n_valid_data": n_valid_data, + "n_invalid_data": n_invalid_data, + } + ) + return jsonify(data) + + +@blueprint.route("//imports//errors", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def get_import_errors(scope, imprt): + """ + .. :quickref: Import; Get errors of an import. + + Get errors of an import. + """ + if not imprt.has_instance_permission(scope, action_code="R"): + raise Forbidden + return jsonify([error.as_dict(fields=["type", "entity"]) for error in imprt.errors]) + + +@blueprint.route("//imports//source_file", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def get_import_source_file(scope, imprt): + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if imprt.source_file is None: + raise Gone + return send_file( + BytesIO(imprt.source_file), + download_name=imprt.full_file_name, + as_attachment=True, + mimetype=f"text/csv; charset={imprt.encoding}; header=present", + ) + + +@blueprint.route("//imports//invalid_rows", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def get_import_invalid_rows_as_csv(scope, imprt): + """ + .. :quickref: Import; Get invalid rows of an import as CSV. + + Export invalid data in CSV. + """ + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.processed: + raise Conflict("Import must have been prepared before executing this action.") + + filename = imprt.full_file_name.rsplit(".", 1)[0] # remove extension + filename = f"{filename}_errors.csv" + + @stream_with_context + def generate_invalid_rows_csv(): + sourcefile = TextIOWrapper(BytesIO(imprt.source_file), encoding=imprt.encoding) + destfile = StringIO() + csvreader = csv.reader(sourcefile, delimiter=imprt.separator) + csvwriter = csv.writer(destfile, dialect=csvreader.dialect, lineterminator="\n") + line_no = 1 + for row in csvreader: + # line_no == 1 → csv header + if line_no == 1 or line_no in imprt.erroneous_rows: + csvwriter.writerow(row) + destfile.seek(0) + yield destfile.read().encode(imprt.encoding) + destfile.seek(0) + destfile.truncate() + line_no += 1 + + response = current_app.response_class( + generate_invalid_rows_csv(), + mimetype=f"text/csv; charset={imprt.encoding}; header=present", + ) + try: + filename.encode("ascii") + except UnicodeEncodeError: + simple = unicodedata.normalize("NFKD", filename) + simple = simple.encode("ascii", "ignore").decode("ascii") + quoted = url_quote(filename, safe="") + names = {"filename": simple, "filename*": f"UTF-8''{quoted}"} + else: + names = {"filename": filename} + response.headers.set("Content-Disposition", "attachment", **names) + return response + + +@blueprint.route("//imports//import", methods=["POST"]) +@permissions.check_cruved_scope("C", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def import_valid_data(scope, imprt): + """ + .. :quickref: Import; Import the valid data. + + Import valid data in destination table. + """ + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.dataset.active: + raise Forbidden("Le jeu de données est fermé.") + if not imprt.processed: + raise Forbidden("L’import n’a pas été préalablement vérifié.") + transient_table = imprt.destination.get_transient_table() + if not db.session.execute( + select( + exists() + .where(transient_table.c.id_import == imprt.id_import) + .where(or_(*[transient_table.c[v] == True for v in imprt.destination.validity_columns])) + ) + ).scalar(): + raise BadRequest("Not valid data to import") + + clean_import(imprt, ImportStep.IMPORT) + + sig = do_import_in_destination.s(imprt.id_import) + task = sig.freeze() + imprt.task_id = task.task_id + db.session.commit() + sig.delay() + + return jsonify(imprt.as_dict()) + + +@blueprint.route("//imports//", methods=["DELETE"]) +@permissions.check_cruved_scope("D", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def delete_import(scope, imprt): + """ + .. :quickref: Import; Delete an import. + + Delete an import. + """ + if not imprt.has_instance_permission(scope, action_code="C"): + raise Forbidden + if not imprt.dataset.active: + raise Forbidden("Le jeu de données est fermé.") + ImportUserError.query.filter_by(imprt=imprt).delete() + transient_table = imprt.destination.get_transient_table() + db.session.execute( + delete(transient_table).where(transient_table.c.id_import == imprt.id_import) + ) + imprt.destination.actions.remove_data_from_destination(imprt) + db.session.delete(imprt) + db.session.commit() + return jsonify() + + +@blueprint.route("//export_pdf/", methods=["POST"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def export_pdf(scope, imprt): + """ + Downloads the report in pdf format + """ + if not imprt.has_instance_permission(scope, action_code="R"): + raise Forbidden + ctx = imprt.as_dict() + + ctx["map"] = request.form.get("map") + if ctx["map"] == "undefined": + ctx["map"] = None + + ctx["chart"] = request.form.get("chart") + url_list = [ + current_app.config["URL_APPLICATION"], + "#", + current_app.config["IMPORT"].get("MODULE_URL", "").replace("/", ""), + str(ctx["id_import"]), + "report", + ] + ctx["url"] = "/".join(url_list) + + ctx["statistics_formated"] = {} + for label_dict in ctx["destination"]["statistics_labels"]: + key = label_dict["value"] + if label_dict["key"] in ctx["statistics"]: + ctx["statistics_formated"][key] = ctx["statistics"][label_dict["key"]] + + pdf_file = generate_pdf_from_template("import_template_pdf.html", ctx) + return send_file( + BytesIO(pdf_file), + mimetype="application/pdf", + as_attachment=True, + download_name="rapport.pdf", + ) + + +def get_foreign_key_attr(obj, field: str): + """ + Go through a object path to find the class to order on + """ + elems = dict(inspect(obj).relationships.items()) + fields = field.split(".") + if len(fields) == 1: + return getattr(obj, fields[0], "") + else: + first_field = fields[0] + remaining_fields = ".".join(fields[1:]) + return get_foreign_key_attr(elems[first_field].mapper.class_, remaining_fields) + + +@blueprint.route("//report_plot/", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="IMPORT") +def report_plot(scope, imprt: TImports): + if not imprt.has_instance_permission(scope, action_code="R"): + raise Forbidden + return json.dumps(imprt.destination.actions.report_plot(imprt)) diff --git a/backend/geonature/core/imports/routes/mappings.py b/backend/geonature/core/imports/routes/mappings.py new file mode 100644 index 0000000000..aeb7831b39 --- /dev/null +++ b/backend/geonature/core/imports/routes/mappings.py @@ -0,0 +1,166 @@ +from flask import request, jsonify, current_app, g +from geonature.core.imports.schemas import MappingSchema +from werkzeug.exceptions import Forbidden, Conflict, BadRequest, NotFound +from sqlalchemy.orm.attributes import flag_modified +import sqlalchemy as sa + +from geonature.utils.env import db +from geonature.core.gn_permissions import decorators as permissions + +from geonature.core.imports.models import ( + MappingTemplate, + FieldMapping, + ContentMapping, +) + +from geonature.core.imports.blueprint import blueprint + + +@blueprint.url_value_preprocessor +def check_mapping_type(endpoint, values): + if current_app.url_map.is_endpoint_expecting(endpoint, "mappingtype"): + if values["mappingtype"] not in ["field", "content"]: + raise NotFound + values["mappingtype"] = values["mappingtype"].upper() + if current_app.url_map.is_endpoint_expecting(endpoint, "id_mapping"): + mapping = MappingTemplate.query.get_or_404(values.pop("id_mapping")) + if mapping.destination != values.pop("destination"): + raise NotFound + if mapping.type != values.pop("mappingtype"): + raise NotFound + values["mapping"] = mapping + + +@blueprint.route("//mappings/", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="MAPPING") +def list_mappings(destination, mappingtype, scope): + """ + .. :quickref: Import; Return all active named mappings. + + Return all active named (non-temporary) mappings. + + :param type: Filter mapping of the given type. + :type type: str + """ + mappings = ( + db.session.scalars( + sa.select(MappingTemplate).where( + MappingTemplate.destination == destination, + MappingTemplate.type == mappingtype, + MappingTemplate.active == True, + MappingTemplate.filter_by_scope(scope), + ) + ) + .unique() + .all() + ) + return jsonify(MappingSchema(many=True).dump(mappings)) + + +@blueprint.route("//mappings//", methods=["GET"]) +@permissions.check_cruved_scope("R", get_scope=True, module_code="IMPORT", object_code="MAPPING") +def get_mapping(mapping, scope): + """ + .. :quickref: Import; Return a mapping. + + Return a mapping. Mapping has to be active. + """ + if not mapping.public and not mapping.has_instance_permission(scope): + raise Forbidden + if mapping.active is False: + raise Forbidden(description="Mapping is not active.") + return jsonify(MappingSchema().dump(mapping)) + + +@blueprint.route("//mappings/", methods=["POST"]) +@permissions.check_cruved_scope("C", get_scope=True, module_code="IMPORT", object_code="MAPPING") +def add_mapping(destination, mappingtype, scope): + """ + .. :quickref: Import; Add a mapping. + """ + label = request.args.get("label") + if not label: + raise BadRequest("Missing label") + + # check if name already exists + if db.session.scalar( + sa.exists(MappingTemplate) + .where( + MappingTemplate.destination == destination, + MappingTemplate.type == mappingtype, + MappingTemplate.label == label, + ) + .select() + ): + raise Conflict(description="Un mapping de ce type portant ce nom existe déjà") + + MappingClass = FieldMapping if mappingtype == "FIELD" else ContentMapping + try: + MappingClass.validate_values(request.json) + except ValueError as e: + raise BadRequest(*e.args) + + mapping = MappingClass( + destination=destination, + type=mappingtype, + label=label, + owners=[g.current_user], + values=request.json, + ) + db.session.add(mapping) + db.session.commit() + return jsonify(mapping.as_dict()) + + +@blueprint.route("//mappings//", methods=["POST"]) +@permissions.check_cruved_scope("U", get_scope=True, module_code="IMPORT", object_code="MAPPING") +def update_mapping(mapping, scope): + """ + .. :quickref: Import; Update a mapping (label and/or content). + """ + if not mapping.has_instance_permission(scope): + raise Forbidden + + label = request.args.get("label") + if label: + # check if name already exists + template_exists = db.session.scalar( + sa.exists(MappingTemplate) + .where( + MappingTemplate.type == mapping.type, + MappingTemplate.label == label, + ) + .select() + ) + if template_exists: + raise Conflict(description="Un mapping de ce type portant ce nom existe déjà") + mapping.label = label + if request.is_json: + try: + mapping.validate_values(request.json) + except ValueError as e: + raise BadRequest(*e.args) + if mapping.type == "FIELD": + mapping.values.update(request.json) + elif mapping.type == "CONTENT": + for key, value in request.json.items(): + if key not in mapping.values: + mapping.values[key] = value + else: + mapping.values[key].update(value) + flag_modified(mapping, "values") # nested dict modification not detected by MutableDict + db.session.commit() + return jsonify(mapping.as_dict()) + + +@blueprint.route("//mappings//", methods=["DELETE"]) +@permissions.check_cruved_scope("D", get_scope=True, module_code="IMPORT", object_code="MAPPING") +def delete_mapping(mapping, scope): + """ + .. :quickref: Import; Delete a mapping. + """ + if not mapping.has_instance_permission(scope): + raise Forbidden + db.session.delete(mapping) + db.session.commit() + return "", 204 diff --git a/backend/geonature/core/imports/schemas.py b/backend/geonature/core/imports/schemas.py new file mode 100644 index 0000000000..7035f2e815 --- /dev/null +++ b/backend/geonature/core/imports/schemas.py @@ -0,0 +1,29 @@ +from geonature.utils.env import db, ma +from marshmallow import EXCLUDE + +from utils_flask_sqla.schema import SmartRelationshipsMixin + +from geonature.core.imports.models import Destination, FieldMapping, MappingTemplate +from geonature.core.gn_commons.schemas import ModuleSchema +from marshmallow import fields + + +class DestinationSchema(SmartRelationshipsMixin, ma.SQLAlchemyAutoSchema): + class Meta: + model = Destination + include_fk = True + load_instance = True + sqla_session = db.session + + module = ma.Nested(ModuleSchema) + + +class MappingSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = MappingTemplate + include_fk = True + load_instance = True + sqla_session = db.session + + cruved = fields.Dict() + values = fields.Dict() diff --git a/backend/geonature/core/imports/tasks.py b/backend/geonature/core/imports/tasks.py new file mode 100644 index 0000000000..c873b5f0f5 --- /dev/null +++ b/backend/geonature/core/imports/tasks.py @@ -0,0 +1,163 @@ +from datetime import datetime + +from flask import current_app +import sqlalchemy as sa +from sqlalchemy import func, select, delete +from sqlalchemy.dialects.postgresql import array_agg, aggregate_order_by +from celery.utils.log import get_task_logger + +from geonature.utils.env import db +from geonature.utils.celery import celery_app + +from geonature.core.notifications.utils import dispatch_notifications + +from geonature.core.imports.models import BibFields, Entity, EntityField, TImports +from geonature.core.imports.checks.sql import init_rows_validity, check_orphan_rows + + +logger = get_task_logger(__name__) + + +@celery_app.task(bind=True) +def do_import_checks(self, import_id): + """ + Verify the import data. + + Parameters + ---------- + import_id : int + The ID of the import to verify. + """ + logger.info(f"Starting verification of import {import_id}.") + imprt = db.session.get(TImports, import_id) + if imprt is None or imprt.task_id != self.request.id: + logger.warning("Task cancelled, doing nothing.") + return + + imprt.destination.actions.check_transient_data(self, logger, imprt) + + self.update_state(state="PROGRESS", meta={"progress": 1}) + + imprt = db.session.get(TImports, import_id, with_for_update={"of": TImports}) + if imprt is None or imprt.task_id != self.request.id: + logger.warning("Task cancelled, rollback changes.") + db.session.rollback() + else: + logger.info("All done, committing…") + transient_table = imprt.destination.get_transient_table() + imprt.processed = True + imprt.task_id = None + stmt = ( + select( + array_agg(aggregate_order_by(transient_table.c.line_no, transient_table.c.line_no)) + ) + .where( + sa.or_(*[transient_table.c[v] == False for v in imprt.destination.validity_columns]) + ) + .where(transient_table.c.id_import == imprt.id_import) + ) + imprt.erroneous_rows = db.session.execute(stmt).scalar() + db.session.commit() + + +@celery_app.task(bind=True) +def do_import_in_destination(self, import_id): + """ + Insert valid transient data into the destination of an import. + + Parameters + ---------- + import_id : int + The ID of the import to insert data into the destination. + """ + logger.info(f"Starting insertion in destination of import {import_id}.") + imprt = db.session.get(TImports, import_id) + if imprt is None or imprt.task_id != self.request.id: + logger.warning("Task cancelled, doing nothing.") + return + transient_table = imprt.destination.get_transient_table() + + # Copy valid transient data to destination + imprt.destination.actions.import_data_to_destination(imprt) + + count_entities = 0 + entities = db.session.scalars( + sa.select(Entity).filter_by(destination=imprt.destination).order_by(Entity.order) + ).all() + for entity in entities: + fields = db.session.scalars( + sa.select(BibFields).where( + BibFields.entities.any(EntityField.entity == entity), + BibFields.dest_field != None, + BibFields.name_field.in_(imprt.fieldmapping.keys()), + ) + ).all() + columns_to_count_unique_entities = [ + transient_table.c[field.dest_column] for field in fields + ] + n_valid_data = db.session.execute( + select(func.count(func.distinct(*columns_to_count_unique_entities))) + .select_from(transient_table) + .where(transient_table.c.id_import == imprt.id_import) + .where(transient_table.c[entity.validity_column] == True) + ).scalar() + count_entities += n_valid_data + imprt.statistics["import_count"] = count_entities + + # COUNT VALID ROW in SOURCE FILE + imprt.statistics["nb_line_valid"] = db.session.execute( + select(func.count("*")) + .select_from(transient_table) + .where(transient_table.c.id_import == imprt.id_import) + .where(sa.or_(*[transient_table.c[entity.validity_column] != False for entity in entities])) + ).scalar() + + # Clear transient data + db.session.execute( + delete(transient_table).where(transient_table.c.id_import == imprt.id_import) + ) + imprt.loaded = False + + imprt = db.session.get(TImports, import_id, with_for_update={"of": TImports}) + if imprt is None or imprt.task_id != self.request.id: + logger.warning("Task cancelled, rollback changes.") + db.session.rollback() + return + + logger.info("All done, committing…") + imprt.task_id = None + imprt.date_end_import = datetime.now() + + # Send element to notification system + notify_import_done(imprt) + + db.session.commit() + + +# Send notification +def notify_import_done(imprt: TImports): + """ + Notify the authors of an import that it has finished. + + Parameters + ---------- + imprt : TImports + The import that has finished. + + """ + id_authors = [author.id_role for author in imprt.authors] + dispatch_notifications( + code_categories=["IMPORT-DONE%"], + id_roles=id_authors, + title="Import terminé", + url=( + current_app.config["URL_APPLICATION"] + + f"/#/import/{imprt.destination.code}/{imprt.id_import}/report" + ), + context={ + "import": imprt, + "destination": imprt.destination, + "url_notification_rules": current_app.config["URL_APPLICATION"] + + "/#/notification/rules", + }, + ) diff --git a/backend/geonature/core/imports/templates/__init__.py b/backend/geonature/core/imports/templates/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/geonature/core/imports/templates/import_template_pdf.html b/backend/geonature/core/imports/templates/import_template_pdf.html new file mode 100644 index 0000000000..467849f33b --- /dev/null +++ b/backend/geonature/core/imports/templates/import_template_pdf.html @@ -0,0 +1,302 @@ + + + + + Export rapport d'import + + + Bandeau + + + + +
+

Rapport de l'import n°{{ data.id_import }}

+

+ Fichier : {{ data.full_file_name }} - Date d'import : {{ data.date_end_import or "en cours" + }} +

+
+
+
+
+
Fiche Descriptive
+
+
    + {% for label, key in {"Auteur:": "authors_name", "SRID:": "srid", "Encodage:": "encoding", + "Format:": "format_source_file"}.items() %} +
  • + {{label}} + : {{data[key]}} +
  • + + {% endfor %} + {% if data.dataset: %} +
  • Jeu de données : {{ data.dataset.dataset_name }}
  • + {% endif %} +
+ {% if data.keywords: %} +
+

Mots-clés

+

{{ data.keywords }}

+
+ {% endif %} +
+
+ + {% if data.processed %} +
+
Zone géographique
+ +
+ +

+
+
+ {% endif %} + + + {% if data.date_end_import is not none %} +
+
+ Statistiques +
+
+ + + + + + + + + {% for key, value in data.statistics_formated.items() %} + + + + + {% endfor %} + +
ChampsValeur
{{ key|capitalize }}{{ value }}
+ +
+ +
+ +
+ +
+ {% endif %} + {% if data.processed %} +
+
+ Erreurs +
+
+ + + + + + + + + + + {% for error in data.errors %} + + + + + + + {% endfor %} + +
Type d'erreurChampNombre d'erreur(s)Entité
{{ error.type.description }}{{ error.column }}{{ error.rows | length }}{{ error.entity.label if error.entity else "" }}
+
+
+ + {% endif %} + + +
+ + Voir le rapport dans + le module d'import + + {{data.date_end_import or 'en cours'}} +
+ + + diff --git a/backend/geonature/core/imports/utils.py b/backend/geonature/core/imports/utils.py new file mode 100644 index 0000000000..0fb57743a9 --- /dev/null +++ b/backend/geonature/core/imports/utils.py @@ -0,0 +1,627 @@ +import os +from io import BytesIO, TextIOWrapper +import csv +import json +from enum import IntEnum +from datetime import datetime, timedelta +from typing import IO, Any, Dict, Iterable, List, Optional, Set, Tuple + +from flask import current_app, render_template +import sqlalchemy as sa +from sqlalchemy import func, select, delete +from chardet.universaldetector import UniversalDetector +from sqlalchemy.sql.expression import select, insert +import pandas as pd +import numpy as np +from sqlalchemy.dialects.postgresql import insert as pg_insert +from werkzeug.exceptions import BadRequest +from geonature.utils.env import db +from weasyprint import HTML + +from geonature.utils.sentry import start_sentry_child +from geonature.core.imports.models import Entity, ImportUserError, BibFields, TImports + + +class ImportStep(IntEnum): + UPLOAD = 1 + DECODE = 2 + LOAD = 3 + PREPARE = 4 + IMPORT = 5 + + +generated_fields = { + "datetime_min": "date_min", + "datetime_max": "date_max", +} + + +def clean_import(imprt: TImports, step: ImportStep) -> None: + """ + Clean an import at a specific step. + + Parameters + ---------- + imprt : TImports + The import to clean. + step : ImportStep + The step at which to clean the import. + + """ + imprt.task_id = None + if step <= ImportStep.UPLOAD: + # source_file will be necessary overwritten + # source_count will be necessary overwritten + pass + if step <= ImportStep.DECODE: + imprt.columns = None + if step <= ImportStep.LOAD: + transient_table = imprt.destination.get_transient_table() + stmt = delete(transient_table).where(transient_table.c.id_import == imprt.id_import) + with start_sentry_child(op="task", description="clean transient data"): + db.session.execute(stmt) + imprt.source_count = None + imprt.loaded = False + if step <= ImportStep.PREPARE: + with start_sentry_child(op="task", description="clean errors"): + ImportUserError.query.filter(ImportUserError.imprt == imprt).delete() + imprt.erroneous_rows = None + imprt.processed = False + if step <= ImportStep.IMPORT: + imprt.date_end_import = None + + imprt.statistics = {"import_count": None} + imprt.destination.actions.remove_data_from_destination(imprt) + + +def get_file_size(file_: IO) -> int: + """ + Get the size of a file in bytes. + + Parameters + ---------- + file_ : IO + The file to get the size of. + + Returns + ------- + int + The size of the file in bytes. + + """ + current_position = file_.tell() + file_.seek(0, os.SEEK_END) + size = file_.tell() + file_.seek(current_position) + return size + + +def detect_encoding(file_: IO) -> str: + """ + Detects the encoding of a file. + + Parameters + ---------- + file_ : IO + The file to detect the encoding of. + + Returns + ------- + str + The detected encoding. If no encoding is detected, then "UTF-8" is returned. + + """ + begin = datetime.now() + max_duration = timedelta( + seconds=current_app.config["IMPORT"]["MAX_ENCODING_DETECTION_DURATION"] + ) + position = file_.tell() + file_.seek(0) + detector = UniversalDetector() + for row in file_: + detector.feed(row) + if detector.done or (datetime.now() - begin) > max_duration: + break + detector.close() + file_.seek(position) + return detector.result["encoding"] or "UTF-8" + + +def detect_separator(file_: IO, encoding: str) -> Optional[str]: + """ + Detects the delimiter used in a CSV file. + + Parameters + ---------- + file_ : IO + The file object to detect the delimiter of. + encoding : str + The encoding of the file. + + Returns + ------- + Optional[str] + The delimiter used in the file, or None if no delimiter is detected. + + Raises + ------ + BadRequest + If the file starts with no column names. + + """ + position = file_.tell() + file_.seek(0) + try: + sample = file_.readline().decode(encoding) + except UnicodeDecodeError: + # encoding is likely to be detected encoding, so prompt to errors + return None + if sample == "\n": # files that do not start with column names + raise BadRequest("File must start with columns") + dialect = csv.Sniffer().sniff(sample) + file_.seek(position) + return dialect.delimiter + + +def preprocess_value(dataframe: pd.DataFrame, field: BibFields, source_col: str) -> pd.Series: + """ + Preprocesses values in a DataFrame depending if the field contains multiple values (e.g. additional_data) or not. + + Parameters + ---------- + dataframe : pd.DataFrame + The DataFrame to preprocess the value of. + field : BibFields + The field to preprocess. + source_col : str + The column to preprocess. + + Returns + ------- + pd.Series + The preprocessed value. + + """ + + def build_additional_data(columns: dict): + result = {} + for key, value in columns.items(): + if value is None: + continue + try: + value = json.loads(value) + assert type(value) is dict + except Exception: + value = {key: value} + result.update(value) + return result + + if field.multi: + assert type(source_col) is list + col = dataframe[source_col].apply(build_additional_data, axis=1) + else: + col = dataframe[source_col] + return col + + +def insert_import_data_in_transient_table(imprt: TImports) -> int: + """ + Insert the data from the import file into the transient table. + + Parameters + ---------- + imprt : TImports + current import + + Returns + ------- + int + The last line number of the import file that was inserted. + """ + transient_table = imprt.destination.get_transient_table() + + columns = imprt.columns + fieldmapping, used_columns = build_fieldmapping(imprt, columns) + extra_columns = set(columns) - set(used_columns) + + csvfile = TextIOWrapper(BytesIO(imprt.source_file), encoding=imprt.encoding) + reader = pd.read_csv( + csvfile, + delimiter=imprt.separator, + header=0, + names=imprt.columns, + index_col=False, + dtype="str", + na_filter=False, + iterator=True, + chunksize=10000, + ) + for chunk in reader: + chunk.replace({"": None}, inplace=True) + data = { + "id_import": np.full(len(chunk), imprt.id_import), + "line_no": 1 + 1 + chunk.index, # header + start line_no at 1 instead of 0 + } + data.update( + { + dest_field: preprocess_value(chunk, source_field["field"], source_field["value"]) + for dest_field, source_field in fieldmapping.items() + } + ) + # XXX keep extra_fields in t_imports_synthese? or add config argument? + if extra_columns and "extra_fields" in transient_table.c: + data.update( + { + "extra_fields": chunk[list(extra_columns)].apply( + lambda cols: {k: v for k, v in cols.items()}, axis=1 + ), + } + ) + df = pd.DataFrame(data) + + imprt.destination.actions.preprocess_transient_data(imprt, df) + + records = df.to_dict(orient="records") + db.session.execute(insert(transient_table).values(records)) + + return 1 + chunk.index[-1] # +1 because chunk.index start at 0 + + +def build_fieldmapping( + imprt: TImports, columns: Iterable[Any] +) -> Tuple[Dict[str, Dict[str, Any]], List[str]]: + """ + Build a dictionary that maps the source column names to the corresponding field and values. + + Parameters + ---------- + imprt : TImports + The import to check. + columns : Iterable[Any] + The columns to map. + + Returns + ------- + tuple + A tuple containing a dictionary that maps the source column names to the corresponding field and values, + and a list of the used columns. + + """ + fields = BibFields.query.filter_by(destination=imprt.destination, autogenerated=False).all() + fieldmapping = {} + used_columns = [] + + for field in fields: + if field.name_field in imprt.fieldmapping: + if field.multi: + correct = list(set(columns) & set(imprt.fieldmapping[field.name_field])) + if len(correct) > 0: + fieldmapping[field.source_column] = { + "value": correct, + "field": field, + } + used_columns.extend(correct) + else: + if imprt.fieldmapping[field.name_field] in columns: + fieldmapping[field.source_column] = { + "value": imprt.fieldmapping[field.name_field], + "field": field, + } + used_columns.append(imprt.fieldmapping[field.name_field]) + return fieldmapping, used_columns + + +def load_transient_data_in_dataframe( + imprt: TImports, entity: Entity, source_cols: list, offset: int = None, limit: int = None +): + """ + Load data from the transient table into a pandas dataframe. + + Parameters + ---------- + imprt : TImports + The import to load. + entity : Entity + The entity to load. + source_cols : list + The columns to load from the transient table. + offset : int, optional + The number of rows to skip. + limit : int, optional + The maximum number of rows to load. + + Returns + ------- + pandas.DataFrame + The dataframe containing the loaded data. + """ + transient_table = imprt.destination.get_transient_table() + source_cols = ["id_import", "line_no", entity.validity_column] + source_cols + stmt = ( + select(*[transient_table.c[col] for col in source_cols]) + .where( + transient_table.c.id_import == imprt.id_import, + transient_table.c[entity.validity_column].isnot(None), + ) + .order_by(transient_table.c.line_no) + ) + if offset is not None: + stmt = stmt.offset(offset) + if limit is not None: + stmt = stmt.limit(limit) + records = db.session.execute(stmt).fetchall() + df = pd.DataFrame.from_records( + records, + columns=source_cols, + ).astype("object") + return df + + +def update_transient_data_from_dataframe( + imprt: TImports, entity: Entity, updated_cols: Set[str], dataframe: pd.DataFrame +): + """ + Update the transient table with the data from the dataframe. + + Parameters + ---------- + imprt : TImports + The import to update. + entity : Entity + The entity to update. + updated_cols : list + The columns to update. + df : pandas.DataFrame + The dataframe to use for the update. + + Notes + ----- + The dataframe must have the columns 'id_import' and 'line_no'. + """ + if not updated_cols: + return + transient_table = imprt.destination.get_transient_table() + updated_cols = ["id_import", "line_no"] + list(updated_cols) + dataframe.replace({np.nan: None}, inplace=True) + records = dataframe[updated_cols].to_dict(orient="records") + insert_stmt = pg_insert(transient_table) + insert_stmt = insert_stmt.values(records).on_conflict_do_update( + index_elements=updated_cols[:2], + set_={col: insert_stmt.excluded[col] for col in updated_cols[2:]}, + ) + db.session.execute(insert_stmt) + + +def generate_pdf_from_template(template: str, data: Any) -> bytes: + """ + Generate a PDF document from a template. + + Parameters + ---------- + template : str + The name of the template file to use. + data : Any + The data to pass to the template. + + Returns + ------- + bytes + The PDF document as bytes. + """ + template_rendered = render_template(template, data=data) + html_file = HTML( + string=template_rendered, + base_url=current_app.config["API_ENDPOINT"], + encoding="utf-8", + ) + return html_file.write_pdf() + + +def get_mapping_data(import_: TImports, entity: Entity): + """ + Get the mapping data for a given import and entity. + + Parameters + ---------- + import_ : TImports + The import to get the mapping data for. + entity : Entity + The entity to get the mapping data for. + + Returns + ------- + fields : dict + A dictionary with the all fields associated with an entity (check gn_imports.bib_fields). This dictionary is keyed by the name field and valued by the corresponding BibField object. + selected_fields : dict + In the same format as fields, but only the fields contained in the mapping. + source_cols : list + List of fields to load in dataframe, mainly source column of non-nomenclature fields + """ + fields = {ef.field.name_field: ef.field for ef in entity.fields} + selected_fields = { + field_name: fields[field_name] + for field_name, source_field in import_.fieldmapping.items() + if source_field in import_.columns and field_name in fields + } + source_cols = set() + for field in selected_fields.values(): + # load source col of all non-nomenclature fields + if field.mnemonique is None and field.source_field is not None: + source_cols |= {field.source_field} + # load source col of all mandatory fields + if field.mandatory: + source_cols |= {field.source_field} + # load all selected field used in conditions + conditions = set(field.mandatory_conditions or {}) | set(field.optional_conditions or {}) + if conditions: + source_cols |= set( + [selected_fields[f].source_field for f in conditions if f in selected_fields] + ) + return fields, selected_fields, list(source_cols) + + +def get_required(import_: TImports, entity: Entity): + fields, selected_fields, _ = get_mapping_data(import_, entity) + required_columns = set([]) + for field, bib_field in fields.items(): + if bib_field.mandatory and field in selected_fields: + required_columns.add(field) + + for field, bib_field in selected_fields.items(): + if all([field_name in selected_fields for field_name in bib_field.required_conditions]): + required_columns.add(field) + + for field, bib_field in selected_fields.items(): + if all([field_name in selected_fields for field_name in bib_field.optional_conditions]): + required_columns.remove(field) + return required_columns + + +def compute_bounding_box( + imprt: TImports, + geom_entity_code: str, + geom_4326_field_name: str, + *, + child_entity_code: str = None, + transient_where_clause=None, + destination_where_clause=None +): + """ + Compute the bounding box of an entity with a geometry in the given import, based on its + entities tree (e.g. Station -> Habitat; Site -> Visite -> Observation). + + Parameters + ---------- + imprt : TImports + The import to get the bounding box of. + geom_entity_code : str + The code of the entity that contains the geometry. + geom_4326_field_name : str + The name of the column in the geom entity table that contains the geometry. + child_entity_code : str, optional + The code of the last child entity (of the geom entity) to consider when computing the bounding box. If not given, + bounding-box will be computed only on the geom entity. + transient_where_clause : sqlalchemy.sql.elements.BooleanClauseList, optional + A where clause to apply to the query when computing the bounding box of a processed import. + destination_where_clause : sqlalchemy.sql.elements.BooleanClauseList, optional + A where clause to apply to the query when computing the bounding box of a finished import. + + Returns + ------- + valid_bbox : dict + The bounding box of all entities in the given import, in GeoJSON format. + """ + + def get_entities_hierarchy(parent_entity, child_entity) -> Iterable[Entity]: + """ + Get all entities between the parent_entity and the child_entity, in order from parent to child. + + Parameters + ---------- + parent_entity : Entity + The parent entity. + child_entity : Entity + The child entity. + + Yields + ------ + Entity + The entities between the parent and child entity, in order from parent to child. + """ + current = child_entity + while current != parent_entity and current: + yield current + current = current.parent + + parent_entity: Entity = db.session.execute( + sa.select(Entity).filter_by(destination=imprt.destination, code=geom_entity_code) + ).scalar_one() + parent_table = parent_entity.get_destination_table() + transient_table = imprt.destination.get_transient_table() + + # If only one entity in an import destination + if not child_entity_code: + if imprt.date_end_import: + table = parent_table + elif imprt.processed: + table = transient_table + else: + return None + + assert geom_4326_field_name in table.columns + query = select(func.ST_AsGeojson(func.ST_Extent(table.c[geom_4326_field_name]))).where( + table.c.id_import == imprt.id_import + ) + (valid_bbox,) = db.session.execute(query).fetchone() + if valid_bbox: + return json.loads(valid_bbox) + return None + + child_entity: Entity = db.session.execute( + sa.select(Entity).filter_by(destination=imprt.destination, code=child_entity_code) + ).scalar_one() + + # When the import is finished + if imprt.date_end_import: + entities = list(get_entities_hierarchy(parent_entity, child_entity)) + entities.reverse() + + # Geom entity linked to the current import based on their children (or grand-children, etc.) + query = sa.select(parent_table.c[geom_4326_field_name].label("geom")) + or_where_clause = [] + for entity in entities: + ent_table = entity.get_destination_table() + query = query.join(ent_table) + or_where_clause.append(ent_table.c.id_import == imprt.id_import) + query.where(sa.or_(*or_where_clause)) + + # Merge with geom entity with an id_import equal to the current import + query = sa.union( + query, + sa.select(parent_table.c[geom_4326_field_name].label("geom")).where( + parent_table.c.id_import == imprt.id_import + ), + ).subquery() + if destination_where_clause: + query = query.where(destination_where_clause) + + # When the import is processed (data are check and prepared but not loaded in the destination) + elif imprt.processed: + transient_table = imprt.destination.get_transient_table() + + # query all existing entities'geom + query = sa.select(parent_table.c[geom_4326_field_name].label("geom")).where( + transient_table.c.id_import == imprt.id_import, # basic ! + sa.and_( # Check that no parent entity is invalid + transient_table.c[entity.validity_column] != False + for entity in get_entities_hierarchy(parent_entity, child_entity) + ), + transient_table.c[parent_entity.validity_column] + == None, # Check that parent entity already exists in DB + transient_table.c[parent_entity.unique_column.dest_field] + == parent_table.c[parent_entity.unique_column.dest_field], # for join + ) + + # UNION between existing geom in the DB and new entities'geom in the transient table + query = sa.union( + query, + sa.select(transient_table.c[geom_4326_field_name].label("geom")).where( + sa.and_( + transient_table.c.id_import == imprt.id_import, + transient_table.c[parent_entity.validity_column] == True, + ) + ), + ).subquery() + + if transient_where_clause: + query = query.where(transient_where_clause) + + else: + return None + # Compute the bounding box using geom entities returned by the query + statement = select(func.ST_AsGeojson(func.ST_Extent(query.c.geom))) + (valid_bbox,) = db.session.execute(statement).fetchone() + + # If a valid bbox is found, return it + if valid_bbox: + return json.loads(valid_bbox) diff --git a/backend/geonature/core/sensitivity/routes.py b/backend/geonature/core/sensitivity/routes.py index 817938036d..b1f4776ae8 100644 --- a/backend/geonature/core/sensitivity/routes.py +++ b/backend/geonature/core/sensitivity/routes.py @@ -52,11 +52,10 @@ def info(): q = ( select( SensitivityRule.source, - func.count(SensitivityRule.id) - .where(SensitivityRule.active == True) - .label("active_count"), + func.count(SensitivityRule.id).label("active_count"), func.count(SensitivityRule.id).label("total_count"), ) + .where(SensitivityRule.active == True) .group_by(SensitivityRule.source) .order_by(SensitivityRule.source) ) @@ -70,6 +69,8 @@ def info(): select(func.count("*")) .select_from(SensitivityRule) .distinct(SensitivityRule.cd_nom) + .group_by(SensitivityRule.cd_nom) + .order_by(SensitivityRule.cd_nom) ) ) ) @@ -80,13 +81,15 @@ def info(): .select_from(SensitivityRule) .filter_by(active=True) .distinct(SensitivityRule.cd_nom) + .group_by(SensitivityRule.cd_nom) + .order_by(SensitivityRule.cd_nom) ) ) ) click.echo( "\tRègles actives extrapolées aux taxons enfants : {}".format( db.session.scalar( - func.count(func.distinct(SensitivityRuleCache.c.cd_nom)).label("count") + select(func.count(func.distinct(SensitivityRuleCache.c.cd_nom)).label("count")) ) ) ) diff --git a/backend/geonature/core/taxonomie/admin.py b/backend/geonature/core/taxonomie/admin.py new file mode 100644 index 0000000000..ef67814d64 --- /dev/null +++ b/backend/geonature/core/taxonomie/admin.py @@ -0,0 +1,92 @@ +import os +from apptax.admin.admin_view import ( + BibListesView, + TaxrefView, + TMediasView, + BibAttributsView, + BibThemesView, +) +from geonature.utils.env import db + +from apptax.admin.admin import adresses +from apptax.taxonomie.models import Taxref, BibListes, TMedias, BibAttributs, BibThemes +from geonature.core.admin.utils import CruvedProtectedMixin + + +class CruvedProtectedBibListesView(CruvedProtectedMixin, BibListesView): + module_code = "TAXHUB" + object_code = "LISTES" + extra_actions_perm = {".import_cd_nom_view": "C"} + + +class CruvedProtectedTaxrefView(CruvedProtectedMixin, TaxrefView): + module_code = "TAXHUB" + object_code = "TAXONS" + + +class CruvedProtectedTMediasView(CruvedProtectedMixin, TMediasView): + module_code = "TAXHUB" + object_code = "TAXONS" + + +class CruvedProtectedBibAttributsView(CruvedProtectedMixin, BibAttributsView): + module_code = "TAXHUB" + object_code = "ATTRIBUTS" + + +class CruvedProtectedBibThemes(CruvedProtectedMixin, BibThemesView): + module_code = "TAXHUB" + object_code = "THEMES" + + +def load_admin_views(app, admin): + static_folder = os.path.join(adresses.root_path, "static") + + admin.add_view( + CruvedProtectedTaxrefView( + Taxref, + db.session, + name="Taxref", + endpoint="taxons", + category="TaxHub", + static_folder=static_folder, + ) + ) + admin.add_view( + CruvedProtectedBibListesView( + BibListes, + db.session, + name="Listes", + category="TaxHub", + static_folder=static_folder, + ) + ) + + admin.add_view( + CruvedProtectedBibAttributsView( + BibAttributs, + db.session, + name="Attributs", + category="TaxHub", + static_folder=static_folder, + ) + ) + admin.add_view( + CruvedProtectedBibThemes( + BibThemes, + db.session, + name="Thèmes", + category="TaxHub", + static_folder=static_folder, + ) + ) + + admin.add_view( + CruvedProtectedTMediasView( + TMedias, + db.session, + name="Médias", + category="TaxHub", + static_folder=static_folder, + ) + ) diff --git a/backend/geonature/migrations/alembic.ini b/backend/geonature/migrations/alembic.ini index 98313afb09..bd04d3c99d 100644 --- a/backend/geonature/migrations/alembic.ini +++ b/backend/geonature/migrations/alembic.ini @@ -8,7 +8,7 @@ # the 'revision' command, regardless of autogenerate # revision_environment = false -version_locations = %(here)s/versions +version_locations = %(here)s/versions,%(here)s/versions/imports # Logging configuration diff --git a/backend/geonature/migrations/data/imports/__init__.py b/backend/geonature/migrations/data/imports/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/geonature/migrations/data/imports/data.sql b/backend/geonature/migrations/data/imports/data.sql new file mode 100644 index 0000000000..0c9b7dc1bd --- /dev/null +++ b/backend/geonature/migrations/data/imports/data.sql @@ -0,0 +1,196 @@ +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SET check_function_bodies = false; +SET client_min_messages = warning; +SET search_path = gn_imports, pg_catalog, public; +SET default_with_oids = false; + + +-------------- +--INSERTIONS-- +-------------- + +INSERT INTO gn_imports.t_user_errors (error_type,"name",description,error_level) VALUES +('Ouverture du fichier','NO_FILE_DETECTED','Aucun fichier détecté.','ERROR') +,('Erreur de format','INVALID_INTEGER','Format numérique entier incorrect ou négatif dans une des colonnes de type Entier.','ERROR') +,('Erreur de format','INVALID_DATE','Le format de date est incorrect dans une colonne de type Datetime. Le format attendu est YYYY-MM-DD ou DD-MM-YYYY (les heures sont acceptées sous ce format: HH:MM:SS) - Les séparateurs / . : sont également acceptés','ERROR') +,('Erreur de format','INVALID_UUID','L''identifiant permanent doit être un UUID valide, ou sa valeur doit être vide.','ERROR') +,('Erreur de format','INVALID_CHAR_LENGTH','Chaîne de caractères trop longue ; la longueur de la chaîne dépasse la longueur maximale autorisée.','ERROR') +,('Champ obligatoire','MISSING_VALUE','Valeur manquante dans un champs obligatoire','ERROR') +,('Incohérence','DATE_MIN_SUP_DATE_MAX','date_min > date_max','ERROR') +,('Incohérence','COUNT_MIN_SUP_COUNT_MAX','Incohérence entre les champs dénombrement. La valeur de denombrement_min est supérieure à celle de denombrement _max ou la valeur de denombrement _max est inférieur à denombrement_min.','ERROR') +,('Erreur de référentiel','CD_NOM_NOT_FOUND','Le Cd_nom renseigné ne peut être importé car il est absent du référentiel TAXREF ou de la liste de taxons importables configurée par l''administrateur','ERROR') +,('Erreur de référentiel','CD_HAB_NOT_FOUND','Le cdHab indiqué n’est pas dans le référentiel HABREF ; la valeur de cdHab n’a pu être trouvée dans la version courante du référentiel.','ERROR') +,('Erreur d''incohérence','ALTI_MIN_SUP_ALTI_MAX','altitude min > altitude max','ERROR') +,('Erreur d''incohérence','DEPTH_MIN_SUP_ALTI_MAX','profondeur min > profondeur max','ERROR') +,('Doublon','DUPLICATE_ENTITY_SOURCE_PK','Deux lignes du fichier ont la même clé primaire d’origine ; les clés primaires du fichier source ne peuvent pas être dupliquées.','ERROR') +,('Erreur de format','INVALID_REAL','Le format numérique réel est incorrect ou négatif dans une des colonnes de type REEL.','ERROR') +,('Géométrie','GEOMETRY_OUT_OF_BOX','Coordonnées géographiques en dehors du périmètre géographique de l''instance','ERROR') +,('Géométrie','PROJECTION_ERROR','Erreur de projection pour les coordonnées fournies','ERROR') +,('Doublon','EXISTING_UUID','L''identifiant SINP fourni existe déjà en base. Il faut en fournir une autre ou laisser la valeur vide pour une attribution automatique.','ERROR') +,('Erreur de référentiel','ID_DIGITISER_NOT_EXISITING','id_digitizer n''existe pas dans la table "t_roles"','ERROR') +,('Erreur de référentiel','INVALID_GEOM_CODE','Le code (maille/département/commune) n''existe pas dans le réferentiel géographique actuel','ERROR') +,('Nom du fichier','FILE_NAME_ERROR','Le nom de fichier ne comporte que des chiffres.','ERROR') +,('Doublon','DUPLICATE_ROWS','Deux lignes du fichier sont identiques ; les lignes ne peuvent pas être dupliquées.','ERROR') +,('Duplication','DUPLICATE_UUID','L''identificant sinp n''est pas unique dans le fichier fournis','ERROR') +,('Erreur de format','MULTIPLE_CODE_ATTACHMENT','Plusieurs codes de rattachement fournis pour une même ligne. Une ligne doit avoir un seul code rattachement (code commune OU code maille OU code département)','ERROR') +,('Ouverture du fichier','FILE_WITH_NO_DATA','Le fichier ne comporte aucune donnée.','ERROR') +,('Format du fichier','FILE_EXTENSION_ERROR','L''extension de fichier fournie n''est pas correct','ERROR') +,('Erreur de ligne sur le fichier','ROW_HAVE_LESS_COLUMN','Une ligne du fichier a moins de colonnes que l''en-tête.','ERROR') +,('Nom du fichier','FILE_NAME_TOO_LONG','Nom de fichier trop long ; la longueur du nom de fichier ne doit pas être supérieure à 100 caractères','ERROR') +,('Taille du fichier','FILE_OVERSIZE','La taille du fichier dépasse la taille du fichier autorisée ','ERROR') +,('Lecture du fichier','FILE_FORMAT_ERROR','Erreur de lecture des données ; le format du fichier est incorrect.','ERROR') +,('Lecture du fichier','ENCODING_ERROR','Erreur de lecture des données en raison d''un problème d''encodage.','ERROR') +,('En-tête du fichier','HEADER_COLUMN_EMPTY','Un des noms de colonne de l’en-tête est vide ; tous les noms de colonne doivent avoir une valeur.','ERROR') +,('En-tête du fichier','HEADER_SAME_COLUMN_NAME','Plusieurs colonnes de l''en-tête portent le même nom ; tous les noms de colonne de l''en-tête doivent être uniques.','ERROR') +,('Erreur de ligne sur le fichier','EMPTY_ROW','Une ligne du fichier est vide ; les lignes doivent avoir au moins une cellule non vide.','ERROR') +,('Erreur de ligne sur le fichier','ROW_HAVE_TOO_MUCH_COLUMN','Une ligne du fichier a plus de colonnes que l''en-tête.','ERROR') +,('Erreur de nomenclature','INVALID_NOMENCLATURE','Code nomenclature erroné ; La valeur du champ n’est pas dans la liste des codes attendus pour ce champ. Pour connaître la liste des codes autorisés, reportez-vous au Standard en cours.','ERROR') +,('Géométrie','INVALID_WKT','Géométrie invalide ; la valeur de la géométrie ne correspond pas au format WKT.','ERROR') +,('Géoréférencement','MISSING_GEOM','Géoréférencement manquant ; un géoréférencement doit être fourni, c’est à dire qu’il faut livrer : soit une géométrie, soit une ou plusieurs commune(s), ou département(s), ou maille(s), dont le champ “typeInfoGeo” est indiqué à 1.','ERROR') +,('Erreur de fichier','ERROR_WHILE_LOADING_FILE','Une erreur de chargement s''est produite, probablement à cause d''un mauvais séparateur dans le fichier.','ERROR') +,('Erreur de format','INVALID_URL_PROOF','PreuveNumerique n’est pas une url ; le champ “preuveNumérique” indique l’adresse web à laquelle on pourra trouver la preuve numérique ou l’archive contenant toutes les preuves numériques. Il doit commencer par “http://”, “https://”, ou “ftp://”.','ERROR') +,('Erreur de réferentiel','INVALID_ATTACHMENT_CODE','Le code commune/maille/département indiqué ne fait pas partie du référentiel des géographique; la valeur de codeCommune/codeMaille/codeDepartement n’a pu être trouvée dans la version courante du référentiel.','ERROR') +,('Géométrie','INVALID_GEOMETRY','Géométrie invalide','ERROR') +,('Géométrie','NO-GEOM','Aucune géometrie fournie (ni X/Y, WKT ou code)','ERROR') +,('Géométrie','GEOMETRY_OUTSIDE','La géométrie se trouve à l''extérieur du territoire renseigné','ERROR') +,('Géoréférencement','MULTIPLE_ATTACHMENT_TYPE_CODE','Plusieurs géoréférencements ; un seul géoréférencement doit être livré. Une seule des colonnes codeCommune/codeMaille/codeDépartement doit être remplie pour chaque ligne','ERROR') +,('Ouverture du fichier','NO_FILE_SENDED','Aucun fichier envoyé','ERROR') +,('Champ obligatoire conditionnel','CONDITIONAL_MANDATORY_FIELD_ERROR','Champs obligatoires conditionnels manquants. Il existe des ensembles de champs liés à un concept qui sont “obligatoires conditionnels”, c’est à dire que si l''un des champs du concept est utilisé, alors d''autres champs du concept deviennent obligatoires. ','ERROR') +,('Incohérence','CONDITIONAL_INVALID_DATA','Erreur de valeur','ERROR') +,('Incohérence','INVALID_EXISTING_PROOF_VALUE','Incohérence entre les champs de preuve ; si le champ “preuveExistante” vaut oui, alors l’un des deux champs “preuveNumérique” ou “preuveNonNumérique” doit être rempli. A l’inverse, si l’un de ces deux champs est rempli, alors “preuveExistante” ne doit pas prendre une autre valeur que “oui” (code 1).','ERROR') +,('Incohérence','INVALID_STATUT_SOURCE_VALUE','Référence bibliographique manquante ; si le champ “statutSource” a la valeur “Li” (Littérature), alors une référence bibliographique doit être indiquée.','ERROR') +,('Erreur','UNKNOWN_ERROR','','ERROR') +,('Ouverture du fichier','EMPTY_FILE','Le fichier fournit est vide','ERROR') +,('Avertissement de nomenclature','INVALID_NOMENCLATURE_WARNING','(Non bloquant) Code nomenclature erroné et remplacé par sa valeur par défaut ; La valeur du champ n’est pas dans la liste des codes attendus pour ce champ. Pour connaître la liste des codes autorisés, reportez-vous au Standard en cours.','WARNING') +; + + +INSERT INTO dict_themes (name_theme, fr_label_theme, eng_label_theme, desc_theme, order_theme) VALUES + ('general_info', 'Informations générales', '', '', 1), + ('statement_info', 'Informations de relevés', '', '', 2), + ('occurrence_sensitivity', 'Informations d''occurrences & sensibilité', '', '', 3), + ('enumeration', 'Dénombrements', '', '', 4), + ('validation', 'Détermination et validité', '', '', 5), + ('additional_data', 'Champs additionnels', '', '', 6); + + +INSERT INTO dict_fields (name_field, fr_label, eng_label, desc_field, type_field, synthese_field, mandatory, autogenerated, nomenclature, id_theme, order_field, display, comment) VALUES + ('entity_source_pk_value', 'Identifiant source', '', '', 'character varying', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='general_info'), 1, TRUE, 'Correspondance champs standard: identifiantOrigine'), + ('unique_id_sinp', 'Identifiant SINP (uuid)', '', '', 'uuid', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='general_info'), 2, TRUE, 'Correspondance champs standard: idSINPOccTax'), + ('unique_id_sinp_generate', 'Générer l''identifiant SINP', '', 'Génère automatiquement un identifiant de type uuid pour chaque observation', '', FALSE, FALSE, TRUE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='general_info'), 3, TRUE, NULL), + ('meta_create_date', 'Date de création de la donnée', '', '', 'timestamp without time zone', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='general_info'), 4, TRUE, NULL), + ('meta_v_taxref', 'Version du référentiel taxonomique', '', '', 'character varying(50)', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='general_info'), 5, FALSE , 'Correspondance champs standard: versionTAXREF'), + ('meta_update_date', 'Date de mise à jour de la donnée', '', '', 'timestamp without time zone', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='general_info'), 6, TRUE, NULL), + ('id_nomenclature_grp_typ', 'Type de relevé/regroupement', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 1, TRUE, 'Correspondance champs standard: typeRegroupement'), + ('unique_id_sinp_grp','Identifiant relevé (uuid)','','','uuid', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 2, TRUE, 'UUID du regroupement. Correspondance champs standard: identifiantRegroupementPermanent'), + ('date_min', 'Date début', '', '', 'timestamp without time zone', TRUE, TRUE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 3, TRUE, 'Date de début de l''observation. Le format attendu est YYYY-MM-DD ou DD-MM-YYYY (les heures sont acceptées sous ce format: HH:MM:SS) - Les séparateurs / . : sont également acceptés '), + ('date_max', 'Date fin', '', '', 'timestamp without time zone', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 4, TRUE, 'Date de fin de l''observation. Le format attendu est YYYY-MM-DD ou DD-MM-YYYY (les heures sont acceptées sous ce format: HH:MM:SS) - Les séparateurs / . : sont également acceptés'), + ('hour_min', 'Heure min', '', '', 'text', FALSE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 5, TRUE, 'Correspondance champs standard: heureDebut'), + ('hour_max', 'Heure max', '', '', 'text', FALSE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 6, TRUE, 'Correspondance champs standard: heureFin'), + ('altitude_min', 'Altitude min', '', '', 'integer', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 7, TRUE, 'Correspondance champs standard: altitudeMin'), + ('altitude_max', 'Altitude max', '', '', 'integer', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 8, TRUE, 'Correspondance champs standard: altitudeMax'), + ('altitudes_generate', 'Générer les altitudes', '', 'Génère automatiquement les altitudes pour chaque observation', '', FALSE, FALSE, TRUE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 7, TRUE, NULL), + ('depth_min', 'Profondeur min', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 9, TRUE, 'Correspondance champs standard: profondeurMin. Entier attendu'), + ('depth_max', 'Profondeur max', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 10, TRUE, 'Correspondance champs standard: profondeurMax. Entier attendu'), + ('observers', 'Observateur(s)', '', '', 'character varying(1000)', TRUE, TRUE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 11, TRUE, 'Correspondance champs standard: identiteObservateur. Format attendu : Nom Prénom (Organisme), Nom Prénom (Organisme)...' ), + ('comment_context', 'Commentaire de relevé', '', '', 'text', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 12, TRUE, 'Commentaire du relevé'), + ('id_nomenclature_info_geo_type', 'Type d''information géographique', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 13, TRUE, 'Correspondance champs standard: typeInfoGeo'), + ('longitude', 'Longitude (coord x)', '', '', 'real', FALSE, TRUE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 14, TRUE, NULL), + ('latitude', 'Latitude (coord y)', '', '', 'real', FALSE, TRUE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 15, TRUE, NULL), + ('WKT', 'Géometrie (WKT)', '', '', 'wkt', FALSE, TRUE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 16, TRUE, NULL), + ('codecommune', 'Code commune', '', '', 'integer', FALSE, TRUE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 17, TRUE, 'Correspondance champs standard: codeCommune. Code INSEE attendu'), + ('codemaille', 'Code maille', '', '', 'integer', FALSE, TRUE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 18, TRUE, 'Correspondance champs standard: codeMaille. Code maille-10 MNHN attendu'), + ('codedepartement', 'Code département', '', '', 'integer', FALSE, TRUE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 19, TRUE, 'Correspondance champs standard: codeDepartement. Code INSEE attendu'), + ('id_nomenclature_geo_object_nature', 'Nature d''objet géographique', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 20, TRUE, 'Correspondance champs standard: natureObjetGeo'), + ('the_geom_point','Geométrie (Point)','','','geometry', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 21, FALSE, NULL), + ('the_geom_local','Geométrie (SRID local)','','','geometry', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 22, FALSE, NULL), + ('the_geom_4326','Geométrie (SRID 4326)','','','geometry', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 23, FALSE, NULL), + ('place_name', 'Nom du lieu', '', '', 'character varying(500)', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 24, TRUE, 'Correspondance champs standard: nomLieu'), + ('precision', 'Précision du pointage (m)', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 25, TRUE, 'Correspondance champs standard: precisionGeometrie. Entier attendu'), + ('cd_hab', 'Code habitat', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 26, TRUE, 'Correspondance champs standard: CodeHabitatValue. Entier attendu'), + ('grp_method', 'Méthode de regroupement', '', '', 'character varying(255)', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='statement_info'), 27, TRUE, 'Correspondance champs standard: methodeRegroupement'), + ('nom_cite', 'Nom du taxon cité', '', '', 'character varying(1000)', TRUE, TRUE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 1, TRUE, 'Correspondance champs standard: nomCite'), + ('cd_nom', 'Cd nom taxref', '', '', 'integer', TRUE, TRUE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 2, TRUE, 'Code Taxref de l''espèce observé Correspondance champs standard: cdNom'), + ('id_nomenclature_obs_technique', 'Techniques d''observation', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 3, TRUE, 'Correspondance champs standard: obsTechnique'), + ('id_nomenclature_bio_status', 'Statut biologique', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 4, TRUE, 'Correspondance champs standard: occStatutBiologique'), + ('id_nomenclature_bio_condition', 'Etat biologique', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 5, TRUE, 'Correspondance champs standard: occEtatBiologique'), + ('id_nomenclature_biogeo_status', 'Statut biogéographique', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 6, TRUE, 'Correspondance champs standard: occStatutBiogeographique'), + ('id_nomenclature_naturalness', 'Naturalité', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 7, TRUE, 'Correspondance champs standard: occNaturalite'), + ('comment_description', 'Commentaire d''occurrence', '', '', 'text', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 8, TRUE, 'Commentaire de l''occurrence. Correspondance champs standard: comment '), + ('id_nomenclature_sensitivity', 'Sensibilité', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 9, TRUE, 'Sensibilité de l''observation. Correspondance champs standard: sensiNiveau'), + ('id_nomenclature_diffusion_level', 'Niveau de diffusion', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 10, TRUE, 'Correspondance champs standard: diffusionNiveauPrecision'), + ('id_nomenclature_blurring', 'Niveau de Floutage', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 11, TRUE, 'Correspondance champs standard: dEEFloutage'), + ('id_nomenclature_life_stage', 'Stade de vie', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='enumeration'), 1, TRUE, 'Correspondance champs standard: occStadeDeVie'), + ('id_nomenclature_sex', 'Sexe', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='enumeration'), 2, TRUE, 'Correspondance champs standard: occSexe'), + ('id_nomenclature_type_count', 'Type du dénombrement', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='enumeration'), 3, TRUE, 'Correspondance champs standard: typeDenombrement'), + ('id_nomenclature_obj_count', 'Objet du dénombrement', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='enumeration'), 4, TRUE, 'Correspondance champs standard: objetDenombrement'), + ('count_min', 'Nombre minimal', '', '', 'integer', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='enumeration'), 5, TRUE, 'Correspondance champs standard: denombrementMin. Entier attendu'), + ('count_max', 'Nombre maximal', '', '', 'integer', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='enumeration'), 6, TRUE, 'Correspondance champs standard: denombrementMax. Entier attendu'), + ('id_nomenclature_determination_method', 'Méthode de détermination', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='validation'), 1, TRUE, 'Correspondance champs standard: occMethodeDetermination'), + ('determiner', 'Déterminateur', '', '', 'character varying(1000)', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='validation'), 2, TRUE, 'Correspondance champs standard: determinateur. Format attendu : Nom Prénom (Organisme), Nom Prénom (Organisme)…"'), + ('id_digitiser', 'Identifiant de l''auteur de la saisie (id_role dans l''instance cible)', '', '', 'integer', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='validation'), 3, FALSE, 'Fournir un id_role GeoNature'), + ('id_nomenclature_exist_proof', 'Existence d''une preuve', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='validation'), 4, TRUE, 'Correspondance champs standard: preuveExistante'), + ('digital_proof', 'Preuve numérique', '', '', 'text', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='validation'), 5, TRUE, 'Correspondance champs standard: urlPreuveNumerique'), + ('non_digital_proof', 'Preuve non-numérique', '', '', 'text', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='validation'), 6, TRUE, 'Correspondance champs standard: preuveNonNumerique'), + ('id_nomenclature_valid_status', 'Statut de validation', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='validation'), 7, TRUE, 'Correspondance champs standard: nivVal. Validation producteur'), + ('validator', 'Validateur', '', '', 'character varying(1000)', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='validation'), 8, TRUE, 'Correspondance champs standard: validateur. Format attendu : Nom Prénom (Organisme), Nom Prénom (Organisme)...'), + ('meta_validation_date', 'Date de validation', '', '', 'timestamp without time zone', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='validation'), 9, TRUE, 'Correspondance champs standard: DateCtrl (Validation producteur)'), + ('validation_comment', 'Commentaire de validation', '', '', 'text', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='validation'), 10, TRUE, NULL), + ('id_nomenclature_observation_status', 'Statut d''observation', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 11, TRUE, 'Correspondance champs standard: statutObservation'), + ('id_nomenclature_source_status', 'Statut de la source', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 12, TRUE, 'Correspondance champs standard: statutSource'), + ('reference_biblio', 'Référence bibliographique', '', '', 'character varying(5000)', TRUE, FALSE, FALSE, FALSE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 13, TRUE, 'Correspondance champs standard: referenceBiblio'), + ('id_nomenclature_behaviour', 'Comportement', '', '', 'integer', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='occurrence_sensitivity'), 14, TRUE, 'Correspondance champs standard: occComportement'), + ('additional_data', 'Champs additionnels', '', '', 'jsonb', TRUE, FALSE, FALSE, TRUE, (SELECT id_theme FROM gn_imports.dict_themes WHERE name_theme='additional_data'), 1, TRUE, 'Attributs additionnels'); + -- Ajouter un thème dédié à terme et prévoir un widget multiselect qui concatène les infos sous format jsonb ? + + +INSERT INTO cor_synthese_nomenclature (mnemonique, synthese_col) VALUES + ('NAT_OBJ_GEO', 'id_nomenclature_geo_object_nature'), + ('DEE_FLOU', 'id_nomenclature_blurring'), + ('NIV_PRECIS', 'id_nomenclature_diffusion_level'), + ('OBJ_DENBR', 'id_nomenclature_obj_count'), + ('ETA_BIO', 'id_nomenclature_bio_condition'), + ('NATURALITE', 'id_nomenclature_naturalness'), + ('SEXE', 'id_nomenclature_sex'), + ('STADE_VIE', 'id_nomenclature_life_stage'), + ('STATUT_BIO', 'id_nomenclature_bio_status'), + ('METH_OBS', 'id_nomenclature_obs_technique'), + ('PREUVE_EXIST', 'id_nomenclature_exist_proof'), + ('SENSIBILITE', 'id_nomenclature_sensitivity'), + ('STATUT_OBS', 'id_nomenclature_observation_status'), + ('STATUT_SOURCE', 'id_nomenclature_source_status'), + ('TYP_DENBR', 'id_nomenclature_type_count'), + ('TYP_INF_GEO', 'id_nomenclature_info_geo_type'), + ('TYP_GRP', 'id_nomenclature_grp_typ'), + ('STATUT_VALID', 'id_nomenclature_valid_status'), + ('METH_DETERMIN', 'id_nomenclature_determination_method'), + ('OCC_COMPORTEMENT', 'id_nomenclature_behaviour'), + ('STAT_BIOGEO', 'id_nomenclature_biogeo_status') + ; + +----------------- +---PERMISSIONS--- +----------------- +DELETE FROM gn_permissions.t_permissions +WHERE id_object = (SELECT id_object FROM gn_permissions.t_objects WHERE code_object = 'MAPPING'); + +DELETE FROM gn_permissions.cor_object_module +WHERE id_object = (SELECT id_object FROM gn_permissions.t_objects WHERE code_object = 'MAPPING'); + +DELETE FROM gn_permissions.t_objects +WHERE code_object = 'MAPPING'; + +INSERT INTO gn_permissions.t_objects +(code_object, description_object) +VALUES('MAPPING', 'Représente un mapping dans le module d''import'); + +INSERT INTO gn_permissions.cor_object_module +(id_object, id_module) +VALUES( + (SELECT id_object FROM gn_permissions.t_objects WHERE code_object = 'MAPPING'), + (SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'IMPORT') +); + diff --git a/backend/geonature/migrations/data/imports/default_mappings_data.sql b/backend/geonature/migrations/data/imports/default_mappings_data.sql new file mode 100644 index 0000000000..5fc46c8051 --- /dev/null +++ b/backend/geonature/migrations/data/imports/default_mappings_data.sql @@ -0,0 +1,181 @@ +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SET check_function_bodies = false; +SET client_min_messages = warning; +SET search_path = gn_imports, pg_catalog, public; +SET default_with_oids = false; + + +-------------- +--INSERTIONS-- +-------------- + +-- Créer les mappings par défaut +INSERT INTO gn_imports.t_mappings (mapping_label, mapping_type, active, is_public) +VALUES +('Format DEE (champs 10 char)', 'FIELD', true, true), +('Synthese GeoNature', 'FIELD', true, true), +('Nomenclatures SINP (labels)', 'CONTENT', true, true), +('Nomenclatures SINP (codes)', 'CONTENT', true, true); + + + +-- Renseigner les correspondances de champs du mapping 'Format DEE' +INSERT INTO gn_imports.t_mappings_fields (id_mapping, source_field, target_field, is_selected, is_added) +VALUES +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'permid','unique_id_sinp',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'idorigine','entity_source_pk_value',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'permidgrp','unique_id_sinp_grp',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'true','unique_id_sinp_generate',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), '','meta_create_date',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'vtaxref','meta_v_taxref',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), '','meta_update_date',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'datedebut','date_min',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'datefin','date_max',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'heuredebut','hour_min',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'heurefin','hour_max',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'altmin','altitude_min',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'altmax','altitude_max',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'profmin','depth_min',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'profmax','depth_max',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), '','altitudes_generate',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'x_centroid','longitude',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'y_centroid','latitude',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'observer','observers',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'obsdescr','comment_description',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'typinfgeo','id_nomenclature_info_geo_type',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'typgrp','id_nomenclature_grp_typ',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'methgrp','grp_method',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'nomcite','nom_cite',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'cdnom','cd_nom',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'obstech','id_nomenclature_obs_technique',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'ocstatbio','id_nomenclature_bio_status',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'ocetatbio','id_nomenclature_bio_condition',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'ocbiogeo','id_nomenclature_biogeo_status',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'occcomport','id_nomenclature_behaviour',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'ocnat','id_nomenclature_naturalness',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'obsctx','comment_context',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'sensiniv','id_nomenclature_sensitivity',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'difnivprec','id_nomenclature_diffusion_level',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'deeflou','id_nomenclature_blurring',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'ocstade','id_nomenclature_life_stage',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'ocsex','id_nomenclature_sex',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'denbrtyp','id_nomenclature_type_count',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'objdenbr','id_nomenclature_obj_count',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'denbrmin','count_min',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'denbrmax','count_max',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'ocmethdet','id_nomenclature_determination_method',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'detminer','determiner',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'id_digitiser','id_digitiser',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'preuveoui','id_nomenclature_exist_proof',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'urlpreuv','digital_proof',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'preuvnonum','non_digital_proof',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'nivval','id_nomenclature_valid_status',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'validateur','validator',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'datectrl','meta_validation_date',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'validcom','validation_comment',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'natobjgeo','id_nomenclature_geo_object_nature',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'obstech','id_nomenclature_obs_technique',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'statobs','id_nomenclature_observation_status',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'statsource','id_nomenclature_source_status',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'refbiblio','reference_biblio',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'cdhab','cd_hab',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'geometrie','WKT',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'nomlieu','place_name',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'precisgeo','precision',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'gn_1_the_geom_point_2','the_geom_point',false,true), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'gn_1_the_geom_local_2','the_geom_local',false,true), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'gn_1_the_geom_4326_2','the_geom_4326',false,true), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'cdcommune','codecommune',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'cdmaille10','codemaille',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Format DEE (champs 10 char)'), 'cddept','codedepartement',true,false), + +-- Renseigner les correspondances de champs du mapping 'Synthese GeoNature' +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'uuid_perm_sinp','unique_id_sinp',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'id_synthese','entity_source_pk_value',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'uuid_perm_grp_sinp','unique_id_sinp_grp',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','unique_id_sinp_generate',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'date_creation','meta_create_date',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','meta_v_taxref',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'date_modification','meta_update_date',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'date_debut','date_min',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'date_fin','date_max',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'heure_debut','hour_min',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'heure_fin','hour_max',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'alti_min','altitude_min',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'alti_max','altitude_max',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'prof_min','depth_min',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'prof_max','depth_max',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','altitudes_generate',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','longitude',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','latitude',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'observateurs','observers',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'comment_occurrence','comment_description',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'type_info_geo','id_nomenclature_info_geo_type',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'type_regroupement','id_nomenclature_grp_typ',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'methode_regroupement','grp_method',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'nom_cite','nom_cite',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'cd_nom','cd_nom',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'technique_observation','id_nomenclature_obs_technique',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'biologique_statut','id_nomenclature_bio_status',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'etat_biologique','id_nomenclature_bio_condition',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'biogeographique_statut','id_nomenclature_biogeo_status',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'comportement','id_nomenclature_behaviour',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'naturalite','id_nomenclature_naturalness',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'comment_releve','comment_context',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'niveau_sensibilite','id_nomenclature_sensitivity',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'niveau_precision_diffusion','id_nomenclature_diffusion_level',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'floutage_dee','id_nomenclature_blurring',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'stade_vie','id_nomenclature_life_stage',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'sexe','id_nomenclature_sex',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'type_denombrement','id_nomenclature_type_count',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'objet_denombrement','id_nomenclature_obj_count',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'nombre_min','count_min',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'nombre_max','count_max',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'methode_determination','id_nomenclature_determination_method',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'determinateur','determiner',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','id_digitiser',false,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'preuve_existante','id_nomenclature_exist_proof',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'preuve_numerique_url','digital_proof',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'preuve_non_numerique','non_digital_proof',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'niveau_validation','id_nomenclature_valid_status',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'validateur','validator',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'date_validation','meta_validation_date',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'comment_validation','validation_comment',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'nature_objet_geo','id_nomenclature_geo_object_nature',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'statut_observation','id_nomenclature_observation_status',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'statut_source','id_nomenclature_source_status',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'reference_biblio','reference_biblio',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'cd_habref','cd_hab',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'geometrie_wkt_4326','WKT',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'nom_lieu','place_name',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), 'precision_geographique','precision',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','the_geom_point',false,true), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','the_geom_local',false,true), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','the_geom_4326',false,true), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','codecommune',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','codemaille',true,false), +((SELECT id_mapping FROM gn_imports.t_mappings WHERE mapping_label='Synthese GeoNature'), '','codedepartement',true,false) +; + +-- Intégration du mapping de valeurs SINP (labels) par défaut pour les nomenclatures de la synthèse +INSERT INTO gn_imports.t_mappings_values (id_mapping, source_value, id_target_value) +SELECT +m.id_mapping, +n.label_default, +n.id_nomenclature +FROM gn_imports.t_mappings m, ref_nomenclatures.t_nomenclatures n +JOIN ref_nomenclatures.bib_nomenclatures_types bnt ON bnt.id_type=n.id_type +WHERE m.mapping_label='Nomenclatures SINP (labels)' AND bnt.mnemonique IN (SELECT DISTINCT(mnemonique) FROM gn_imports.cor_synthese_nomenclature); + +-- Intégration du mapping de valeurs SINP (codes) par défaut pour les nomenclatures de la synthèse +INSERT INTO gn_imports.t_mappings_values (id_mapping, source_value, id_target_value) +SELECT +m.id_mapping, +n.cd_nomenclature, +n.id_nomenclature +FROM gn_imports.t_mappings m, ref_nomenclatures.t_nomenclatures n +JOIN ref_nomenclatures.bib_nomenclatures_types bnt ON bnt.id_type=n.id_type +WHERE m.mapping_label='Nomenclatures SINP (codes)' AND bnt.mnemonique IN (SELECT DISTINCT(mnemonique) FROM gn_imports.cor_synthese_nomenclature); diff --git a/backend/geonature/migrations/data/imports/sample_data.sql b/backend/geonature/migrations/data/imports/sample_data.sql new file mode 100644 index 0000000000..bc81e25297 --- /dev/null +++ b/backend/geonature/migrations/data/imports/sample_data.sql @@ -0,0 +1,1040 @@ +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SET check_function_bodies = false; +SET client_min_messages = warning; +--------- +--DATAS-- +--------- +-- Ajout des utilisateurs de test pour import +DO $$ +DECLARE id_organisme_admin INT; +id_organisme_agent INT; +BEGIN +SELECT id_organisme INTO id_organisme_admin +FROM utilisateurs.bib_organismes +WHERE nom_organisme = 'ma structure test'; +SELECT id_organisme into id_organisme_agent +FROM utilisateurs.bib_organismes +WHERE nom_organisme = 'Autre'; +-- Step 2: Insert data +INSERT INTO utilisateurs.t_roles ( + groupe, + identifiant, + nom_role, + prenom_role, + desc_role, + pass, + email, + date_insert, + date_update, + id_organisme, + remarques, + pass_plus + ) +VALUES ( + false, + 'admin-test-import', + 'Administrateur-test-import', + NULL, + NULL, + '21232f297a57a5a743894a0e4a801fc3', + -- MD5 of 'admin' + NULL, + NULL, + NULL, + id_organisme_admin, + 'utilisateur test à modifier', + '$2y$13$TMuRXgvIg6/aAez0lXLLFu0lyPk4m8N55NDhvLoUHh/Ar3rFzjFT.' + ), + ( + false, + 'agent-test-import', + 'Agent-test-import', + NULL, + NULL, + 'b33aed8f3134996703dc39f9a7c95783', + -- MD5 of 'agent' + NULL, + NULL, + NULL, + id_organisme_agent, + 'utilisateur test à modifier ou supprimer', + '$2b$12$ItumBAShoFbLe.vluoIlZOeVQPoR/rkaW4xRVuqx48npwEt.WMJYe' + ); +END $$; +INSERT INTO utilisateurs.cor_roles (id_role_groupe, id_role_utilisateur) +VALUES ( + ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE nom_role = 'Grp_admin' + ), + ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'admin-test-import' + ) + ); +-- Insert role agent-test-import into cor_role_app_profil +INSERT INTO utilisateurs.cor_role_app_profil (id_role, id_application, id_profil) +SELECT ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'agent-test-import' + ) AS id_role, + app.id_application, + profils.id_profil +FROM ( + SELECT id_application + FROM utilisateurs.t_applications + WHERE code_application = 'GN' + ) AS app, + ( + SELECT id_profil + FROM utilisateurs.t_profils + WHERE nom_profil LIKE 'Lecteur' + ) AS profils ON CONFLICT DO NOTHING; +-- Ajout des permissions aux utilisateurs de tests +-- Insert permissions for admin-test-import +INSERT INTO gn_permissions.t_permissions ( + id_role, + id_action, + id_module, + id_object, + scope_value + ) +SELECT tr.id_role, + ba.id_action, + tm.id_module, + t_o.id_object, + NULL AS scope_value +FROM ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'admin-test-import' + ) tr, + ( + SELECT id_action + FROM gn_permissions.bib_actions + WHERE code_action IN ('C', 'R', 'U', 'D') + ) ba, + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'IMPORT' + ) tm, + ( + SELECT id_object + FROM gn_permissions.t_objects + WHERE code_object IN ('IMPORT', 'MAPPING') + ) t_o; +-- Insert permissions for agent-test-import +INSERT INTO gn_permissions.t_permissions ( + id_role, + id_action, + id_module, + id_object, + scope_value + ) +SELECT tr.id_role, + ba.id_action, + tm.id_module, + t_o.id_object, + 1 AS scope_value +FROM ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'agent-test-import' + ) tr, + ( + SELECT id_action + FROM gn_permissions.bib_actions + WHERE code_action IN ('C', 'R', 'U', 'D') + ) ba, + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'IMPORT' + ) tm, + ( + SELECT id_object + FROM gn_permissions.t_objects + WHERE code_object IN ('IMPORT', 'MAPPING') + ) t_o; + +-- Insert permissions for agent-test-import +INSERT INTO gn_permissions.t_permissions ( + id_role, + id_action, + id_module, + id_object, + scope_value + ) +SELECT tr.id_role, + ba.id_action, + tm.id_module, + t_o.id_object, + 1 AS scope_value +FROM ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'agent-test-import' + ) tr, + ( + SELECT id_action + FROM gn_permissions.bib_actions + WHERE code_action IN ('C', 'R', 'U', 'D') + ) ba, + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'SYNTHESE' + ) tm, + ( + SELECT id_object + FROM gn_permissions.t_objects + WHERE code_object IN ('ALL') + ) t_o; + +-- Insert permissions for agent-test-import +INSERT INTO gn_permissions.t_permissions ( + id_role, + id_action, + id_module, + id_object, + scope_value + ) +SELECT tr.id_role, + ba.id_action, + tm.id_module, + t_o.id_object, + 1 AS scope_value +FROM ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'agent-test-import' + ) tr, + ( + SELECT id_action + FROM gn_permissions.bib_actions + WHERE code_action IN ('R') + ) ba, + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'METADATA' + ) tm, + ( + SELECT id_object + FROM gn_permissions.t_objects + WHERE code_object IN ('ALL') + ) t_o; + +-- Ajout du module occhab si non présent +INSERT INTO gn_commons.t_modules ( + module_code, + module_label, + module_path, + active_frontend, + active_backend, + ng_module + ) +SELECT 'OCCHAB', + 'Occhab', + 'occhab', + true, + false, + 'OCCHAB' +WHERE NOT EXISTS ( + SELECT 1 + FROM gn_commons.t_modules + WHERE module_code = 'OCCHAB' + ); +-- ajout des tables de destinations +INSERT INTO gn_imports.bib_destinations (id_module, code, "label", table_name) +SELECT ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'OCCHAB' + ), + 'occhab', + 'Occhab', + 't_imports_occhab' +WHERE NOT EXISTS ( + SELECT 1 + FROM gn_imports.bib_destinations + WHERE code = 'occhab' + ) +UNION ALL +SELECT ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'SYNTHESE' + ), + 'synthese', + 'Synthese', + 't_imports_synthese' +WHERE NOT EXISTS ( + SELECT 1 + FROM gn_imports.bib_destinations + WHERE code = 'synthese' + ); +---- Ajouter permissions disponibles pour les nouveau module +INSERT INTO gn_permissions.t_permissions_available ( + id_module, + id_object, + id_action, + label, + scope_filter + ) +SELECT m.id_module, + o.id_object, + a.id_action, + v.label, + v.scope_filter +FROM ( + VALUES ('OCCHAB', 'ALL', 'C', True, 'Créer des habitats'), + ('OCCHAB', 'ALL', 'R', True, 'Voir les habitats'), + ( + 'OCCHAB', + 'ALL', + 'U', + True, + 'Modifier les habitats' + ), + ( + 'OCCHAB', + 'ALL', + 'D', + True, + 'Supprimer des habitats' + ), + ( + 'OCCHAB', + 'ALL', + 'E', + True, + 'Exporter des habitats' + ) + ) AS v ( + module_code, + object_code, + action_code, + scope_filter, + label + ) + JOIN gn_commons.t_modules m ON m.module_code = v.module_code + JOIN gn_permissions.t_objects o ON o.code_object = v.object_code + JOIN gn_permissions.bib_actions a ON a.code_action = v.action_code ON CONFLICT DO NOTHING; +-- Insérer un cadre d'acquisition d'exemple +INSERT INTO gn_meta.t_acquisition_frameworks ( + unique_acquisition_framework_id, + acquisition_framework_name, + acquisition_framework_desc, + id_nomenclature_territorial_level, + territory_desc, + keywords, + id_nomenclature_financing_type, + target_description, + ecologic_or_geologic_target, + acquisition_framework_parent_id, + is_parent, + acquisition_framework_start_date, + acquisition_framework_end_date, + meta_create_date, + meta_update_date + ) +VALUES ( + '5b054340-210c-4350-9034-300543210c43', + 'CA-1-TEST-IMPORT', + 'CA-1-TEST-IMPORT', + ref_nomenclatures.get_id_nomenclature('NIVEAU_TERRITORIAL', '4'), + 'Territoire du Parc national des Ecrins correspondant au massif alpin des Ecrins', + 'Ecrins, parc national, faune, flore, fonge', + ref_nomenclatures.get_id_nomenclature('TYPE_FINANCEMENT', '1'), + 'Tous les taxons', + null, + null, + false, + '1973-03-27', + null, + '2018-09-01 10:35:08', + null + ); +; +INSERT INTO gn_meta.t_acquisition_frameworks ( + unique_acquisition_framework_id, + acquisition_framework_name, + acquisition_framework_desc, + id_nomenclature_territorial_level, + territory_desc, + keywords, + id_nomenclature_financing_type, + target_description, + ecologic_or_geologic_target, + acquisition_framework_parent_id, + is_parent, + acquisition_framework_start_date, + acquisition_framework_end_date, + meta_create_date, + meta_update_date + ) +VALUES ( + '7a2b3c4d-5e6f-4a3b-2c1d-e6f5a4b3c2d1', + 'CA-1-TEST-IMPORT-empty', + 'CA-1-TEST-IMPORT-empty', + ref_nomenclatures.get_id_nomenclature('NIVEAU_TERRITORIAL', '4'), + 'Test', + 'flore, fonge', + ref_nomenclatures.get_id_nomenclature('TYPE_FINANCEMENT', '1'), + 'Tous les taxons', + null, + null, + false, + '2002-03-27', + null, + '2022-09-01 10:35:08', + null + ); +-- Insérer 2 jeux de données d'exemple +INSERT INTO gn_meta.t_datasets ( + id_dataset, + unique_dataset_id, + id_acquisition_framework, + dataset_name, + dataset_shortname, + dataset_desc, + id_nomenclature_data_type, + keywords, + marine_domain, + terrestrial_domain, + id_nomenclature_dataset_objectif, + bbox_west, + bbox_east, + bbox_south, + bbox_north, + id_nomenclature_collecting_method, + id_nomenclature_data_origin, + id_nomenclature_source_status, + id_nomenclature_resource_type, + active, + validable, + meta_create_date, + meta_update_date + ) +VALUES ( + 9999, + '9f86d081-8292-466e-9e7b-16f3960d255f', + ( + SELECT id_acquisition_framework + FROM gn_meta.t_acquisition_frameworks + WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43' + ), + 'JDD-TEST-IMPORT-ADMIN', + 'Jeu de données - test import admin', + 'JDD-TEST-IMPORT-ADMIN', + ref_nomenclatures.get_id_nomenclature('DATA_TYP', '1'), + 'Aléatoire, hors protocole, faune, flore, fonge', + false, + true, + ref_nomenclatures.get_id_nomenclature('JDD_OBJECTIFS', '1.1'), + 4.85695, + 6.85654, + 44.5020, + 45.25, + ref_nomenclatures.get_id_nomenclature('METHO_RECUEIL', '1'), + ref_nomenclatures.get_id_nomenclature('DS_PUBLIQUE', 'Pu'), + ref_nomenclatures.get_id_nomenclature('STATUT_SOURCE', 'Te'), + ref_nomenclatures.get_id_nomenclature('RESOURCE_TYP', '1'), + true, + true, + '2018-09-01 16:57:44.45879', + null + ), + ( + 9998, + '2f543d86-ec4e-4f1a-b4d9-123456789abc', + ( + SELECT id_acquisition_framework + FROM gn_meta.t_acquisition_frameworks + WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43' + ), + 'JDD-TEST-IMPORT-2', + 'Jeu de données - test import 2', + 'Jeu de données - test import 2', + ref_nomenclatures.get_id_nomenclature('DATA_TYP', '1'), + 'Aléatoire, ATBI, biodiversité, faune, flore, fonge', + false, + true, + ref_nomenclatures.get_id_nomenclature('JDD_OBJECTIFS', '1.1'), + 4.85695, + 6.85654, + 44.5020, + 45.25, + ref_nomenclatures.get_id_nomenclature('METHO_RECUEIL', '1'), + ref_nomenclatures.get_id_nomenclature('DS_PUBLIQUE', 'Pu'), + ref_nomenclatures.get_id_nomenclature('STATUT_SOURCE', 'Te'), + ref_nomenclatures.get_id_nomenclature('RESOURCE_TYP', '1'), + true, + true, + '2018-09-01 16:59:03.25687', + null + ), + ( + 9997, + 'a1b2c3d4-e5f6-4a3b-2c1d-e6f5a4b3c2d1', + ( + SELECT id_acquisition_framework + FROM gn_meta.t_acquisition_frameworks + WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43' + ), + 'JDD-TEST-IMPORT-3', + 'Jeu de données - test import 3', + 'Jeu de données - test import 3', + ref_nomenclatures.get_id_nomenclature('DATA_TYP', '1'), + 'Aléatoire, hors protocole, faune, flore, fonge', + false, + true, + ref_nomenclatures.get_id_nomenclature('JDD_OBJECTIFS', '1.1'), + 4.85695, + 6.85654, + 44.5020, + 45.25, + ref_nomenclatures.get_id_nomenclature('METHO_RECUEIL', '1'), + ref_nomenclatures.get_id_nomenclature('DS_PUBLIQUE', 'Pu'), + ref_nomenclatures.get_id_nomenclature('STATUT_SOURCE', 'Te'), + ref_nomenclatures.get_id_nomenclature('RESOURCE_TYP', '1'), + true, + true, + '2018-09-01 16:57:44.45879', + null + ), + ( + 9996, + '5f45d560-1ce3-420c-b45c-3d589eedaee1', + ( + SELECT id_acquisition_framework + FROM gn_meta.t_acquisition_frameworks + WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43' + ), + 'JDD-TEST-IMPORT-INACTIF', + 'Jeu de données - test import inactif', + 'Jeu de données - test import inactif', + ref_nomenclatures.get_id_nomenclature('DATA_TYP', '1'), + 'Aléatoire, hors protocole, faune, flore, fonge', + false, + true, + ref_nomenclatures.get_id_nomenclature('JDD_OBJECTIFS', '1.1'), + 4.85695, + 6.85654, + 44.5020, + 45.25, + ref_nomenclatures.get_id_nomenclature('METHO_RECUEIL', '1'), + ref_nomenclatures.get_id_nomenclature('DS_PUBLIQUE', 'Pu'), + ref_nomenclatures.get_id_nomenclature('STATUT_SOURCE', 'Te'), + ref_nomenclatures.get_id_nomenclature('RESOURCE_TYP', '1'), + false, + true, + '2018-09-01 16:57:44.45879', + null + ); +-- ajout des JDD dans les modules IMPORT et IMPORT dupliqué +INSERT INTO gn_commons.cor_module_dataset (id_module, id_dataset) +VALUES ( + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'IMPORT' + ), + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f' + ) + ), + ( + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'IMPORT' + ), + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc' + ) + ), + ( + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'IMPORT' + ), + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = 'a1b2c3d4-e5f6-4a3b-2c1d-e6f5a4b3c2d1' + ) + ), + ( + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'OCCHAB' + ), + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f' + ) + ), + ( + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'SYNTHESE' + ), + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f' + ) + ), + ( + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'IMPORT' + ), + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '5f45d560-1ce3-420c-b45c-3d589eedaee1' + ) + ), + ( + ( + SELECT id_module + FROM gn_commons.t_modules + WHERE module_code = 'SYNTHESE' + ), + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc' + ) + ); +-- Renseigner les tables de correspondance +INSERT INTO gn_meta.cor_acquisition_framework_voletsinp ( + id_acquisition_framework, + id_nomenclature_voletsinp + ) +VALUES ( + ( + SELECT id_acquisition_framework + FROM gn_meta.t_acquisition_frameworks + WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43' + ), + ref_nomenclatures.get_id_nomenclature('VOLET_SINP', '1') + ); +INSERT INTO gn_meta.cor_acquisition_framework_objectif ( + id_acquisition_framework, + id_nomenclature_objectif + ) +VALUES ( + ( + SELECT id_acquisition_framework + FROM gn_meta.t_acquisition_frameworks + WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43' + ), + ref_nomenclatures.get_id_nomenclature('CA_OBJECTIFS', '8') + ); +INSERT INTO gn_meta.cor_acquisition_framework_actor ( + id_acquisition_framework, + id_role, + id_organism, + id_nomenclature_actor_role + ) +VALUES ( + ( + SELECT id_acquisition_framework + FROM gn_meta.t_acquisition_frameworks + WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43' + ), + NULL, + ( + SELECT id_organisme + FROM utilisateurs.bib_organismes + WHERE nom_organisme = 'ma structure test' + ), + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '1') + ), + ( + ( + SELECT id_acquisition_framework + FROM gn_meta.t_acquisition_frameworks + WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43' + ), + NULL, + ( + SELECT id_organisme + FROM utilisateurs.bib_organismes + WHERE nom_organisme = 'ma structure test' + ), + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '6') + ), + ( + ( + SELECT id_acquisition_framework + FROM gn_meta.t_acquisition_frameworks + WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43' + ), + NULL, + ( + SELECT id_organisme + FROM utilisateurs.bib_organismes + WHERE nom_organisme = 'ma structure test' + ), + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '8') + ); +INSERT INTO gn_meta.cor_dataset_actor ( + id_dataset, + id_role, + id_organism, + id_nomenclature_actor_role + ) +VALUES ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f' + ), + ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'admin-test-import' + ), + ( + SELECT id_organisme + FROM utilisateurs.bib_organismes + WHERE nom_organisme = 'ma structure test' + ), + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '1') + ), + ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f' + ), + NULL, + ( + SELECT id_organisme + FROM utilisateurs.bib_organismes + WHERE nom_organisme = 'ma structure test' + ), + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '6') + ), + ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc' + ), + NULL, + ( + SELECT id_organisme + FROM utilisateurs.bib_organismes + WHERE nom_organisme = 'ma structure test' + ), + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '1') + ), + ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc' + ), + NULL, + ( + SELECT id_organisme + FROM utilisateurs.bib_organismes + WHERE nom_organisme = 'ma structure test' + ), + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '6') + ), + ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc' + ), + ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'admin-test-import' + ), + NULL, + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '8') + ), + ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc' + ), + ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'agent-test-import' + ), + NULL, + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '5') + ), + ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc' + ), + NULL, + ( + SELECT id_organisme + FROM utilisateurs.bib_organismes + WHERE nom_organisme = 'Autre' + ), + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '6') + ), + ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = 'a1b2c3d4-e5f6-4a3b-2c1d-e6f5a4b3c2d1' + ), + ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'admin-test-import' + ), + ( + SELECT id_organisme + FROM utilisateurs.bib_organismes + WHERE nom_organisme = 'ma structure test' + ), + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '1') + ), + ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '5f45d560-1ce3-420c-b45c-3d589eedaee1' + ), + ( + SELECT id_role + FROM utilisateurs.t_roles + WHERE identifiant = 'admin-test-import' + ), + ( + SELECT id_organisme + FROM utilisateurs.bib_organismes + WHERE nom_organisme = 'ma structure test' + ), + ref_nomenclatures.get_id_nomenclature('ROLE_ACTEUR', '1') + ); +INSERT INTO gn_meta.cor_dataset_territory ( + id_dataset, + id_nomenclature_territory, + territory_desc + ) +VALUES ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f' + ), + ref_nomenclatures.get_id_nomenclature('TERRITOIRE', 'METROP'), + 'Territoire du parc national des Ecrins et de ses environs immédiats' + ), + ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc' + ), + ref_nomenclatures.get_id_nomenclature('TERRITOIRE', 'METROP'), + 'Réserve intégrale de lauvitel' + ); +INSERT INTO gn_meta.cor_dataset_protocol (id_dataset, id_protocol) +VALUES ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f' + ), + ( + SELECT id_protocol + FROM gn_meta.sinp_datatype_protocols + WHERE protocol_name = 'hors protocole' + ) + ), + ( + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc' + ), + ( + SELECT id_protocol + FROM gn_meta.sinp_datatype_protocols + WHERE protocol_name = 'hors protocole' + ) + ); +-- #On peuple la liste d'import +INSERT INTO gn_imports.t_imports ( + id_import, + id_dataset, + id_destination, + format_source_file, + srid, + separator, + encoding, + full_file_name, + source_count, + uuid_autogenerated, + altitude_autogenerated, + date_min_data, + date_max_data, + fieldmapping, + contentmapping, + detected_separator, + task_id, + erroneous_rows + ) +VALUES ( + 1000, + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc' + ), + ( + SELECT id_destination + FROM gn_imports.bib_destinations + WHERE code = 'synthese' + ), + 'CSV', + 4326, + ';', + 'UTF-8', + 'valid_file_test_link_list_import_synthese.csv', + 100, + true, + true, + '2022-01-01 00:00:00', + '2022-12-31 23:59:59', + '{"entity_source_pk_value": "id_synthese", "unique_id_sinp": "uuid_perm_sinp", "meta_create_date": "date_creation", "meta_update_date": "date_modification", "id_nomenclature_grp_typ": "type_regroupement", "unique_id_sinp_grp": "uuid_perm_grp_sinp", "date_min": "date_debut", "date_max": "date_fin", "hour_min": "heure_debut", "hour_max": "heure_fin", "altitude_min": "alti_min", "altitude_max": "alti_max", "depth_min": "prof_min", "depth_max": "prof_max", "observers": "observateurs", "comment_context": "comment_releve", "WKT": "geometrie_wkt_4326", "id_nomenclature_geo_object_nature": "nature_objet_geo", "place_name": "nom_lieu", "precision": "precision_geographique", "cd_hab": "cd_habref", "grp_method": "methode_regroupement", "nom_cite": "nom_cite", "cd_nom": "cd_nom", "id_nomenclature_obs_technique": "technique_observation", "id_nomenclature_bio_status": "biologique_statut", "id_nomenclature_bio_condition": "etat_biologique", "id_nomenclature_biogeo_status": "biogeographique_statut", "id_nomenclature_naturalness": "naturalite", "comment_description": "comment_occurrence", "id_nomenclature_sensitivity": "niveau_sensibilite", "id_nomenclature_diffusion_level": "niveau_precision_diffusion", "id_nomenclature_observation_status": "statut_observation", "id_nomenclature_blurring": "floutage_dee", "id_nomenclature_source_status": "statut_source", "reference_biblio": "reference_biblio", "id_nomenclature_behaviour": "comportement", "id_nomenclature_life_stage": "stade_vie", "id_nomenclature_sex": "sexe", "id_nomenclature_type_count": "type_denombrement", "id_nomenclature_obj_count": "objet_denombrement", "count_min": "nombre_min", "count_max": "nombre_max", "id_nomenclature_determination_method": "methode_determination", "determiner": "determinateur", "id_nomenclature_exist_proof": "preuve_existante", "digital_proof": "preuve_numerique_url", "non_digital_proof": "preuve_non_numerique", "id_nomenclature_valid_status": "niveau_validation", "validator": "validateur", "meta_validation_date": "date_validation", "validation_comment": "comment_validation"}', + '{"OCC_COMPORTEMENT": {"Non renseign\u00e9": "1"}, "ETA_BIO": {"Non renseign\u00e9": "1"}, "STATUT_BIO": {"Non renseign\u00e9": "1"}, "STAT_BIOGEO": {"Non renseign\u00e9": "1"}, "DEE_FLOU": {"Non": "NON"}, "METH_DETERMIN": {"Autre m\u00e9thode de d\u00e9termination": "2"}, "NIV_PRECIS": {"Pr\u00e9cise": "5"}, "PREUVE_EXIST": {"Oui": "1"}, "NAT_OBJ_GEO": {"Inventoriel": "In"}, "TYP_GRP": {"OBS": "OBS"}, "STADE_VIE": {"Adulte": "2", "Immature": "4", "Juv\u00e9nile": "3"}, "NATURALITE": {"Sauvage": "1"}, "OBJ_DENBR": {"Individu": "IND"}, "METH_OBS": {"Galerie/terrier": "23"}, "STATUT_OBS": {"Pr\u00e9sent": "Pr"}, "SENSIBILITE": {"Non sensible - Diffusion pr\u00e9cise": "0"}, "SEXE": {"Femelle": "2"}, "STATUT_SOURCE": {"Terrain": "Te"}, "TYP_DENBR": {"Compt\u00e9": "Co"}, "STATUT_VALID": {"En attente de validation": "0"}}', + ';', + NULL, + '{5}' + ), + ( + 1001, + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f' + ), + ( + SELECT id_destination + FROM gn_imports.bib_destinations + WHERE code = 'occhab' + ), + 'CSV', + 4326, + ';', + 'UTF-8', + 'valid_file_test_import_occhab.csv', + 100, + true, + true, + '2022-01-01 00:00:00', + '2022-12-31 23:59:59', + '{"date_min": "date_min", "cd_hab": "cd_hab", "nom_cite": "nom_cite"}', + '{}', + ';', + NULL, + '{}' + ), + ( + 1002, + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = 'a1b2c3d4-e5f6-4a3b-2c1d-e6f5a4b3c2d1' + ), + ( + SELECT id_destination + FROM gn_imports.bib_destinations + WHERE code = 'synthese' + ), + 'CSV', + 4326, + ';', + 'UTF-8', + 'valid_file_test_import_synthese.csv', + 100, + true, + true, + '2022-01-01 00:00:00', + '2022-12-31 23:59:59', + '{"entity_source_pk_value": "id_synthese", "unique_id_sinp": "uuid_perm_sinp", "meta_create_date": "date_creation", "meta_update_date": "date_modification", "id_nomenclature_grp_typ": "type_regroupement", "unique_id_sinp_grp": "uuid_perm_grp_sinp", "date_min": "date_debut", "date_max": "date_fin", "hour_min": "heure_debut", "hour_max": "heure_fin", "altitude_min": "alti_min", "altitude_max": "alti_max", "depth_min": "prof_min", "depth_max": "prof_max", "observers": "observateurs", "comment_context": "comment_releve", "WKT": "geometrie_wkt_4326", "id_nomenclature_geo_object_nature": "nature_objet_geo", "place_name": "nom_lieu", "precision": "precision_geographique", "cd_hab": "cd_habref", "grp_method": "methode_regroupement", "nom_cite": "nom_cite", "cd_nom": "cd_nom", "id_nomenclature_obs_technique": "technique_observation", "id_nomenclature_bio_status": "biologique_statut", "id_nomenclature_bio_condition": "etat_biologique", "id_nomenclature_biogeo_status": "biogeographique_statut", "id_nomenclature_naturalness": "naturalite", "comment_description": "comment_occurrence", "id_nomenclature_sensitivity": "niveau_sensibilite", "id_nomenclature_diffusion_level": "niveau_precision_diffusion", "id_nomenclature_observation_status": "statut_observation", "id_nomenclature_blurring": "floutage_dee", "id_nomenclature_source_status": "statut_source", "reference_biblio": "reference_biblio", "id_nomenclature_behaviour": "comportement", "id_nomenclature_life_stage": "stade_vie", "id_nomenclature_sex": "sexe", "id_nomenclature_type_count": "type_denombrement", "id_nomenclature_obj_count": "objet_denombrement", "count_min": "nombre_min", "count_max": "nombre_max", "id_nomenclature_determination_method": "methode_determination", "determiner": "determinateur", "id_nomenclature_exist_proof": "preuve_existante", "digital_proof": "preuve_numerique_url", "non_digital_proof": "preuve_non_numerique", "id_nomenclature_valid_status": "niveau_validation", "validator": "validateur", "meta_validation_date": "date_validation", "validation_comment": "comment_validation"}', + '{"OCC_COMPORTEMENT": {"Non renseign\u00e9": "1"}, "ETA_BIO": {"Non renseign\u00e9": "1"}, "STATUT_BIO": {"Non renseign\u00e9": "1"}, "STAT_BIOGEO": {"Non renseign\u00e9": "1"}, "DEE_FLOU": {"Non": "NON"}, "METH_DETERMIN": {"Autre m\u00e9thode de d\u00e9termination": "2"}, "NIV_PRECIS": {"Pr\u00e9cise": "5"}, "PREUVE_EXIST": {"Oui": "1"}, "NAT_OBJ_GEO": {"Inventoriel": "In"}, "TYP_GRP": {"OBS": "OBS"}, "STADE_VIE": {"Adulte": "2", "Immature": "4", "Juv\u00e9nile": "3"}, "NATURALITE": {"Sauvage": "1"}, "OBJ_DENBR": {"Individu": "IND"}, "METH_OBS": {"Galerie/terrier": "23"}, "STATUT_OBS": {"Pr\u00e9sent": "Pr"}, "SENSIBILITE": {"Non sensible - Diffusion pr\u00e9cise": "0"}, "SEXE": {"Femelle": "2"}, "STATUT_SOURCE": {"Terrain": "Te"}, "TYP_DENBR": {"Compt\u00e9": "Co"}, "STATUT_VALID": {"En attente de validation": "0"}}', + ';', + NULL, + '{5}' + ), + ( + 1003, + ( + SELECT id_dataset + FROM gn_meta.t_datasets + WHERE unique_dataset_id = '5f45d560-1ce3-420c-b45c-3d589eedaee1' + ), + ( + SELECT id_destination + FROM gn_imports.bib_destinations + WHERE code = 'synthese' + ), + 'CSV', + 4326, + ';', + 'UTF-8', + 'valid_file_test_import_synthese.csv', + 100, + true, + true, + '2022-01-01 00:00:00', + '2022-12-31 23:59:59', + '{"entity_source_pk_value": "id_synthese", "unique_id_sinp": "uuid_perm_sinp", "meta_create_date": "date_creation", "meta_update_date": "date_modification", "id_nomenclature_grp_typ": "type_regroupement", "unique_id_sinp_grp": "uuid_perm_grp_sinp", "date_min": "date_debut", "date_max": "date_fin", "hour_min": "heure_debut", "hour_max": "heure_fin", "altitude_min": "alti_min", "altitude_max": "alti_max", "depth_min": "prof_min", "depth_max": "prof_max", "observers": "observateurs", "comment_context": "comment_releve", "WKT": "geometrie_wkt_4326", "id_nomenclature_geo_object_nature": "nature_objet_geo", "place_name": "nom_lieu", "precision": "precision_geographique", "cd_hab": "cd_habref", "grp_method": "methode_regroupement", "nom_cite": "nom_cite", "cd_nom": "cd_nom", "id_nomenclature_obs_technique": "technique_observation", "id_nomenclature_bio_status": "biologique_statut", "id_nomenclature_bio_condition": "etat_biologique", "id_nomenclature_biogeo_status": "biogeographique_statut", "id_nomenclature_naturalness": "naturalite", "comment_description": "comment_occurrence", "id_nomenclature_sensitivity": "niveau_sensibilite", "id_nomenclature_diffusion_level": "niveau_precision_diffusion", "id_nomenclature_observation_status": "statut_observation", "id_nomenclature_blurring": "floutage_dee", "id_nomenclature_source_status": "statut_source", "reference_biblio": "reference_biblio", "id_nomenclature_behaviour": "comportement", "id_nomenclature_life_stage": "stade_vie", "id_nomenclature_sex": "sexe", "id_nomenclature_type_count": "type_denombrement", "id_nomenclature_obj_count": "objet_denombrement", "count_min": "nombre_min", "count_max": "nombre_max", "id_nomenclature_determination_method": "methode_determination", "determiner": "determinateur", "id_nomenclature_exist_proof": "preuve_existante", "digital_proof": "preuve_numerique_url", "non_digital_proof": "preuve_non_numerique", "id_nomenclature_valid_status": "niveau_validation", "validator": "validateur", "meta_validation_date": "date_validation", "validation_comment": "comment_validation"}', + '{"OCC_COMPORTEMENT": {"Non renseign\u00e9": "1"}, "ETA_BIO": {"Non renseign\u00e9": "1"}, "STATUT_BIO": {"Non renseign\u00e9": "1"}, "STAT_BIOGEO": {"Non renseign\u00e9": "1"}, "DEE_FLOU": {"Non": "NON"}, "METH_DETERMIN": {"Autre m\u00e9thode de d\u00e9termination": "2"}, "NIV_PRECIS": {"Pr\u00e9cise": "5"}, "PREUVE_EXIST": {"Oui": "1"}, "NAT_OBJ_GEO": {"Inventoriel": "In"}, "TYP_GRP": {"OBS": "OBS"}, "STADE_VIE": {"Adulte": "2", "Immature": "4", "Juv\u00e9nile": "3"}, "NATURALITE": {"Sauvage": "1"}, "OBJ_DENBR": {"Individu": "IND"}, "METH_OBS": {"Galerie/terrier": "23"}, "STATUT_OBS": {"Pr\u00e9sent": "Pr"}, "SENSIBILITE": {"Non sensible - Diffusion pr\u00e9cise": "0"}, "SEXE": {"Femelle": "2"}, "STATUT_SOURCE": {"Terrain": "Te"}, "TYP_DENBR": {"Compt\u00e9": "Co"}, "STATUT_VALID": {"En attente de validation": "0"}}', + ';', + NULL, + '{5}' + ); +-- On peuple les tables de correspondances - Ajout des roles lié aux imports +CREATE TEMP TABLE temp_filtered_imports AS +SELECT ti.id_import, + ti.id_dataset +FROM gn_imports.t_imports ti + JOIN gn_meta.t_datasets td ON ti.id_dataset = td.id_dataset +WHERE td.dataset_name ILIKE '%JDD-TEST-IMPORT%'; +INSERT INTO gn_imports.cor_role_import (id_role, id_import) +SELECT cda.id_role, + tfi.id_import +FROM temp_filtered_imports tfi + JOIN gn_meta.cor_dataset_actor cda ON tfi.id_dataset = cda.id_dataset +WHERE cda.id_role IS NOT NULL; +DROP TABLE temp_filtered_imports; diff --git a/backend/geonature/migrations/data/imports/schema.sql b/backend/geonature/migrations/data/imports/schema.sql new file mode 100644 index 0000000000..095e3f5d83 --- /dev/null +++ b/backend/geonature/migrations/data/imports/schema.sql @@ -0,0 +1,341 @@ +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SET check_function_bodies = false; +SET client_min_messages = warning; + +-------------- +-------------- +--GN_IMPORTS-- +-------------- +-------------- + +-- Créer le schéma du module IMPORT +CREATE SCHEMA IF NOT EXISTS gn_imports; + +SET search_path = gn_imports, pg_catalog, public; +SET default_with_oids = false; + +------------------------ +--TABLES AND SEQUENCES-- +------------------------ + +-- Créer la table stockant la liste des imports +CREATE TABLE t_imports( + id_import serial NOT NULL, + format_source_file character varying(10), + SRID integer, + separator character varying, + encoding character varying, + import_table character varying(255), + full_file_name character varying(255), + id_dataset integer, + id_field_mapping integer, + id_content_mapping integer, + date_create_import timestamp without time zone DEFAULT now(), + date_update_import timestamp without time zone DEFAULT now(), + date_end_import timestamp without time zone, + source_count integer, + import_count integer, + taxa_count integer, + uuid_autogenerated boolean, + altitude_autogenerated boolean, + date_min_data timestamp without time zone, + date_max_data timestamp without time zone, + step integer, + is_finished boolean DEFAULT FALSE, + processing boolean DEFAULT FALSE, + in_error boolean DEFAULT FALSE, + id_source_synthese integer, + need_fix boolean DEFAULT FALSE, + fix_comment text +); + +-- Créer la table stockant les auteurs des imports +CREATE TABLE cor_role_import( + id_role integer NOT NULL, + id_import integer NOT NULL +); + +-- Créer la table listant les erreurs +CREATE TABLE t_user_errors( + id_error serial NOT NULL, + error_type character varying(100) NOT NULL, + name character varying(255) NOT NULL UNIQUE, + description text, + error_level character varying(25) +); + +-- Créer la table stockant les auteurs des mappings +CREATE TABLE cor_role_mapping( + id_role integer NOT NULL, + id_mapping integer NOT NULL +); + +-- Créer la table stockant les champs des mappings +CREATE TABLE t_mappings_fields( + id_match_fields serial NOT NULL, + id_mapping integer NOT NULL, + source_field character varying(255) NOT NULL, + target_field character varying(255) NOT NULL, + is_selected boolean NOT NULL, + is_added boolean NOT NULL +); + +-- Créer la table stockant les valeurs des mappings +CREATE TABLE t_mappings_values( + id_match_values serial NOT NULL, + id_mapping integer NOT NULL, + --id_type_mapping integer NOT NULL, + source_value character varying(255) NOT NULL, + id_target_value integer NOT NULL +); + +-- Créer la table des mappings +CREATE TABLE t_mappings( + id_mapping serial NOT NULL, + mapping_label character varying(255) NOT NULL, + mapping_type character varying(10) NOT NULL, + active boolean NOT NULL, + temporary boolean NOT NULL DEFAULT false, + is_public boolean default false +); + +-- Créer la table des thèmes permettant de regroupant les champs à mapper +CREATE TABLE dict_themes( + id_theme serial, + name_theme character varying(100) NOT NULL, + fr_label_theme character varying(100) NOT NULL, + eng_label_theme character varying(100), + desc_theme character varying(1000), + order_theme integer NOT NULL +); + +-- Créer la table des champs à mapper +CREATE TABLE dict_fields( + id_field serial, + name_field character varying(100) NOT NULL, + fr_label character varying(100) NOT NULL, + eng_label character varying(100), + desc_field character varying(1000), + type_field character varying(50), + synthese_field boolean NOT NULL, + mandatory boolean NOT NULL, + autogenerated boolean NOT NULL, + nomenclature boolean NOT NULL, + id_theme integer NOT NULL, + order_field integer NOT NULL, + display boolean NOT NULL, + comment text +); + +-- Créer la table associant les nomenclatures aux champs de la table Synthèse +CREATE TABLE cor_synthese_nomenclature( + mnemonique character varying(50) NOT NULL, + synthese_col character varying(50) NOT NULL +); + +-- Créer la table stockant les erreurs des imports +CREATE TABLE t_user_error_list( + id_user_error serial NOT NULL, + id_import integer NOT NULL, + id_error integer NOT NULL, + column_error character varying(100) NOT NULL, + id_rows text[], + step character varying(20), + comment text +); + +-- Créer une vue listant les erreurs des imports + CREATE VIEW gn_imports.v_imports_errors AS + SELECT + id_user_error, + id_import, + error_type, + name AS error_name, + error_level, + description AS error_description, + column_error, + id_rows, + comment + FROM gn_imports.t_user_error_list el + JOIN gn_imports.t_user_errors ue on ue.id_error = el.id_error; + + +---------------------------- +--PRIMARY KEY AND UNICITY--- +---------------------------- + +ALTER TABLE ONLY t_imports + ADD CONSTRAINT pk_gn_imports_t_imports PRIMARY KEY (id_import); + +ALTER TABLE ONLY cor_role_import + ADD CONSTRAINT pk_cor_role_import PRIMARY KEY (id_role, id_import); + +ALTER TABLE ONLY t_user_errors + ADD CONSTRAINT pk_user_errors PRIMARY KEY (id_error); + +ALTER TABLE ONLY cor_role_mapping + ADD CONSTRAINT pk_cor_role_mapping PRIMARY KEY (id_role, id_mapping); + +ALTER TABLE ONLY t_mappings_fields + ADD CONSTRAINT pk_t_mappings_fields PRIMARY KEY (id_match_fields); + +ALTER TABLE ONLY t_mappings_values + ADD CONSTRAINT pk_t_mappings_values PRIMARY KEY (id_match_values); + +ALTER TABLE ONLY t_mappings + ADD CONSTRAINT pk_t_mappings PRIMARY KEY (id_mapping); + + +--ALTER TABLE ONLY bib_type_mapping_values +-- ADD CONSTRAINT pk_bib_type_mapping_values PRIMARY KEY (id_type_mapping, mapping_type); + +ALTER TABLE ONLY dict_themes + ADD CONSTRAINT pk_dict_themes_id_theme PRIMARY KEY (id_theme); + +ALTER TABLE ONLY dict_fields + ADD CONSTRAINT pk_dict_fields_id_theme PRIMARY KEY (id_field); + +ALTER TABLE ONLY dict_fields + ADD CONSTRAINT unicity_t_mappings_fields_name_field UNIQUE (name_field); + +ALTER TABLE ONLY cor_synthese_nomenclature + ADD CONSTRAINT pk_cor_synthese_nomenclature PRIMARY KEY (mnemonique, synthese_col); + +ALTER TABLE ONLY t_user_error_list + ADD CONSTRAINT pk_t_user_error_list PRIMARY KEY (id_user_error); + +--------------- +--FOREIGN KEY-- +--------------- + +ALTER TABLE ONLY t_imports + ADD CONSTRAINT fk_gn_meta_t_datasets FOREIGN KEY (id_dataset) REFERENCES gn_meta.t_datasets(id_dataset) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE only t_imports + ADD CONSTRAINT fk_gn_imports_t_import_id_source_synthese FOREIGN KEY (id_source_synthese) REFERENCES gn_synthese.t_sources(id_source) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY t_imports + ADD CONSTRAINT fk_gn_imports_t_mappings_fields FOREIGN KEY (id_field_mapping) REFERENCES gn_imports.t_mappings(id_mapping) ON UPDATE CASCADE ON DELETE NO ACTION; + +ALTER TABLE ONLY t_imports + ADD CONSTRAINT fk_gn_import_t_mappings_values FOREIGN KEY (id_content_mapping) REFERENCES gn_imports.t_mappings(id_mapping) ON UPDATE CASCADE ON DELETE NO ACTION; + +ALTER TABLE ONLY cor_role_import + ADD CONSTRAINT fk_utilisateurs_t_roles FOREIGN KEY (id_role) REFERENCES utilisateurs.t_roles(id_role) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY cor_role_mapping + ADD CONSTRAINT fk_utilisateurs_t_roles FOREIGN KEY (id_role) REFERENCES utilisateurs.t_roles(id_role) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY cor_role_mapping + ADD CONSTRAINT fk_gn_imports_t_mappings_id_mapping FOREIGN KEY (id_mapping) REFERENCES gn_imports.t_mappings(id_mapping) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY t_mappings_fields + ADD CONSTRAINT fk_gn_imports_t_mappings_id_mapping FOREIGN KEY (id_mapping) REFERENCES gn_imports.t_mappings(id_mapping) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY t_mappings_fields + ADD CONSTRAINT fk_gn_imports_t_mappings_fields_target_field FOREIGN KEY (target_field) REFERENCES gn_imports.dict_fields(name_field) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY t_mappings_values + ADD CONSTRAINT fk_gn_imports_t_mappings_id_mapping FOREIGN KEY (id_mapping) REFERENCES gn_imports.t_mappings(id_mapping) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY t_mappings_values + ADD CONSTRAINT fk_gn_imports_t_mappings_values_id_nomenclature FOREIGN KEY (id_target_value) REFERENCES ref_nomenclatures.t_nomenclatures(id_nomenclature) ON UPDATE CASCADE ON DELETE CASCADE; + +--ALTER TABLE ONLY t_mappings_values +-- ADD CONSTRAINT fk_gn_imports_bib_type_mapping_values_id_type_mapping FOREIGN KEY (id_type_mapping) REFERENCES gn_imports.bib_type_mapping_values(id_type_mapping) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY dict_fields + ADD CONSTRAINT fk_gn_imports_dict_themes_id_theme FOREIGN KEY (id_theme) REFERENCES gn_imports.dict_themes(id_theme) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY cor_synthese_nomenclature + ADD CONSTRAINT fk_cor_synthese_nomenclature_id_type FOREIGN KEY (mnemonique) REFERENCES ref_nomenclatures.bib_nomenclatures_types(mnemonique) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY t_user_error_list + ADD CONSTRAINT fk_t_user_error_list_id_import FOREIGN KEY (id_import) REFERENCES gn_imports.t_imports(id_import) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY t_user_error_list + ADD CONSTRAINT fk_t_user_error_list_id_error FOREIGN KEY (id_error) REFERENCES gn_imports.t_user_errors(id_error) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY cor_role_import + ADD CONSTRAINT fk_cor_role_import_role FOREIGN KEY (id_role) REFERENCES utilisateurs.t_roles(id_role) ON UPDATE CASCADE ON DELETE CASCADE; + + ALTER TABLE ONLY cor_role_import + ADD CONSTRAINT fk_cor_role_import_import FOREIGN KEY (id_import) REFERENCES gn_imports.t_imports(id_import) ON UPDATE CASCADE ON DELETE CASCADE; + +--------------------- +--OTHER CONSTRAINTS-- +--------------------- + +ALTER TABLE ONLY t_mappings + ADD CONSTRAINT check_mapping_type_in_t_mappings CHECK (mapping_type IN ('FIELD', 'CONTENT')); + +ALTER TABLE ONLY dict_fields + ADD CONSTRAINT chk_mandatory CHECK ( + CASE + WHEN name_field IN ('date_min', 'longitude', 'latitude', 'nom_cite', 'cd_nom', 'wkt') + THEN mandatory=TRUE + END +); + +------------ +--TRIGGERS-- +------------ + +-- faire un trigger pour cor_role_mapping qui remplit quand create ou delete t_mappings.id_mapping ? + +------------- +--FUNCTIONS-- +------------- + +----------------------- +----------------------- +--GN_IMPORTS_ARCHIVES-- +----------------------- +----------------------- + +-- Créer un schéma pour stocker les données sources des fichiers importés +CREATE SCHEMA gn_import_archives; + +SET search_path = gn_import_archives, pg_catalog; +SET default_with_oids = false; + +---------- +--TABLES-- +---------- + +-- Créer la table stockant les données sources des fichiers importés +CREATE TABLE cor_import_archives( + id_import integer NOT NULL, + table_archive character varying(255) NOT NULL +); + + +--------------- +--PRIMARY KEY-- +--------------- + +ALTER TABLE ONLY cor_import_archives ADD CONSTRAINT pk_cor_import_archives PRIMARY KEY (id_import, table_archive); + + +--------------- +--FOREIGN KEY-- +--------------- + +ALTER TABLE ONLY cor_import_archives + ADD CONSTRAINT fk_gn_imports_t_imports FOREIGN KEY (id_import) REFERENCES gn_imports.t_imports(id_import) ON UPDATE CASCADE ON DELETE CASCADE; + + +------------ +--TRIGGERS-- +------------ + +-- faire trigger pour rapatrier données dans cor_import_archives quand creation dun nouvel import? + + +------------- +--FUNCTIONS-- +------------- + diff --git a/backend/geonature/migrations/versions/02e9b8758709_update_notifications_import_done.py b/backend/geonature/migrations/versions/02e9b8758709_update_notifications_import_done.py new file mode 100644 index 0000000000..8edd8f1dd4 --- /dev/null +++ b/backend/geonature/migrations/versions/02e9b8758709_update_notifications_import_done.py @@ -0,0 +1,61 @@ +"""update notifications import-done to make them generic with respect to the destination + +Revision ID: 02e9b8758709 +Revises: 5a2c9c65129f +Create Date: 2024-01-23 16:10:58.149517 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "02e9b8758709" +down_revision = "5a2c9c65129f" +branch_labels = None +depends_on = None + +ORIGINAL_TEMPLATE_DB = ( + "Import n° {{ import.id_import }} correctement terminé et intégré dans la synthèse" +) +ORIGINAL_TEMPLATE_MAIL = """

Bonjour {{ role.nom_complet }} !

Votre import n°{{ import.id_import }} s’est terminé correctement {% if import.statistics["import_count"] == import.source_count %} 👌 et a été bien {% else %} 👍 mais a été partiellement {% endif %} intégré dans la synthèse.

{{ import.statistics["import_count"] }} / {{ import.source_count }} données ont pu être effectivement intégrées dans la synthèse.


Vous recevez cet email automatiquement via le service de notification de GeoNature. Gestion de vos règles de notification.

""" + +NEW_TEMPLATE_DB = """Import n° {{ import.id_import }} terminé{% if import.errors|length > 0 %} 👍 et partiellement {% else %} 👌 et correctement {% endif %}intégré : {{ import.statistics["import_count"] }} entité{% if import.statistics["import_count"] > 1 %}s{% endif %} importée{% if import.statistics["import_count"] > 1 %}s{% endif %} dans la destination {{ destination.label }}""" +NEW_TEMPLATE_MAIL = """

Bonjour {{ role.nom_complet }} !

Votre import n°{{ import.id_import }} est terminé {% if import.errors|length > 0 %} 👍 mais a été partiellement {% else %} 👌 et a été correctement {% endif %} intégré dans la destination {{ destination.label }}.

{{ import.statistics["import_count"] }} entité{% if import.statistics["import_count"] > 1 %}s valides ont{% else %} valide a{% endif %} pu être effectivement intégrée{% if import.statistics["import_count"] > 1 %}s{% endif %}.


Vous recevez cet email automatiquement via le service de notification de GeoNature. Gestion de vos règles de notification.

""" + + +def upgrade(): + # Update templates 'EMAIL' and 'DB' for the category 'IMPORT-DONE', IF AND ONLY IF still equal to the original template from "485a659efdcd" + op.execute( + f""" + UPDATE gn_notifications.bib_notifications_templates + SET content = '{NEW_TEMPLATE_DB}' + WHERE code_method = 'DB' AND code_category = 'IMPORT-DONE' AND content = '{ORIGINAL_TEMPLATE_DB}' + """ + ) + op.execute( + f""" + UPDATE gn_notifications.bib_notifications_templates + SET content = '{NEW_TEMPLATE_MAIL}' + WHERE code_method = 'EMAIL' AND code_category = 'IMPORT-DONE' AND content = '{ORIGINAL_TEMPLATE_MAIL}' + """ + ) + + +def downgrade(): + # Set back to the original templates + op.execute( + f""" + UPDATE gn_notifications.bib_notifications_templates + SET content = '{ORIGINAL_TEMPLATE_DB}' + WHERE code_method = 'DB' AND code_category = 'IMPORT-DONE' + """ + ) + op.execute( + f""" + UPDATE gn_notifications.bib_notifications_templates + SET content = '{ORIGINAL_TEMPLATE_MAIL}' + WHERE code_method = 'EMAIL' AND code_category = 'IMPORT-DONE' + """ + ) diff --git a/backend/geonature/migrations/versions/0e8e1943c215_add_import_missing_error.py b/backend/geonature/migrations/versions/0e8e1943c215_add_import_missing_error.py new file mode 100644 index 0000000000..a8e68ac0f5 --- /dev/null +++ b/backend/geonature/migrations/versions/0e8e1943c215_add_import_missing_error.py @@ -0,0 +1,68 @@ +"""add new import error types + +Revision ID: 0e8e1943c215 +Revises: 8b149244d586 +Create Date: 2024-05-03 14:22:20.773467 + +""" + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = "0e8e1943c215" +down_revision = "6e1852ecfea2" +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + metadata = sa.MetaData(bind=conn) + error_type = sa.Table("bib_errors_types", metadata, schema="gn_imports", autoload_with=conn) + op.bulk_insert( + error_type, + [ + { + "error_type": "Erreur de format booléen", + "name": "INVALID_BOOL", + "description": "Le champ doit être renseigné avec une valeur binaire (0 ou 1, true ou false).", + "error_level": "ERROR", + }, + { + "error_type": "Données incohérentes d'une ou plusieurs entités", + "name": "INCOHERENT_DATA", + "description": "Les données indiquées pour une ou plusieurs entités sont incohérentes sur différentes lignes.", + "error_level": "ERROR", + }, + { + "error_type": "Erreur de format nombre", + "name": "INVALID_NUMERIC", + "description": "Le champ doit être renseigné avec une valeur numérique (entier, flottant).", + "error_level": "ERROR", + }, + { + "error_type": "Ignorer les données existantes", + "name": "SKIP_EXISTING_UUID", + "description": "Les entitiés existantes selon UUID sont ignorees.", + "error_level": "WARNING", + }, + { + "error_type": "Erreur de référentiel", + "name": "DATASET_NOT_ACTIVE", + "description": "Les jeux de données doivent être actifs pour pouvoir importer des données.", + "error_level": "ERROR", + }, + ], + ) + + +def downgrade(): + conn = op.get_bind() + metadata = sa.MetaData(bind=conn) + error_type = sa.Table("bib_errors_types", metadata, schema="gn_imports", autoload_with=conn) + op.execute(sa.delete(error_type).where(error_type.c.name == "INVALID_BOOL")) + op.execute(sa.delete(error_type).where(error_type.c.name == "INCOHERENT_DATA")) + op.execute(sa.delete(error_type).where(error_type.c.name == "INVALID_NUMERIC")) + op.execute(sa.delete(error_type).where(error_type.c.name == "SKIP_EXISTING_UUID")) + op.execute(sa.delete(error_type).where(error_type.c.name == "DATASET_NOT_ACTIVE")) diff --git a/backend/geonature/migrations/versions/5a2c9c65129f_add_sensitivity_filter_export_synthese.py b/backend/geonature/migrations/versions/5a2c9c65129f_add_sensitivity_filter_export_synthese.py index e8ab60fd7a..85fd506fe0 100644 --- a/backend/geonature/migrations/versions/5a2c9c65129f_add_sensitivity_filter_export_synthese.py +++ b/backend/geonature/migrations/versions/5a2c9c65129f_add_sensitivity_filter_export_synthese.py @@ -1,7 +1,7 @@ """add sensitivity filter export synthese Revision ID: 5a2c9c65129f -Revises: 446e902a14e7 +Revises: 92f0083cf735 Create Date: 2023-08-08 16:23:53.059110 """ @@ -12,7 +12,7 @@ # revision identifiers, used by Alembic. revision = "5a2c9c65129f" -down_revision = "d99a7c22cc3c" +down_revision = "92f0083cf735" branch_labels = None depends_on = None diff --git a/backend/geonature/migrations/versions/7b6a578eccd7_drop_import_count_column.py b/backend/geonature/migrations/versions/7b6a578eccd7_drop_import_count_column.py new file mode 100644 index 0000000000..64bc777b3d --- /dev/null +++ b/backend/geonature/migrations/versions/7b6a578eccd7_drop_import_count_column.py @@ -0,0 +1,66 @@ +"""drop import_count column + +Revision ID: 7b6a578eccd7 +Revises: c49474d2f1f7 +Create Date: 2024-10-18 16:24:44.145501 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "7b6a578eccd7" +down_revision = "c49474d2f1f7" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + """ + UPDATE gn_imports.t_imports + SET statistics = statistics::jsonb || jsonb_build_object('import_count', import_count) || jsonb_build_object('nb_line_valid', import_count); + """ + ) + op.drop_column( + schema="gn_imports", + table_name="t_imports", + column_name="import_count", + ) + + +def downgrade(): + op.add_column( + schema="gn_imports", + table_name="t_imports", + column=sa.Column( + "import_count", + sa.Integer, + ), + ) + + op.execute( + """ + WITH count_ AS ( + SELECT + id_import as id_import, + (statistics->>'import_count')::integer as import_count + FROM + gn_imports.t_imports + WHERE + statistics->>'import_count' IS NOT NULL + ) + UPDATE gn_imports.t_imports as GN + SET import_count = count_.import_count + FROM count_ + where GN.id_import = count_.id_import; + """ + ) + op.execute( + """ + UPDATE gn_imports.t_imports as GN + SET statistics = statistics::jsonb #- '{import_count}'; + """ + ) diff --git a/backend/geonature/migrations/versions/8b149244d586_add_required_conditions_in_import.py b/backend/geonature/migrations/versions/8b149244d586_add_required_conditions_in_import.py new file mode 100644 index 0000000000..ba99791038 --- /dev/null +++ b/backend/geonature/migrations/versions/8b149244d586_add_required_conditions_in_import.py @@ -0,0 +1,148 @@ +"""add required conditions in import + +Revision ID: 8b149244d586 +Revises: ebbe0f7ed866 +Create Date: 2024-03-20 11:17:57.360785 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "8b149244d586" +down_revision = "ebbe0f7ed866" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + table_name="bib_fields", + schema="gn_imports", + column=sa.Column( + "mandatory_conditions", + sa.ARRAY(sa.Unicode), + comment="Contient la liste de champs qui rendent le champ obligatoire.", + ), + ) + op.add_column( + table_name="bib_fields", + schema="gn_imports", + column=sa.Column( + "optional_conditions", + sa.ARRAY(sa.Unicode), + comment="Contient la liste de champs qui rendent le champ optionnel.", + ), + ) + + op.execute( + """ + CREATE OR REPLACE FUNCTION gn_imports.isInNameFields ( + fields TEXT[],destination_id INTEGER + ) + RETURNS BOOLEAN + AS $$ + DECLARE + name_field_other TEXT; + BEGIN + IF fields IS DISTINCT FROM NULL THEN + FOREACH name_field_other IN ARRAY fields LOOP + IF NOT EXISTS ( + SELECT * + FROM gn_imports.bib_fields + WHERE name_field = name_field_other AND id_destination = destination_id + ) then + return FALSE; + END IF; + END LOOP; + END IF; + return TRUE; + END; + $$ LANGUAGE plpgsql; + """ + ) + + op.execute( + """ + alter table gn_imports.bib_fields + ADD CONSTRAINT mandatory_conditions_field_exists CHECK (gn_imports.isInNameFields(mandatory_conditions,id_destination)); + """ + ) + op.execute( + """ + alter table gn_imports.bib_fields + ADD CONSTRAINT optional_conditions_field_exists CHECK (gn_imports.isInNameFields(optional_conditions,id_destination)); + """ + ) + conn = op.get_bind() + metadata = sa.MetaData(bind=conn) + destination = sa.Table("bib_destinations", metadata, schema="gn_imports", autoload_with=conn) + synthese_dest_id = conn.scalar( + sa.select(destination.c.id_destination).where(destination.c.code == "synthese") + ) + field = sa.Table("bib_fields", metadata, schema="gn_imports", autoload_with=conn) + op.execute( + sa.update(field) + .where(field.c.name_field == "WKT", field.c.id_destination == synthese_dest_id) + .values( + optional_conditions=[ + "latitude", + "longitude", + "codecommune", + "codedepartement", + "codemaille", + ], + mandatory=True, + ) + ) + op.execute( + sa.update(field) + .where(field.c.name_field == "longitude", field.c.id_destination == synthese_dest_id) + .values( + optional_conditions=["WKT", "codecommune", "codedepartement", "codemaille"], + mandatory_conditions=["latitude"], + mandatory=True, + ) + ) + op.execute( + sa.update(field) + .where(field.c.name_field == "latitude", field.c.id_destination == synthese_dest_id) + .values( + optional_conditions=["WKT", "codecommune", "codedepartement", "codemaille"], + mandatory_conditions=["longitude"], + mandatory=True, + ) + ) + + +def downgrade(): + op.drop_constraint("mandatory_conditions_field_exists", "bib_fields", schema="gn_imports") + op.drop_constraint("optional_conditions_field_exists", "bib_fields", schema="gn_imports") + op.execute("DROP FUNCTION IF EXISTS gn_imports.isInNameFields") + op.drop_column(table_name="bib_fields", schema="gn_imports", column_name="mandatory_conditions") + op.drop_column(table_name="bib_fields", schema="gn_imports", column_name="optional_conditions") + + conn = op.get_bind() + metadata = sa.MetaData(bind=conn) + destination = sa.Table("bib_destinations", metadata, schema="gn_imports", autoload_with=conn) + synthese_dest_id = conn.scalar( + sa.select(destination.c.id_destination).where(destination.c.code == "synthese") + ) + field = sa.Table("bib_fields", metadata, schema="gn_imports", autoload_with=conn) + op.execute( + sa.update(field) + .where(field.c.name_field == "WKT", field.c.id_destination == synthese_dest_id) + .values(mandatory=False) + ) + op.execute( + sa.update(field) + .where(field.c.name_field == "longitude", field.c.id_destination == synthese_dest_id) + .values(mandatory=False) + ) + op.execute( + sa.update(field) + .where(field.c.name_field == "latitude", field.c.id_destination == synthese_dest_id) + .values(mandatory=False) + ) diff --git a/backend/geonature/migrations/versions/92f0083cf735_import_schema.py b/backend/geonature/migrations/versions/92f0083cf735_import_schema.py new file mode 100644 index 0000000000..b9b080862f --- /dev/null +++ b/backend/geonature/migrations/versions/92f0083cf735_import_schema.py @@ -0,0 +1,43 @@ +"""import schema + +Revision ID: 92f0083cf735 +Revises: 446e902a14e7 +Create Date: 2023-11-07 16:06:36.745188 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "92f0083cf735" +down_revision = "d99a7c22cc3c" +branch_labels = None +depends_on = ("2b0b3bd0248c",) + + +def upgrade(): + op.execute( + """ + UPDATE + gn_commons.t_modules + SET + type = 'synthese' + WHERE + module_code = 'SYNTHESE' + """ + ) + + +def downgrade(): + op.execute( + """ + UPDATE + gn_commons.t_modules + SET + type = 'base' + WHERE + module_code = 'SYNTHESE' + """ + ) diff --git a/backend/geonature/migrations/versions/9f4db1786c22_add_group3_inpn_to_synthese_views.py b/backend/geonature/migrations/versions/9f4db1786c22_add_group3_inpn_to_synthese_views.py index af63ece1a5..57c6b80e0a 100644 --- a/backend/geonature/migrations/versions/9f4db1786c22_add_group3_inpn_to_synthese_views.py +++ b/backend/geonature/migrations/versions/9f4db1786c22_add_group3_inpn_to_synthese_views.py @@ -1,7 +1,7 @@ """add group3 inpn to synthese views Revision ID: 9f4db1786c22 -Revises: 446e902a14e7 +Revises: bfc90691737d Create Date: 2023-07-21 14:15:23.311469 """ @@ -11,7 +11,7 @@ # revision identifiers, used by Alembic. revision = "9f4db1786c22" -down_revision = "5a2c9c65129f" +down_revision = "bfc90691737d" branch_labels = None depends_on = ("c4415009f164",) # Taxref v15 db structure diff --git a/backend/geonature/migrations/versions/c49474d2f1f7_update_synthese_for_import.py b/backend/geonature/migrations/versions/c49474d2f1f7_update_synthese_for_import.py new file mode 100644 index 0000000000..2cde5d8c40 --- /dev/null +++ b/backend/geonature/migrations/versions/c49474d2f1f7_update_synthese_for_import.py @@ -0,0 +1,280 @@ +"""update synthese for import + +Revision ID: c49474d2f1f7 +Revises: 0e8e1943c215 +Create Date: 2024-10-01 10:09:10.937073 + +""" + +from alembic import op +from sqlalchemy.orm import Session +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "c49474d2f1f7" +down_revision = "0e8e1943c215" +branch_labels = None +depends_on = None + + +def upgrade(): + # modifier la suppression de l'import synthese + # modifier la création de la synthese depuis l'import + # ajouter un filtre pour filtrer sur l'id_import + # le mettre par default quand dans l'url + op.add_column( + schema="gn_synthese", + table_name="synthese", + column=sa.Column("id_import", sa.Integer()), + ) + conn = op.get_bind() + metadata = sa.MetaData(bind=conn) + t_sources = sa.Table("t_sources", metadata, schema="gn_synthese", autoload_with=conn) + t_modules = sa.Table("t_modules", metadata, schema="gn_commons", autoload_with=conn) + t_synthese = sa.Table("synthese", metadata, schema="gn_synthese", autoload_with=conn) + id_import_module = conn.execute( + sa.select(t_modules.c.id_module).where(t_modules.c.module_code == "IMPORT") + ).scalar_one() + id_source = conn.execute( + t_sources.insert() + .values( + name_source="Import", + desc_source="Données issues du module Import", + entity_source_pk_field="entity_source_pk_value", + id_module=id_import_module, + ) + .returning(t_sources.c.id_source) + ).scalar_one() + op.execute( + sa.update(t_synthese) + .where(t_synthese.c.id_source == t_sources.c.id_source) + .where(t_sources.c.id_module == id_import_module) + .values( + id_source=id_source, + id_import=sa.func.cast( + sa.func.regexp_replace( + t_sources.c.name_source, + r"^Import\(id=(\d+)\)$", + r"\1", + flags="g", + ), + sa.INT, + ), + ) + ) + conn.execute( + t_sources.delete() + .where(t_sources.c.id_module == id_import_module) + .where(t_sources.c.id_source != id_source) + ) + + op.execute( + """ + DROP VIEW gn_synthese.v_synthese_for_web_app; + """ + ) + + op.execute( + """ + CREATE VIEW gn_synthese.v_synthese_for_web_app AS + SELECT s.id_synthese, + s.unique_id_sinp, + s.unique_id_sinp_grp, + s.id_source, + s.entity_source_pk_value, + s.count_min, + s.count_max, + s.nom_cite, + s.meta_v_taxref, + s.sample_number_proof, + s.digital_proof, + s.non_digital_proof, + s.altitude_min, + s.altitude_max, + s.depth_min, + s.depth_max, + s.place_name, + s."precision", + s.the_geom_4326, + st_asgeojson(s.the_geom_4326) AS st_asgeojson, + s.date_min, + s.date_max, + s.validator, + s.validation_comment, + s.observers, + s.id_digitiser, + s.determiner, + s.comment_context, + s.comment_description, + s.meta_validation_date, + s.meta_create_date, + s.meta_update_date, + s.last_action, + d.id_dataset, + d.dataset_name, + d.id_acquisition_framework, + s.id_nomenclature_geo_object_nature, + s.id_nomenclature_info_geo_type, + s.id_nomenclature_grp_typ, + s.grp_method, + s.id_nomenclature_obs_technique, + s.id_nomenclature_bio_status, + s.id_nomenclature_bio_condition, + s.id_nomenclature_naturalness, + s.id_nomenclature_exist_proof, + s.id_nomenclature_valid_status, + s.id_nomenclature_diffusion_level, + s.id_nomenclature_life_stage, + s.id_nomenclature_sex, + s.id_nomenclature_obj_count, + s.id_nomenclature_type_count, + s.id_nomenclature_sensitivity, + s.id_nomenclature_observation_status, + s.id_nomenclature_blurring, + s.id_nomenclature_source_status, + s.id_nomenclature_determination_method, + s.id_nomenclature_behaviour, + s.reference_biblio, + sources.name_source, + sources.url_source, + t.cd_nom, + t.cd_ref, + t.nom_valide, + t.lb_nom, + t.nom_vern, + s.id_module, + t.group1_inpn, + t.group2_inpn, + t.group3_inpn, + s.id_import + FROM gn_synthese.synthese s + JOIN taxonomie.taxref t ON t.cd_nom = s.cd_nom + JOIN gn_meta.t_datasets d ON d.id_dataset = s.id_dataset + JOIN gn_synthese.t_sources sources ON sources.id_source = s.id_source; + """ + ) + + +def downgrade(): + conn = op.get_bind() + metadata = sa.MetaData(bind=conn) + t_sources = sa.Table("t_sources", metadata, schema="gn_synthese", autoload_with=conn) + t_modules = sa.Table("t_modules", metadata, schema="gn_commons", autoload_with=conn) + t_synthese = sa.Table("synthese", metadata, schema="gn_synthese", autoload_with=conn) + query = ( + sa.select( + (sa.func.concat("Import(id=", t_synthese.c.id_import, ")")).label("name_source"), + ( + sa.func.concat("Imported data from import module (id=", t_synthese.c.id_import, ")") + ).label("desc_source"), + (sa.literal("entity_source_pk_value")).label("entity_source_pk_field"), + ((sa.select(t_modules.c.id_module).where(t_modules.c.module_code == "IMPORT"))).label( + "id_module" + ), + ) + .where(t_synthese.c.id_import != None) + .distinct(t_synthese.c.id_import) + ) + conn.execute( + t_sources.insert().from_select( + ["name_source", "desc_source", "entity_source_pk_field", "id_module"], query + ) + ) + conn.execute( + sa.update(t_synthese) + .where(t_synthese.c.id_import != None) + .where(t_sources.c.name_source == sa.func.concat("Import(id=", t_synthese.c.id_import, ")")) + .values(id_import=None, id_source=t_sources.c.id_source) + ) + op.execute(t_sources.delete().where(t_sources.c.name_source == "Import")) + + op.execute( + """ + DROP VIEW gn_synthese.v_synthese_for_web_app; + """ + ) + + op.drop_column( + schema="gn_synthese", + table_name="synthese", + column_name="id_import", + ) + op.execute( + """ + CREATE VIEW gn_synthese.v_synthese_for_web_app AS + SELECT s.id_synthese, + s.unique_id_sinp, + s.unique_id_sinp_grp, + s.id_source, + s.entity_source_pk_value, + s.count_min, + s.count_max, + s.nom_cite, + s.meta_v_taxref, + s.sample_number_proof, + s.digital_proof, + s.non_digital_proof, + s.altitude_min, + s.altitude_max, + s.depth_min, + s.depth_max, + s.place_name, + s."precision", + s.the_geom_4326, + st_asgeojson(s.the_geom_4326) AS st_asgeojson, + s.date_min, + s.date_max, + s.validator, + s.validation_comment, + s.observers, + s.id_digitiser, + s.determiner, + s.comment_context, + s.comment_description, + s.meta_validation_date, + s.meta_create_date, + s.meta_update_date, + s.last_action, + d.id_dataset, + d.dataset_name, + d.id_acquisition_framework, + s.id_nomenclature_geo_object_nature, + s.id_nomenclature_info_geo_type, + s.id_nomenclature_grp_typ, + s.grp_method, + s.id_nomenclature_obs_technique, + s.id_nomenclature_bio_status, + s.id_nomenclature_bio_condition, + s.id_nomenclature_naturalness, + s.id_nomenclature_exist_proof, + s.id_nomenclature_valid_status, + s.id_nomenclature_diffusion_level, + s.id_nomenclature_life_stage, + s.id_nomenclature_sex, + s.id_nomenclature_obj_count, + s.id_nomenclature_type_count, + s.id_nomenclature_sensitivity, + s.id_nomenclature_observation_status, + s.id_nomenclature_blurring, + s.id_nomenclature_source_status, + s.id_nomenclature_determination_method, + s.id_nomenclature_behaviour, + s.reference_biblio, + sources.name_source, + sources.url_source, + t.cd_nom, + t.cd_ref, + t.nom_valide, + t.lb_nom, + t.nom_vern, + s.id_module, + t.group1_inpn, + t.group2_inpn, + t.group3_inpn + FROM gn_synthese.synthese s + JOIN taxonomie.taxref t ON t.cd_nom = s.cd_nom + JOIN gn_meta.t_datasets d ON d.id_dataset = s.id_dataset + JOIN gn_synthese.t_sources sources ON sources.id_source = s.id_source; + """ + ) diff --git a/backend/geonature/migrations/versions/ebbe0f7ed866_declare_taxhub_admin.py b/backend/geonature/migrations/versions/ebbe0f7ed866_declare_taxhub_admin.py new file mode 100644 index 0000000000..720d91eac2 --- /dev/null +++ b/backend/geonature/migrations/versions/ebbe0f7ed866_declare_taxhub_admin.py @@ -0,0 +1,145 @@ +"""declare module TAXHUB + +Revision ID: ebbe0f7ed866 +Revises: f1dd984bff97 +Create Date: 2023-08-02 13:15:38.542530 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "ebbe0f7ed866" +down_revision = "9f4db1786c22" +branch_labels = None +depends_on = None + +from geonature.utils.config import config + + +def upgrade(): + op.get_bind().execute( + sa.text( + """ + INSERT INTO gn_commons.t_modules + (module_code, module_label, module_picto, module_desc, module_target, module_external_url, active_frontend, active_backend) + VALUES('TAXHUB', 'TaxHub', 'fa-sitemap', 'Module TaxHub', '_blank', :module_url, false, false); + + INSERT INTO gn_permissions.t_objects + (code_object, description_object) + VALUES + ('TAXONS', 'Gestion des taxons dans TaxHub'), + ('THEMES', 'Gestion des thèmes d''attributs dans TaxHub'), + ('LISTES', 'Gestion des listes dans TaxHub'), + ('ATTRIBUTS', 'Gestion des types d''attributs dans TaxHub') + ; + + INSERT INTO gn_permissions.cor_object_module + (id_object, id_module) + SELECT _to.id_object, (SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'TAXHUB') + FROM ( + VALUES + ('TAXONS'), + ('THEMES'), + ('LISTES'), + ('ATTRIBUTS') + ) AS o (object_code) + JOIN gn_permissions.t_objects _to ON _to.code_object = o.object_code; + + + + INSERT INTO + gn_permissions.t_permissions_available ( + id_module, + id_object, + id_action, + scope_filter, + label + ) + SELECT + m.id_module, + o.id_object, + a.id_action, + v.scope_filter, + v.label + FROM ( + VALUES + ('TAXHUB', 'TAXONS', 'R', False, 'Voir les taxons') + ,('TAXHUB', 'TAXONS', 'U', False, 'Modifier les taxons (médias - listes - attributs)') + ,('TAXHUB', 'THEMES', 'C', False, 'Créer des thèmes') + ,('TAXHUB', 'THEMES', 'R', False, 'Voir les thèmes') + ,('TAXHUB', 'THEMES', 'U', False, 'Modifier les thèmes') + ,('TAXHUB', 'THEMES', 'D', False, 'Supprimer des thèmes') + ,('TAXHUB', 'LISTES', 'C', False, 'Creer des listes') + ,('TAXHUB', 'LISTES', 'R', False, 'Voir les listes') + ,('TAXHUB', 'LISTES', 'U', False, 'Modifier les listes') + ,('TAXHUB', 'LISTES', 'D', False, 'Supprimer des listes') + ,('TAXHUB', 'ATTRIBUTS', 'C', False, 'Créer des types d''attributs') + ,('TAXHUB', 'ATTRIBUTS', 'R', False, 'Voir les types d''attributs') + ,('TAXHUB', 'ATTRIBUTS', 'U', False, 'Modfier les types d''attributs') + ,('TAXHUB', 'ATTRIBUTS', 'D', False, 'Supprimer des types d''attributs') + ) AS v (module_code, object_code, action_code, scope_filter, label) + JOIN + gn_commons.t_modules m ON m.module_code = v.module_code + JOIN + gn_permissions.t_objects o ON o.code_object = v.object_code + JOIN + gn_permissions.bib_actions a ON a.code_action = v.action_code + WHERE m.module_code = 'TAXHUB' + """ + ), + module_url=f"{config['API_ENDPOINT']}/admin/taxons", + ) + # rapatriement des permissions de l'application TaxHub + + op.execute( + """ + INSERT INTO gn_permissions.t_permissions + (id_role, id_action, id_module, id_object) + SELECT crap.id_role, t.id_action, t.id_module, t.id_object + FROM + ( values ('TH', 'TAXHUB')) as v (code_appli, code_module) + JOIN gn_commons.t_modules m ON m.module_code = v.code_module + JOIN gn_permissions.t_permissions_available t on t.id_module = m.id_module + JOIN utilisateurs.t_applications app on app.code_application = v.code_appli + JOIN utilisateurs.cor_role_app_profil crap on crap.id_application = app.id_application + WHERE m.module_code = 'TAXHUB' and app.code_application = 'TH' and crap.id_profil = 6; + + INSERT INTO gn_permissions.t_permissions + (id_role, id_action, id_module, id_object) + SELECT crap.id_role, t.id_action, t.id_module, t.id_object + FROM + ( values ('TH', 'TAXHUB')) as v (code_appli, code_module) + JOIN gn_commons.t_modules m ON m.module_code = v.code_module + JOIN gn_permissions.t_permissions_available t on t.id_module = m.id_module + JOIN gn_permissions.t_objects obj on t.id_object = obj.id_object + JOIN utilisateurs.t_applications app on app.code_application = v.code_appli + JOIN utilisateurs.cor_role_app_profil crap on crap.id_application = app.id_application + WHERE + m.module_code = 'TAXHUB' + AND app.code_application = 'TH' + AND crap.id_profil in (1,2,3,4,5) + AND obj.code_object = 'TAXON'; + """ + ) + + op.execute( + """ + DELETE FROM utilisateurs.cor_role_app_profil where id_application = (select id_application from utilisateurs.t_applications t where t.code_application = 'TH' ); + DELETE FROM utilisateurs.cor_profil_for_app where id_application = (select id_application from utilisateurs.t_applications t where t.code_application = 'TH' ); + DELETE FROM utilisateurs.t_applications where code_application = 'TH'; + """ + ) + + +def downgrade(): + op.execute( + """ + DELETE FROM gn_permissions.t_permissions WHERE id_module = (SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'TAXHUB'); + DELETE FROM gn_permissions.t_permissions_available WHERE id_module = (SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'TAXHUB'); + DELETE FROM gn_commons.t_modules where module_code = 'TAXHUB'; + DELETE FROM gn_permissions.t_objects where code_object in ('TAXON', 'ATTRIBUT', 'THEME', 'LISTE'); + """ + ) diff --git a/backend/geonature/migrations/versions/imports/0e4f9da0e33f_remove_in_error_column.py b/backend/geonature/migrations/versions/imports/0e4f9da0e33f_remove_in_error_column.py new file mode 100644 index 0000000000..9c2884a1dc --- /dev/null +++ b/backend/geonature/migrations/versions/imports/0e4f9da0e33f_remove_in_error_column.py @@ -0,0 +1,46 @@ +"""remove in_error column + +Revision ID: 0e4f9da0e33f +Revises: 906231e8f8e0 +Create Date: 2022-04-25 10:51:14.746232 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "0e4f9da0e33f" +down_revision = "906231e8f8e0" +branch_labels = None +depends_on = None + + +def upgrade(): + op.drop_column( + schema="gn_imports", + table_name="t_imports", + column_name="in_error", + ) + + +def downgrade(): + op.add_column( + schema="gn_imports", + table_name="t_imports", + column=sa.Column( + "in_error", + sa.Boolean, + ), + ) + op.execute( + """ + UPDATE + gn_imports.t_imports i + SET + in_error = EXISTS ( + SELECT * FROM gn_imports.t_user_errors err WHERE err.id_import = i.id_import + ) + """ + ) diff --git a/backend/geonature/migrations/versions/imports/0ff8fc0b4233_remove_archive_schema.py b/backend/geonature/migrations/versions/imports/0ff8fc0b4233_remove_archive_schema.py new file mode 100644 index 0000000000..d065e54eef --- /dev/null +++ b/backend/geonature/migrations/versions/imports/0ff8fc0b4233_remove_archive_schema.py @@ -0,0 +1,130 @@ +"""remove_archive_schema + +Revision ID: 0ff8fc0b4233 +Revises: 699c25251384 +Create Date: 2022-05-12 09:39:45.951064 + +""" + +import sqlalchemy as sa +from sqlalchemy.schema import Table, MetaData +from sqlalchemy.exc import NoReferenceError +from alembic import op +import pandas as pd + +# revision identifiers, used by Alembic. +revision = "0ff8fc0b4233" +down_revision = "6f60b0b934b1" +branch_labels = None +depends_on = None + +archive_schema = "gn_import_archives" + + +def upgrade(): + conn = op.get_bind() + inspector = sa.inspect(conn.engine) + archive_tables = inspector.get_table_names(schema="gn_import_archives") + metadata = MetaData(bind=op.get_bind()) + imprt = Table("t_imports", metadata, autoload=True, schema="gn_imports") + + for archive_table in list(filter(lambda x: x != "cor_import_archives", archive_tables)): + # Read table with pandas + op.drop_column(archive_table, "gn_pk", schema="gn_import_archives") + arch_df = pd.read_sql_table(archive_table, con=conn, schema="gn_import_archives") + update_id = conn.execute( + imprt.update() + .where(imprt.c.import_table == archive_table) + .values( + { + "source_file": arch_df.to_csv(index=False).encode(), + "encoding": "utf-8", + "separator": ",", + } + ) + .returning(imprt.c.id_import) # To raise error if not exists + ) + if not update_id.rowcount: + raise NoReferenceError( + f"No import linked with archive table '{archive_table}'." + " Please backup (if wanted) and delete this archive table manually." + ) + op.drop_table(table_name=archive_table, schema=archive_schema) + op.execute(f"DROP SEQUENCE IF EXISTS {archive_schema}.{archive_table}_gn_pk_seq") + # Drop cor table + op.drop_table(table_name="cor_import_archives", schema=archive_schema) + op.execute(f"DROP SCHEMA {archive_schema}") + + tables = inspector.get_table_names(schema="gn_imports") + for table_name in list(filter(lambda x: x.startswith("i_"), tables)): + id_import = int(table_name.rsplit("_", 1)[-1]) + op.execute( + f""" + WITH cte AS ( + SELECT + array_agg(gn_pk::int ORDER BY gn_pk::int) erroneous_rows + FROM + gn_imports.{table_name} + WHERE + gn_is_valid = 'False' + OR + gn_invalid_reason IS NOT NULL + ) + UPDATE + gn_imports.t_imports + SET + erroneous_rows = cte.erroneous_rows + FROM + cte + WHERE + id_import = {id_import} + """ + ) + op.execute( + f""" + WITH cte AS ( + SELECT EXISTS( + SELECT + 1 + FROM + gn_imports.{table_name} + WHERE + gn_is_valid IS NOT NULL + OR + gn_invalid_reason IS NOT NULL + ) + ) + UPDATE + gn_imports.t_imports + SET + processing = cte.exists + FROM + cte + WHERE + id_import = {id_import} + """ + ) + op.drop_table(table_name=table_name, schema="gn_imports") + op.drop_column( + schema="gn_imports", + table_name="t_imports", + column_name="import_table", + ) + + +def downgrade(): + op.execute(f"CREATE SCHEMA {archive_schema}") + op.execute( + """ + CREATE TABLE gn_import_archives.cor_import_archives( + id_import integer NOT NULL, + table_archive character varying(255) NOT NULL + ); + """ + ) + op.execute( + """ + ALTER TABLE gn_imports.t_imports + ADD COLUMN import_table character varying(255) + """ + ) diff --git a/backend/geonature/migrations/versions/imports/2896cf965dd6_unique_import_error.py b/backend/geonature/migrations/versions/imports/2896cf965dd6_unique_import_error.py new file mode 100644 index 0000000000..a0b686862c --- /dev/null +++ b/backend/geonature/migrations/versions/imports/2896cf965dd6_unique_import_error.py @@ -0,0 +1,32 @@ +"""unique import error + +Revision ID: 2896cf965dd6 +Revises: d6bf8eaf088c +Create Date: 2023-09-28 10:19:10.133530 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "2896cf965dd6" +down_revision = "ea67bf7b6888" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_unique_constraint( + schema="gn_imports", + table_name="t_user_errors", + columns=["id_import", "id_error", "column_error"], + constraint_name="t_user_errors_un", + ) + + +def downgrade(): + op.drop_constraint( + schema="gn_imports", table_name="t_user_errors", constraint_name="t_user_errors_un" + ) diff --git a/backend/geonature/migrations/versions/imports/2b0b3bd0248c_multidest.py b/backend/geonature/migrations/versions/imports/2b0b3bd0248c_multidest.py new file mode 100644 index 0000000000..8effef558c --- /dev/null +++ b/backend/geonature/migrations/versions/imports/2b0b3bd0248c_multidest.py @@ -0,0 +1,576 @@ +"""multidest + +Revision ID: 2b0b3bd0248c +Revises: 2896cf965dd6 +Create Date: 2023-10-20 09:05:49.973738 + +""" + +import warnings + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.schema import Table, MetaData +from sqlalchemy.dialects.postgresql import JSON +from sqlalchemy.orm.session import Session + + +# revision identifiers, used by Alembic. +revision = "2b0b3bd0248c" +down_revision = "2896cf965dd6" +branch_labels = None +depends_on = None + + +def upgrade(): + meta = MetaData(bind=op.get_bind()) + # Rename synthese_field → dest_field + op.alter_column( + schema="gn_imports", + table_name="bib_fields", + column_name="synthese_field", + new_column_name="dest_field", + ) + # Set valid column nullable + op.alter_column( + schema="gn_imports", + table_name="t_imports_synthese", + column_name="valid", + nullable=True, + server_default=None, + ) + ### Destination + module = Table("t_modules", meta, autoload=True, schema="gn_commons") + id_module_synthese = ( + op.get_bind() + .execute(sa.select([module.c.id_module]).where(module.c.module_code == "SYNTHESE")) + .scalar() + ) + destination = op.create_table( + "bib_destinations", + sa.Column("id_destination", sa.Integer, primary_key=True), + sa.Column( + "id_module", + sa.Integer, + sa.ForeignKey("gn_commons.t_modules.id_module", ondelete="CASCADE"), + ), + sa.Column("code", sa.String(64), unique=True), + sa.Column("label", sa.String(128)), + sa.Column("table_name", sa.String(64)), + schema="gn_imports", + ) + id_dest_synthese = ( + op.get_bind() + .execute( + sa.insert(destination) + .values( + id_module=id_module_synthese, + code="synthese", + label="Synthèse", + table_name="t_imports_synthese", + ) + .returning(destination.c.id_destination) + ) + .scalar() + ) + # Add reference from fields + op.add_column( + "bib_fields", + sa.Column( + "id_destination", + sa.Integer, + sa.ForeignKey("gn_imports.bib_destinations.id_destination", ondelete="CASCADE"), + nullable=True, + ), + schema="gn_imports", + ) + field = Table("bib_fields", meta, autoload=True, schema="gn_imports") + op.execute(field.update().values({"id_destination": id_dest_synthese})) + op.alter_column( + table_name="bib_fields", column_name="id_destination", nullable=False, schema="gn_imports" + ) + # Change unique constraint to include destination + op.drop_constraint( + schema="gn_imports", + table_name="bib_fields", + constraint_name="unicity_t_mappings_fields_name_field", + ) + op.create_unique_constraint( + schema="gn_imports", + table_name="bib_fields", + columns=["id_destination", "name_field"], + constraint_name="unicity_bib_fields_dest_name_field", + ) + # Add reference from imports + op.add_column( + "t_imports", + sa.Column( + "id_destination", + sa.Integer, + sa.ForeignKey("gn_imports.bib_destinations.id_destination", ondelete="RESTRICT"), + nullable=True, + ), + schema="gn_imports", + ) + imprt = Table("t_imports", meta, autoload=True, schema="gn_imports") + op.execute(imprt.update().values({"id_destination": id_dest_synthese})) + op.alter_column( + table_name="t_imports", column_name="id_destination", nullable=False, schema="gn_imports" + ) + # Add reference from mappings + op.add_column( + "t_mappings", + sa.Column( + "id_destination", + sa.Integer, + sa.ForeignKey("gn_imports.bib_destinations.id_destination", ondelete="CASCADE"), + nullable=True, + ), + schema="gn_imports", + ) + mapping = Table("t_mappings", meta, autoload=True, schema="gn_imports") + op.execute(mapping.update().values({"id_destination": id_dest_synthese})) + op.alter_column( + table_name="t_mappings", column_name="id_destination", nullable=False, schema="gn_imports" + ) + ### Entities + entity = op.create_table( + "bib_entities", + sa.Column("id_entity", sa.Integer, primary_key=True), + sa.Column( + "id_destination", + sa.Integer, + sa.ForeignKey(destination.c.id_destination, ondelete="CASCADE"), + ), + sa.Column("code", sa.String(16)), + sa.Column("label", sa.String(64)), + sa.Column("order", sa.Integer), + sa.Column("validity_column", sa.String(64)), + sa.Column("destination_table_schema", sa.String(63)), + sa.Column("destination_table_name", sa.String(63)), + sa.Column("id_unique_column", sa.Integer, sa.ForeignKey("bib_fields.id_field")), + sa.Column( + "id_parent", + sa.Integer, + sa.ForeignKey("bib_entities.id_entity"), + ), + schema="gn_imports", + ) + bib_fields = Table("bib_fields", meta, autoload=True, schema="gn_imports") + unique_column_field_query = sa.select(bib_fields.c.id_field).where( + sa.and_( + bib_fields.c.id_destination == id_dest_synthese, + bib_fields.c.name_field == "unique_id_sinp", + ) + ) + session = Session(bind=op.get_bind()) + id_unique_column_field = session.scalar(unique_column_field_query) + + session.close() + id_entity_obs = ( + op.get_bind() + .execute( + sa.insert(entity) + .values( + id_destination=id_dest_synthese, + code="observation", + label="Observation", + order=1, + validity_column="valid", + destination_table_schema="gn_synthese", + destination_table_name="synthese", + id_unique_column=id_unique_column_field, + ) + .returning(entity.c.id_entity) + ) + .scalar() + ) + # Association fields ↔ entities + cor_entity_field = op.create_table( + "cor_entity_field", + sa.Column( + "id_entity", + sa.Integer, + sa.ForeignKey("gn_imports.bib_entities.id_entity", ondelete="CASCADE"), + primary_key=True, + ), + sa.Column( + "id_field", + sa.Integer, + sa.ForeignKey("gn_imports.bib_fields.id_field", ondelete="CASCADE"), + primary_key=True, + ), + sa.Column("desc_field", sa.String(1000)), + sa.Column( + "id_theme", sa.Integer, sa.ForeignKey("gn_imports.bib_themes.id_theme"), nullable=False + ), + sa.Column("order_field", sa.Integer, nullable=False), + sa.Column("comment", sa.String), + schema="gn_imports", + ) + op.execute( + sa.insert(cor_entity_field).from_select( + [ + "id_entity", + "id_field", + "desc_field", + "id_theme", + "order_field", + "comment", + ], + sa.select( + [ + id_entity_obs, + field.c.id_field, + field.c.desc_field, + field.c.id_theme, + field.c.order_field, + field.c.comment, + ] + ), + ) + ) + # Remove from bib_fields columns moved to cor_entity_field + for column_name in ["desc_field", "id_theme", "order_field", "comment"]: + op.drop_column(schema="gn_imports", table_name="bib_fields", column_name=column_name) + + ### Permissions + + op.execute( + """ + INSERT INTO + gn_permissions.t_permissions_available (id_module, id_object, id_action, label, scope_filter) + SELECT + m.id_module, o.id_object, a.id_action, 'Importer des observations', TRUE + FROM + gn_commons.t_modules m, + gn_permissions.t_objects o, + gn_permissions.bib_actions a + WHERE + m.module_code = 'SYNTHESE' + AND + o.code_object = 'ALL' + AND + a.code_action = 'C' + """ + ) + + op.execute( + """ + INSERT INTO + gn_permissions.t_permissions (id_role, id_module, id_object, id_action, scope_value) + SELECT + p.id_role, new_module.id_module, new_object.id_object, p.id_action, p.scope_value + FROM + gn_permissions.t_permissions p + JOIN gn_permissions.bib_actions a USING(id_action) + JOIN gn_commons.t_modules m USING(id_module) + JOIN gn_permissions.t_objects o USING(id_object) + JOIN utilisateurs.t_roles r USING(id_role), + gn_commons.t_modules new_module, + gn_permissions.t_objects new_object + WHERE + a.code_action = 'C' AND m.module_code = 'IMPORT' AND o.code_object = 'IMPORT' + AND + new_module.module_code = 'SYNTHESE' AND new_object.code_object = 'ALL'; + """ + ) + + # TODO constraint entity_field.entity.id_destination == entity_field.field.id_destination + ### Remove synthese specific 'id_source' column + op.drop_column(schema="gn_imports", table_name="t_imports", column_name="id_source_synthese") + ### Put synthese specific 'taxa_count' field in generic 'statistics' field + op.add_column( + schema="gn_imports", + table_name="t_imports", + column=sa.Column( + "statistics", + JSON, + nullable=False, + server_default=sa.text("'{}'::jsonb"), + ), + ) + op.execute( + """ + UPDATE + gn_imports.t_imports + SET + statistics = json_build_object('taxa_count', taxa_count) + WHERE + taxa_count IS NOT NULL + """ + ) + op.drop_column(schema="gn_imports", table_name="t_imports", column_name="taxa_count") + # Add new error types + error_type = Table("bib_errors_types", meta, autoload=True, schema="gn_imports") + op.execute( + sa.insert(error_type).values( + [ + { + "error_type": "Ligne orpheline", + "name": "ORPHAN_ROW", + "description": "La ligne du fichier n’a pû être rattaché à aucune entité.", + "error_level": "WARNING", + }, + { + "error_type": "Erreur de référentiel", + "name": "DATASET_NOT_FOUND", + "description": "La référence du jeu de données n’a pas été trouvé", + "error_level": "ERROR", + }, + { + "error_type": "Erreur de référentiel", + "name": "DATASET_NOT_AUTHORIZED", + "description": "Vous n’avez pas les permissions nécessaire sur le jeu de données.", + "error_level": "ERROR", + }, + { + "error_type": "Entités", + "name": "NO_PARENT_ENTITY", + "description": "Aucune entité parente identifiée.", + "error_level": "ERROR", + }, + { + "error_type": "Entités", + "name": "ERRONEOUS_PARENT_ENTITY", + "description": "L’entité parente est en erreur.", + "error_level": "ERROR", + }, + ] + ) + ) + # Remove ng_module from import + op.execute( + """ + UPDATE + gn_commons.t_modules + SET + ng_module = NULL + WHERE + module_code = 'IMPORT' + """ + ) + + ID_MODULE_IMPORT = ( + op.get_bind() + .execute( + """ + SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'IMPORT'; + """ + ) + .first()[0] + ) + ID_MODULE_SYNTHESE = ( + op.get_bind() + .execute( + """ + SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'SYNTHESE'; + """ + ) + .first()[0] + ) + + ## JDD IMPORT -> JDD Synthese + # update row with module = import to module=synthese in cor_module_dataset, only if the dataset is not already associated with a synthese + op.execute( + f""" + UPDATE gn_commons.cor_module_dataset + SET id_module = {ID_MODULE_SYNTHESE} + WHERE id_module = {ID_MODULE_IMPORT}; + """ + ) + + +def downgrade(): + + ## C IMPORT -> C Synthese + warnings.warn("!!!!! Created synthese permissions created previously will remain !!!!)") + ## JDD Synthese -> JDD Import + warnings.warn( + "Re-add association between datasets and the import module (!!!!! association between synthese and dataset created previously will remain! !!!!)" + ) + + ID_MODULE_IMPORT = ( + op.get_bind() + .execute( + """ + SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'IMPORT'; + """ + ) + .first()[0] + ) + ID_MODULE_SYNTHESE = ( + op.get_bind() + .execute( + """ + SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'SYNTHESE'; + """ + ) + .first()[0] + ) + op.execute( + f"""INSERT INTO gn_commons.cor_module_dataset (id_module, id_dataset) + SELECT {ID_MODULE_IMPORT}, id_dataset FROM gn_commons.cor_module_dataset WHERE id_module = {ID_MODULE_SYNTHESE}; + """ + ) + + meta = MetaData(bind=op.get_bind()) + # Remove new error types + error_type = Table("bib_errors_types", meta, autoload=True, schema="gn_imports") + op.execute( + sa.delete(error_type).where( + error_type.c.name.in_( + [ + "ORPHAN_ROW", + "DATASET_NOT_FOUND", + "DATASET_NOT_AUTHORIZED", + "NO_PARENT_ENTITY", + "ERRONEOUS_PARENT_ENTITY", + ] + ) + ) + ) + # Restore 'taxa_count' + op.add_column( + schema="gn_imports", + table_name="t_imports", + column=sa.Column( + "taxa_count", + sa.Integer, + nullable=True, + ), + ) + op.execute( + """ + UPDATE + gn_imports.t_imports + SET + taxa_count = (statistics ->> 'taxa_count')::integer + WHERE + to_jsonb(statistics) ? 'taxa_count' + """ + ) + op.drop_column(schema="gn_imports", table_name="t_imports", column_name="statistics") + # Restore 'id_source_synthese' + op.add_column( + schema="gn_imports", + table_name="t_imports", + column=sa.Column( + "id_source_synthese", + sa.Integer, + sa.ForeignKey("gn_synthese.t_sources.id_source"), + nullable=True, + ), + ) + op.execute( + """ + UPDATE + gn_imports.t_imports i + SET + id_source_synthese = s.id_source + FROM + gn_synthese.t_sources s + WHERE + s.id_module = (SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'IMPORT') + AND + s.name_source = 'Import(id=' || i.id_import || ')' + """ + ) + op.execute( + """ + DELETE FROM + gn_permissions.t_permissions p + USING + gn_permissions.bib_actions a, + gn_commons.t_modules m, + gn_permissions.t_objects o + WHERE + p.id_action = a.id_action AND a.code_action = 'C' + AND + p.id_module = m.id_module AND m.module_code = 'SYNTHESE' + AND + p.id_object = o.id_object AND o.code_object = 'ALL'; + """ + ) + op.execute( + """ + DELETE FROM + gn_permissions.t_permissions_available pa + USING + gn_permissions.bib_actions a, + gn_commons.t_modules m, + gn_permissions.t_objects o + WHERE + pa.id_action = a.id_action AND a.code_action = 'C' + AND + pa.id_module = m.id_module AND m.module_code = 'SYNTHESE' + AND + pa.id_object = o.id_object AND o.code_object = 'ALL'; + """ + ) + with op.batch_alter_table(schema="gn_imports", table_name="bib_fields") as batch: + batch.add_column(sa.Column("desc_field", sa.String(1000))) + batch.add_column( + sa.Column("id_theme", sa.Integer, sa.ForeignKey("gn_imports.bib_themes.id_theme")) + ) + batch.add_column(sa.Column("order_field", sa.Integer)) + batch.add_column(sa.Column("comment", sa.String)) + # Note: there should be only synthese observations fields + op.execute( + """ + UPDATE + gn_imports.bib_fields f + SET + desc_field = cef.desc_field, + id_theme = cef.id_theme, + order_field = cef.order_field, + comment = cef.comment + FROM + gn_imports.cor_entity_field cef, + gn_imports.bib_entities e, + gn_imports.bib_destinations d + WHERE + cef.id_field = f.id_field + AND + e.id_entity = cef.id_entity + AND + d.id_destination = e.id_destination + AND + d.code = 'synthese' + AND + e.code = 'observation' + """ + ) + with op.batch_alter_table(schema="gn_imports", table_name="bib_fields") as batch: + batch.alter_column(column_name="id_theme", nullable=False) + batch.alter_column(column_name="order_field", nullable=False) + op.drop_table("cor_entity_field", schema="gn_imports") + op.drop_table("bib_entities", schema="gn_imports") + op.drop_column(schema="gn_imports", table_name="t_imports", column_name="id_destination") + op.drop_column(schema="gn_imports", table_name="t_mappings", column_name="id_destination") + op.drop_constraint( + schema="gn_imports", + table_name="bib_fields", + constraint_name="unicity_bib_fields_dest_name_field", + ) + op.create_unique_constraint( + schema="gn_imports", + table_name="bib_fields", + columns=["name_field"], + constraint_name="unicity_t_mappings_fields_name_field", + ) + op.drop_column(schema="gn_imports", table_name="bib_fields", column_name="id_destination") + op.drop_table("bib_destinations", schema="gn_imports") + op.alter_column( + schema="gn_imports", + table_name="t_imports_synthese", + column_name="valid", + nullable=False, + server_default=sa.false(), + ) + op.alter_column( + schema="gn_imports", + table_name="bib_fields", + column_name="dest_field", + new_column_name="synthese_field", + ) diff --git a/backend/geonature/migrations/versions/imports/2ed6a7ee5250_add_two_columns.py b/backend/geonature/migrations/versions/imports/2ed6a7ee5250_add_two_columns.py new file mode 100644 index 0000000000..23174e7b28 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/2ed6a7ee5250_add_two_columns.py @@ -0,0 +1,47 @@ +"""Add two columns + +Revision ID: 2ed6a7ee5250 +Revises: 3a65de65b697 +Create Date: 2021-03-30 11:06:40.502478 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "2ed6a7ee5250" +down_revision = "3a65de65b697" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + """ + ALTER TABLE gn_imports.t_imports + ADD COLUMN detected_encoding VARCHAR + """ + ) + op.execute( + """ + ALTER TABLE gn_imports.t_imports + ADD COLUMN source_file BYTEA + """ + ) + + +def downgrade(): + op.execute( + """ + ALTER TABLE gn_imports.t_imports + DROP COLUMN source_file + """ + ) + op.execute( + """ + ALTER TABLE gn_imports.t_imports + DROP COLUMN detected_encoding + """ + ) diff --git a/backend/geonature/migrations/versions/imports/3a65de65b697_refactoring.py b/backend/geonature/migrations/versions/imports/3a65de65b697_refactoring.py new file mode 100644 index 0000000000..17ed1d0096 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/3a65de65b697_refactoring.py @@ -0,0 +1,159 @@ +"""Refactoring of database structure + +Revision ID: 3a65de65b697 +Revises: 4b137deaf201 +Create Date: 2021-03-29 23:02:14.880716 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "3a65de65b697" +down_revision = "4b137deaf201" +branch_labels = None +depends_on = None + + +schema = "gn_imports" + + +def upgrade(): + # Remove duplicates + op.execute( + """ + DELETE FROM gn_imports.t_mappings_fields WHERE id_match_fields IN ( + SELECT id_match_fields FROM gn_imports.t_mappings_fields tmf1 + LEFT OUTER JOIN ( + SELECT COUNT(*) AS c, MAX(id_match_fields) AS m, id_mapping, target_field + FROM gn_imports.t_mappings_fields + GROUP BY (id_mapping, target_field) + ) AS tmf2 ON tmf1.id_match_fields = tmf2.m + WHERE tmf2.m IS NULL + ) + """ + ) + op.execute( + """ + WITH unq AS ( + SELECT DISTINCT ON (tmv.id_mapping, df.name_field, tmv.source_value) tmv.id_match_values + FROM gn_imports.t_mappings_values tmv + INNER JOIN ref_nomenclatures.t_nomenclatures tn ON tn.id_nomenclature = tmv.id_target_value + INNER JOIN ref_nomenclatures.bib_nomenclatures_types bnt ON bnt.id_type = tn.id_type + INNER JOIN gn_imports.cor_synthese_nomenclature csn ON csn.mnemonique = bnt.mnemonique + INNER JOIN gn_imports.dict_fields df ON df.name_field = csn.synthese_col + ) + DELETE FROM gn_imports.t_mappings_values tmv + WHERE tmv.id_match_values NOT IN (SELECT unq.id_match_values FROM unq); + """ + ) + # Add unique constraint + op.execute( + """ + ALTER TABLE gn_imports.t_mappings_fields + ADD CONSTRAINT un_t_mappings_fields + UNIQUE (id_mapping, target_field) + """ + ) + # Add mnemonique directly on dict_fields and drop table cor_synthese_nomenclature + op.execute("ALTER TABLE gn_imports.dict_fields ADD mnemonique VARCHAR NULL") + op.execute( + """ + ALTER TABLE gn_imports.dict_fields + ADD CONSTRAINT fk_gn_imports_dict_fields_nomenclature + FOREIGN KEY (mnemonique) + REFERENCES ref_nomenclatures.bib_nomenclatures_types(mnemonique) + ON UPDATE SET NULL ON DELETE SET NULL + """ + ) + op.execute( + """ + UPDATE gn_imports.dict_fields df + SET mnemonique = csn.mnemonique + FROM gn_imports.cor_synthese_nomenclature csn + WHERE csn.synthese_col = df.name_field + """ + ) + op.execute("DROP TABLE gn_imports.cor_synthese_nomenclature") + # Set source_value NULL as it is used to map empty cell from source csv file + op.execute("ALTER TABLE gn_imports.t_mappings_values ALTER COLUMN source_value DROP NOT NULL") + # Add target_field column in gn_imports.t_mappings_values, allowing null values for now + op.execute("ALTER TABLE gn_imports.t_mappings_values ADD target_field VARCHAR NULL") + # Set target_field as foreign key referencing dict_fields + op.execute( + """ + ALTER TABLE gn_imports.t_mappings_values + ADD CONSTRAINT fk_gn_imports_t_mappings_values_target_field + FOREIGN KEY (target_field) + REFERENCES gn_imports.dict_fields(name_field) + ON UPDATE CASCADE ON DELETE CASCADE + """ + ) + # Populating target_field from id_target_value through t_nomenclatures and bib_nomenclatures_type + op.execute( + """ + UPDATE + gn_imports.t_mappings_values tmv + SET + target_field = df.name_field + FROM + gn_imports.dict_fields df + INNER JOIN ref_nomenclatures.bib_nomenclatures_types bnt ON bnt.mnemonique = df.mnemonique + INNER JOIN ref_nomenclatures.t_nomenclatures tn ON tn.id_type = bnt.id_type + WHERE + tmv.id_target_value = tn.id_nomenclature + """ + ) + # Set target_field as not null as it is now populated + op.execute("ALTER TABLE gn_imports.t_mappings_values ALTER COLUMN target_field SET NOT NULL") + # Create a function to check the consistency between target_field and id_target_value (same way we calculate it previously) + op.execute( + """ + CREATE OR REPLACE FUNCTION gn_imports.check_nomenclature_type_consistency(_target_field varchar, _id_target_value integer) + RETURNS BOOLEAN + AS $$ + BEGIN + RETURN EXISTS ( + SELECT 1 + FROM gn_imports.dict_fields df + INNER JOIN ref_nomenclatures.bib_nomenclatures_types bnt ON bnt.mnemonique = df.mnemonique + INNER JOIN ref_nomenclatures.t_nomenclatures tn ON tn.id_type = bnt.id_type + WHERE df.name_field = _target_field AND tn.id_nomenclature = _id_target_value + ); + END + $$ LANGUAGE plpgsql; + """ + ) + # Add a constraint calling the created function + op.execute( + """ + ALTER TABLE gn_imports.t_mappings_values + ADD CONSTRAINT check_nomenclature_type_consistency + CHECK (gn_imports.check_nomenclature_type_consistency(target_field, id_target_value)); + """ + ) + # Set a constraint making (id_mapping, target_field, source_value) unique + op.execute( + """ + ALTER TABLE gn_imports.t_mappings_values + ADD CONSTRAINT un_t_mappings_values + UNIQUE (id_mapping, target_field, source_value) + """ + ) + # Set nullable mapping_label and remove temporary column + op.execute("ALTER TABLE gn_imports.t_mappings ALTER COLUMN mapping_label DROP NOT NULL") + op.execute("UPDATE gn_imports.t_mappings SET mapping_label = NULL WHERE temporary = TRUE") + op.execute("ALTER TABLE gn_imports.t_mappings DROP COLUMN temporary") + op.execute("ALTER TABLE gn_imports.t_mappings ALTER COLUMN active SET DEFAULT TRUE") + # Add constraint to ensure mapping label unicity + op.execute( + "ALTER TABLE gn_imports.t_mappings ADD CONSTRAINT t_mappings_un UNIQUE (mapping_label)" + ) + # Remove errors view as we use the ORM instead + op.execute("DROP VIEW gn_imports.v_imports_errors") + + +def downgrade(): + pass diff --git a/backend/geonature/migrations/versions/imports/485a659efdcd_add_import_done_notification.py b/backend/geonature/migrations/versions/imports/485a659efdcd_add_import_done_notification.py new file mode 100644 index 0000000000..f46061f5df --- /dev/null +++ b/backend/geonature/migrations/versions/imports/485a659efdcd_add_import_done_notification.py @@ -0,0 +1,66 @@ +"""add 'IMPORT-DONE' notification + +Revision ID: 485a659efdcd +Revises: a11c9a2db7bb +Create Date: 2023-01-12 12:01:34.177079 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "485a659efdcd" +down_revision = "a11c9a2db7bb" +branch_labels = None +depends_on = ("36d0bd313a47",) + +SCHEMA_NAME = "gn_notifications" + + +def upgrade(): + # Insert the notification category 'IMPORT-DONE' + op.execute( + f""" + INSERT INTO {SCHEMA_NAME}.bib_notifications_categories + VALUES ('IMPORT-DONE', 'Import en synthèse terminé', 'Se déclenche lorsqu’un de vos imports est terminé et correctement intégré à la synthèse') + """ + ) + + # Insert templates 'EMAIL' and 'DB' for the category 'IMPORT-DONE' + op.execute( + """ + INSERT INTO gn_notifications.bib_notifications_templates + VALUES ('IMPORT-DONE', 'DB', 'Import n° {{ import.id_import }} correctement terminé et intégré dans la synthèse') + """ + ) + op.execute( + """ + INSERT INTO gn_notifications.bib_notifications_templates + VALUES ('IMPORT-DONE', 'EMAIL', '

Bonjour {{ role.nom_complet }} !

Votre import n°{{ import.id_import }} s’est terminé correctement {% if import.import_count == import.source_count %} 👌 et a été bien {% else %} 👍 mais a été partiellement {% endif %} intégré dans la synthèse.

{{ import.import_count }} / {{ import.source_count }} données ont pu être effectivement intégrées dans la synthèse.


Vous recevez cet email automatiquement via le service de notification de GeoNature. Gestion de vos règles de notification.

') + """ + ) + + +def downgrade(): + # First, remove the notifications rules corresponding to 'IMPORT-DONE' + op.execute( + f""" + DELETE FROM {SCHEMA_NAME}.t_notifications_rules WHERE code_category = 'IMPORT-DONE' + """ + ) + + # Then, Remove the notifications templates corresponding to 'IMPORT-DONE' + op.execute( + f""" + DELETE FROM {SCHEMA_NAME}.bib_notifications_templates WHERE code_category = 'IMPORT-DONE' + """ + ) + + # Lastly, Remove the notifications category 'IMPORT-DONE' + op.execute( + f""" + DELETE FROM {SCHEMA_NAME}.bib_notifications_categories WHERE code = 'IMPORT-DONE' + """ + ) diff --git a/backend/geonature/migrations/versions/imports/4b137deaf201_create_import_schema.py b/backend/geonature/migrations/versions/imports/4b137deaf201_create_import_schema.py new file mode 100644 index 0000000000..4a0fa741a7 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/4b137deaf201_create_import_schema.py @@ -0,0 +1,54 @@ +"""create_import_schema + +Revision ID: 4b137deaf201 +Revises: +Create Date: 2021-03-29 18:38:24.512562 + +""" + +from alembic import op, context +import sqlalchemy as sa +import pkg_resources +from distutils.util import strtobool + + +# revision identifiers, used by Alembic. +revision = "4b137deaf201" +down_revision = "75e78027227d" +branch_labels = None +depends_on = ("dde31e76ce45",) + + +schema = "gn_imports" +archive_schema = "gn_import_archives" + + +def upgrade(): + sql_files = ["schema.sql", "data.sql"] + if strtobool(context.get_x_argument(as_dictionary=True).get("default-mappings", "true")): + sql_files += ["default_mappings_data.sql"] + for sql_file in sql_files: + operations = pkg_resources.resource_string( + "geonature.migrations", f"data/imports/{sql_file}" + ).decode("utf-8") + op.execute(operations) + + +def downgrade(): + op.execute(f"DROP TABLE {archive_schema}.cor_import_archives") + op.execute(f"DROP SCHEMA {archive_schema}") + op.execute(f"DROP VIEW IF EXISTS {schema}.v_imports_errors") + for table in [ + "cor_role_import", + "cor_role_mapping", + "cor_synthese_nomenclature", + "t_mappings_fields", + "t_mappings_values", + "t_user_error_list", + "t_imports", + "t_mappings", + "t_user_errors", + "dict_fields", + "dict_themes", + ]: + op.execute(f"DROP TABLE IF EXISTS {schema}.{table}") diff --git a/backend/geonature/migrations/versions/imports/5158afe602d2_default_notification.py b/backend/geonature/migrations/versions/imports/5158afe602d2_default_notification.py new file mode 100644 index 0000000000..6fd4c5aa6e --- /dev/null +++ b/backend/geonature/migrations/versions/imports/5158afe602d2_default_notification.py @@ -0,0 +1,42 @@ +"""default notification + +Revision ID: 5158afe602d2 +Revises: 485a659efdcd +Create Date: 2023-03-22 16:17:51.354279 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "5158afe602d2" +down_revision = "485a659efdcd" +branch_labels = None +depends_on = ("09a637f06b96",) + + +def upgrade(): + op.execute( + """ + INSERT INTO + gn_notifications.t_notifications_rules (code_category, code_method) + VALUES + ('IMPORT-DONE', 'DB'), + ('IMPORT-DONE', 'EMAIL') + """ + ) + + +def downgrade(): + op.execute( + """ + DELETE FROM + gn_notifications.t_notifications_rules + WHERE + code_category = 'IMPORT-DONE' + AND + id_role IS NULL + """ + ) diff --git a/backend/geonature/migrations/versions/imports/5c31e356cedc_add_loaded_column.py b/backend/geonature/migrations/versions/imports/5c31e356cedc_add_loaded_column.py new file mode 100644 index 0000000000..ef301d8308 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/5c31e356cedc_add_loaded_column.py @@ -0,0 +1,58 @@ +"""add loaded column + +Revision ID: 5c31e356cedc +Revises: 0ff8fc0b4233 +Create Date: 2022-06-22 12:58:31.609964 + +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import expression + + +# revision identifiers, used by Alembic. +revision = "5c31e356cedc" +down_revision = "0ff8fc0b4233" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + schema="gn_imports", + table_name="t_imports", + column=sa.Column("loaded", sa.Boolean, nullable=False, server_default=expression.false()), + ) + op.execute( + """ + UPDATE + gn_imports.t_imports + SET + loaded = TRUE + WHERE + source_count > 0 + """ + ) + op.alter_column( + schema="gn_imports", + table_name="t_imports", + column_name="processing", + new_column_name="processed", + nullable=False, + ) + + +def downgrade(): + op.alter_column( + schema="gn_imports", + table_name="t_imports", + column_name="processed", + new_column_name="processing", + nullable=True, + ) + op.drop_column( + schema="gn_imports", + table_name="t_imports", + column_name="loaded", + ) diff --git a/backend/geonature/migrations/versions/imports/61e11414f177_add_detected_separator.py b/backend/geonature/migrations/versions/imports/61e11414f177_add_detected_separator.py new file mode 100644 index 0000000000..2b80837b97 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/61e11414f177_add_detected_separator.py @@ -0,0 +1,36 @@ +"""add detected separator + +Revision ID: 61e11414f177 +Revises: 0e4f9da0e33f +Create Date: 2022-04-14 14:03:41.842620 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "61e11414f177" +down_revision = "0e4f9da0e33f" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + schema="gn_imports", + table_name="t_imports", + column=sa.Column( + "detected_separator", + sa.Unicode, + ), + ) + + +def downgrade(): + op.drop_column( + schema="gn_imports", + table_name="t_imports", + column_name="detected_separator", + ) diff --git a/backend/geonature/migrations/versions/imports/627b7968a55b_date_min_max_too_low.py b/backend/geonature/migrations/versions/imports/627b7968a55b_date_min_max_too_low.py new file mode 100644 index 0000000000..02121ad640 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/627b7968a55b_date_min_max_too_low.py @@ -0,0 +1,35 @@ +"""date min/max too low + +Revision ID: 627b7968a55b +Revises: 699c25251384 +Create Date: 2022-05-20 14:43:24.306971 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "627b7968a55b" +down_revision = "699c25251384" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + """ + INSERT INTO gn_imports.bib_errors_types (error_type,"name",description,error_level) VALUES + ('Incohérence','DATE_MIN_TOO_LOW','La date de début est inférieur à 1900','WARNING'), + ('Incohérence','DATE_MAX_TOO_LOW','La date de fin est inférieur à 1900','WARNING') + """ + ) + + +def downgrade(): + op.execute( + """ + DELETE FROM gn_imports.bib_errors_types WHERE name in ('DATE_MIN_TOO_LOW', 'DATE_MAX_TOO_LOW') + """ + ) diff --git a/backend/geonature/migrations/versions/imports/6470a2141c83_mappings.py b/backend/geonature/migrations/versions/imports/6470a2141c83_mappings.py new file mode 100644 index 0000000000..54aca7ab66 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/6470a2141c83_mappings.py @@ -0,0 +1,503 @@ +"""mappings + +Revision ID: 6470a2141c83 +Revises: bf80cb5679be +Create Date: 2022-02-09 10:35:27.895766 + +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import JSON + + +# revision identifiers, used by Alembic. +revision = "6470a2141c83" +down_revision = "bf80cb5679be" +branch_labels = None +depends_on = None + + +def upgrade(): + ### Rename mapping columns, add Not Null + op.alter_column( + table_name="t_mappings", + column_name="id_mapping", + new_column_name="id", + schema="gn_imports", + ) + op.alter_column( + table_name="t_mappings", + column_name="mapping_label", + new_column_name="label", + schema="gn_imports", + ) + op.alter_column( + table_name="t_mappings", + column_name="mapping_type", + new_column_name="type", + schema="gn_imports", + ) + op.alter_column( + table_name="t_mappings", + column_name="is_public", + new_column_name="public", + nullable=False, + schema="gn_imports", + ) + + ### Add fieldmapping (resp. contentmapping) table with JSON values column + fieldmapping = op.create_table( + "t_fieldmappings", + sa.Column( + "id", + sa.INTEGER, + sa.ForeignKey("gn_imports.t_mappings.id", ondelete="CASCADE"), + primary_key=True, + ), + sa.Column("values", JSON), + schema="gn_imports", + ) + contentmapping = op.create_table( + "t_contentmappings", + sa.Column( + "id", + sa.INTEGER, + sa.ForeignKey("gn_imports.t_mappings.id", ondelete="CASCADE"), + primary_key=True, + ), + sa.Column("values", JSON), + schema="gn_imports", + ) + + ### Populate fieldmapping and contentmapping tables + op.execute( + """ + WITH + cte1 AS ( + SELECT + id_mapping, + json_object_agg( + target_field, + source_field + ) AS fieldmapping + FROM + gn_imports.t_mappings_fields mf + JOIN + gn_imports.dict_fields df ON df.name_field = mf.target_field + WHERE + df.autogenerated IS FALSE + AND + NOT (source_field IS NULL OR source_field = '') + GROUP BY + id_mapping + ), + cte2 AS ( + SELECT + id_mapping, + json_object_agg( + target_field, + CASE WHEN source_field = 'true' + THEN TRUE + ELSE FALSE + END + ) AS fieldmapping + FROM + gn_imports.t_mappings_fields mf + JOIN + gn_imports.dict_fields df ON df.name_field = mf.target_field + WHERE + df.autogenerated IS TRUE + GROUP BY + id_mapping + ) + INSERT INTO + gn_imports.t_fieldmappings (id, values) + SELECT + id_mapping, + cte1.fieldmapping::jsonb || cte2.fieldmapping::jsonb + FROM + cte1 + LEFT JOIN + cte2 USING(id_mapping) + """ + ) + op.execute( + """ + INSERT INTO + gn_imports.t_contentmappings (id, values) + WITH cte AS ( + SELECT + m.id_mapping, + nt.id_type, + json_object_agg(source_value, n.cd_nomenclature) AS source2nomenc + FROM + gn_imports.t_mappings_values m + JOIN + ref_nomenclatures.t_nomenclatures n ON m.id_target_value = n.id_nomenclature + JOIN + ref_nomenclatures.bib_nomenclatures_types nt ON n.id_type = nt.id_type + GROUP BY m.id_mapping, nt.id_type + ) + SELECT + id_mapping, + json_object_agg(nt.mnemonique, source2nomenc) AS json + FROM + cte + JOIN + ref_nomenclatures.bib_nomenclatures_types nt ON cte.id_type = nt.id_type + GROUP BY + id_mapping + """ + ) + op.drop_constraint( + constraint_name="fk_gn_imports_t_mappings_fields", + schema="gn_imports", + table_name="t_imports", + ) + op.drop_constraint( + constraint_name="fk_gn_import_t_mappings_values", + schema="gn_imports", + table_name="t_imports", + ) + op.execute( + """ + DELETE FROM + gn_imports.t_mappings + USING + gn_imports.t_mappings m + LEFT OUTER JOIN + gn_imports.t_fieldmappings fm ON m.id = fm.id + WHERE + gn_imports.t_mappings.id = m.id + AND + m."type" = 'FIELD' + AND + fm.id is NULL + """ + ) + op.execute( + """ + DELETE FROM + gn_imports.t_mappings + USING + gn_imports.t_mappings m + LEFT OUTER JOIN + gn_imports.t_contentmappings cm ON m.id = cm.id + WHERE + gn_imports.t_mappings.id = m.id + AND + m."type" = 'CONTENT' + AND + cm.id is NULL + """ + ) + op.drop_table("t_mappings_fields", schema="gn_imports") + op.drop_table("t_mappings_values", schema="gn_imports") + + ### Add mappings columns on import, populate them, drop old foreign key to mappings + op.add_column("t_imports", sa.Column("fieldmapping", JSON), schema="gn_imports") + op.add_column("t_imports", sa.Column("contentmapping", JSON), schema="gn_imports") + op.execute( + """ + UPDATE + gn_imports.t_imports i + SET + fieldmapping = fm.values + FROM + gn_imports.t_fieldmappings fm + WHERE + i.id_field_mapping = fm.id + """ + ) + op.execute( + """ + UPDATE + gn_imports.t_imports i + SET + contentmapping = cm.values + FROM + gn_imports.t_contentmappings cm + WHERE + i.id_content_mapping = cm.id + """ + ) + op.drop_column("t_imports", "id_field_mapping", schema="gn_imports") + op.drop_column("t_imports", "id_content_mapping", schema="gn_imports") + + # Remove unnamed mappings, set Not Null on label + op.execute( + """ + DELETE FROM + gn_imports.t_mappings + WHERE + label IS NULL + """ + ) + op.alter_column( + table_name="t_mappings", + column_name="label", + nullable=False, + schema="gn_imports", + ) + + # Set unique constraint on (label, type) + op.drop_constraint("t_mappings_un", "t_mappings", schema="gn_imports") + op.create_unique_constraint( + "t_mappings_un", "t_mappings", schema="gn_imports", columns=["label", "type"] + ) + + +def downgrade(): + # Set unique constraint on label + op.drop_constraint("t_mappings_un", "t_mappings", schema="gn_imports") + op.create_unique_constraint( + "t_mappings_un", "t_mappings", schema="gn_imports", columns=["label"] + ) + + # Remove Not Null on label for temporary mappings + op.alter_column( + table_name="t_mappings", + column_name="label", + nullable=True, + schema="gn_imports", + ) + + ### Create an mapping for each import + op.add_column( + "t_imports", + sa.Column( + "id_field_mapping", + sa.Integer, + sa.ForeignKey("gn_imports.t_mappings.id", onupdate="CASCADE", ondelete="NO ACTION"), + ), + schema="gn_imports", + ) + op.add_column( + "t_imports", + sa.Column( + "id_content_mapping", + sa.Integer, + sa.ForeignKey("gn_imports.t_mappings.id", onupdate="CASCADE", ondelete="NO ACTION"), + ), + schema="gn_imports", + ) + op.execute( + """ + DO $$ + DECLARE + _id_mapping INTEGER; + _id_import INTEGER; + BEGIN + FOR _id_import IN + SELECT + i.id_import + FROM + gn_imports.t_imports i + WHERE + i.fieldmapping IS NOT NULL + LOOP + INSERT INTO + gn_imports.t_mappings ("type" , "active", "public") + VALUES + ('FIELD', TRUE, FALSE) + RETURNING + id + INTO + _id_mapping; + + UPDATE + gn_imports.t_imports i + SET + id_field_mapping = _id_mapping + WHERE + i.id_import = _id_import; + + INSERT INTO + gn_imports.t_fieldmappings ("id", "values") + SELECT + id_field_mapping, + fieldmapping + FROM + gn_imports.t_imports + WHERE + id_import = _id_import; + END LOOP; + END $$; + """ + ) + op.execute( + """ + DO $$ + DECLARE + _id_mapping INTEGER; + _id_import INTEGER; + BEGIN + FOR _id_import IN + SELECT + i.id_import + FROM + gn_imports.t_imports i + WHERE + i.contentmapping IS NOT NULL + LOOP + INSERT INTO + gn_imports.t_mappings ("type" , "active", "public") + VALUES + ('CONTENT', TRUE, FALSE) + RETURNING + id + INTO + _id_mapping; + + UPDATE + gn_imports.t_imports i + SET + id_content_mapping = _id_mapping + WHERE + i.id_import = _id_import; + + INSERT INTO + gn_imports.t_contentmappings ("id", "values") + SELECT + id_content_mapping, + contentmapping + FROM + gn_imports.t_imports + WHERE + id_import = _id_import; + END LOOP; + END $$; + """ + ) + + op.drop_column("t_imports", "fieldmapping", schema="gn_imports") + op.drop_column("t_imports", "contentmapping", schema="gn_imports") + + op.create_table( + "t_mappings_fields", + sa.Column("id_match_fields", sa.Integer, primary_key=True), + sa.Column( + "id_mapping", + sa.Integer, + sa.ForeignKey("gn_imports.t_mappings.id", onupdate="CASCADE", ondelete="CASCADE"), + ), + sa.Column("source_field", sa.Unicode(255)), + sa.Column( + "target_field", + sa.Unicode(255), + sa.ForeignKey( + "gn_imports.dict_fields.name_field", onupdate="CASCADE", ondelete="CASCADE" + ), + ), + schema="gn_imports", + ) + op.create_table( + "t_mappings_values", + sa.Column("id_match_fields", sa.Integer, primary_key=True), + sa.Column( + "id_mapping", + sa.Integer, + sa.ForeignKey("gn_imports.t_mappings.id", onupdate="CASCADE", ondelete="CASCADE"), + ), + sa.Column("source_value", sa.Unicode(255)), + sa.Column( + "id_target_value", + sa.Integer, + sa.ForeignKey( + "ref_nomenclatures.t_nomenclatures.id_nomenclature", + onupdate="CASCADE", + ondelete="CASCADE", + ), + ), + schema="gn_imports", + ) + + op.execute( + """ + WITH cte AS ( + SELECT + id AS id_mapping, + (JSON_EACH_TEXT("values")).* + FROM + gn_imports.t_fieldmappings + ) + INSERT INTO + gn_imports.t_mappings_fields ("id_mapping", "target_field", "source_field") + SELECT + id_mapping, + key, + value + FROM + cte + JOIN + gn_imports.dict_fields f ON f.name_field = cte.key + """ + ) + + op.execute( + """ + WITH outercte AS ( + WITH innercte AS ( + SELECT + id AS id_mapping, + (JSON_EACH("values")).* + FROM + gn_imports.t_contentmappings + ) + SELECT + id_mapping, + "key" AS type_mnemonique, + (JSON_EACH_TEXT("value")).* + FROM + innercte + ) + INSERT INTO + gn_imports.t_mappings_values ("id_mapping", "source_value", "id_target_value") + SELECT + id_mapping, + "key" AS source_value, + n.id_nomenclature AS id_target_value + FROM + outercte + JOIN + ref_nomenclatures.bib_nomenclatures_types nt + ON + outercte.type_mnemonique = nt.mnemonique + JOIN + ref_nomenclatures.t_nomenclatures n + ON + outercte.value = n.cd_nomenclature + AND + nt.id_type = n.id_type + """ + ) + + op.drop_table("t_fieldmappings", schema="gn_imports") + op.drop_table("t_contentmappings", schema="gn_imports") + + op.alter_column( + table_name="t_mappings", + column_name="id", + new_column_name="id_mapping", + schema="gn_imports", + ) + op.alter_column( + table_name="t_mappings", + column_name="label", + new_column_name="mapping_label", + schema="gn_imports", + ) + op.alter_column( + table_name="t_mappings", + column_name="type", + new_column_name="mapping_type", + schema="gn_imports", + ) + op.alter_column( + table_name="t_mappings", + column_name="public", + new_column_name="is_public", + nullable=True, + schema="gn_imports", + ) diff --git a/backend/geonature/migrations/versions/imports/65defbe5027b_set_id_module_on_import_sources.py b/backend/geonature/migrations/versions/imports/65defbe5027b_set_id_module_on_import_sources.py new file mode 100644 index 0000000000..62e4628fbf --- /dev/null +++ b/backend/geonature/migrations/versions/imports/65defbe5027b_set_id_module_on_import_sources.py @@ -0,0 +1,43 @@ +"""set id_module on import sources + +Revision ID: 65defbe5027b +Revises: f394a5edcb56 +Create Date: 2022-07-05 18:09:53.133560 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "65defbe5027b" +down_revision = "f394a5edcb56" +branch_labels = None +depends_on = ("f4ffdc68072c",) # add id_module in t_sources + + +def upgrade(): + op.execute( + """ + UPDATE + gn_synthese.t_sources + SET + id_module = (SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'IMPORT') + WHERE + name_source LIKE 'Import(id=%)' + """ + ) + + +def downgrade(): + op.execute( + """ + UPDATE + gn_synthese.t_sources + SET + id_module = NULL + WHERE + id_module = (SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'IMPORT') + """ + ) diff --git a/backend/geonature/migrations/versions/imports/681062ef2939_add_date_error.py b/backend/geonature/migrations/versions/imports/681062ef2939_add_date_error.py new file mode 100644 index 0000000000..8c72edd8f8 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/681062ef2939_add_date_error.py @@ -0,0 +1,37 @@ +"""add date error + +Revision ID: 681062ef2939 +Revises: eb217f32d7d7 +Create Date: 2022-05-10 12:42:31.793379 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "681062ef2939" +down_revision = "eb217f32d7d7" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + """ + INSERT INTO gn_imports.dict_errors (error_type,"name",description,error_level) VALUES + ('Date invalide','DATE_MIN_TOO_HIGH','La date de début est dans le futur ','ERROR'), + ('Date invalide','DATE_MAX_TOO_HIGH','La date de fin est dans le futur ','ERROR') + ; + """ + ) + + +def downgrade(): + op.execute( + """ + DELETE FROM gn_imports.dict_errors WHERE name = 'DATE_MIN_TOO_HIGH' OR name = 'DATE_MAX_TOO_HIGH' + ; + """ + ) diff --git a/backend/geonature/migrations/versions/imports/699c25251384_update_dict_fields.py b/backend/geonature/migrations/versions/imports/699c25251384_update_dict_fields.py new file mode 100644 index 0000000000..f76f1bf951 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/699c25251384_update_dict_fields.py @@ -0,0 +1,156 @@ +"""Update fields and themes + +Revision ID: 699c25251384 +Revises: 681062ef2939 +Create Date: 2022-05-10 20:18:37.214323 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "699c25251384" +down_revision = "681062ef2939" +branch_labels = None +depends_on = None + + +def upgrade(): + op.rename_table( + old_table_name="dict_fields", + new_table_name="bib_fields", + schema="gn_imports", + ) + op.rename_table( + old_table_name="dict_themes", + new_table_name="bib_themes", + schema="gn_imports", + ) + op.rename_table( + old_table_name="dict_errors", + new_table_name="bib_errors_types", + schema="gn_imports", + ) + + op.execute( + """ + DELETE FROM + gn_imports.bib_fields + WHERE + name_field = 'id_nomenclature_info_geo_type' + """ + ) + op.execute( + """ + UPDATE + gn_imports.t_fieldmappings + SET + values = values::jsonb - 'id_nomenclature_info_geo_type' + """ + ) + op.execute( + """ + UPDATE + gn_imports.t_contentmappings + SET + values = values::jsonb - 'TYP_INF_GEO' + """ + ) + + op.execute( + """ + UPDATE + gn_imports.bib_fields + SET + fr_label = 'Générer les identifiants SINP manquants' + WHERE + name_field = 'unique_id_sinp_generate' + """ + ) + op.execute( + """ + UPDATE + gn_imports.bib_fields + SET + fr_label = 'Générer les altitudes manquantes' + WHERE + name_field = 'altitudes_generate' + """ + ) + + +def downgrade(): + op.execute( + """ + UPDATE + gn_imports.bib_fields + SET + fr_label = E'Générer l\\'identifiant SINP' + WHERE + name_field = 'unique_id_sinp_generate' + """ + ) + op.execute( + """ + UPDATE + gn_imports.bib_fields + SET + fr_label = 'Générer les altitudes' + WHERE + name_field = 'altitudes_generate' + """ + ) + + op.execute( + """ + INSERT INTO + gn_imports.bib_fields + ( + name_field, + fr_label, + type_field, + mandatory, + autogenerated, + id_theme, + order_field, + display, + comment, + mnemonique, + source_field, + synthese_field + ) + VALUES + ( + 'id_nomenclature_info_geo_type', + E'Type d\\'information géographique', + 'integer', + FALSE, + FALSE, + (SELECT id_theme FROM gn_imports.bib_themes WHERE name_theme = 'statement_info'), + 13, + TRUE, + 'Correspondance champs standard: typeInfoGeo', + 'TYP_INF_GEO', + 'src_id_nomenclature_info_geo_type', + 'id_nomenclature_info_geo_type' + ) + """ + ) + + op.rename_table( + new_table_name="dict_fields", + old_table_name="bib_fields", + schema="gn_imports", + ) + op.rename_table( + new_table_name="dict_themes", + old_table_name="bib_themes", + schema="gn_imports", + ) + op.rename_table( + new_table_name="dict_errors", + old_table_name="bib_errors_types", + schema="gn_imports", + ) diff --git a/backend/geonature/migrations/versions/imports/6e1852ecfea2_add_column_unique_dataset_id_to_t_.py b/backend/geonature/migrations/versions/imports/6e1852ecfea2_add_column_unique_dataset_id_to_t_.py new file mode 100644 index 0000000000..44d485f2f6 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/6e1852ecfea2_add_column_unique_dataset_id_to_t_.py @@ -0,0 +1,219 @@ +"""add column unique_dataset_id to t_imports_synthese and insert into bib_fields and cor_entity_field + +Revision ID: 6e1852ecfea2 +Revises: 8b149244d586 +Create Date: 2024-03-04 12:31:00.861460 + +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.schema import Table, MetaData + +# revision identifiers, used by Alembic. +revision = "6e1852ecfea2" +down_revision = "8b149244d586" +branch_labels = None +depends_on = None + + +def upgrade(): + meta = MetaData(bind=op.get_bind()) + + # Add columns to t_imports_synthese table + with op.batch_alter_table("t_imports_synthese", schema="gn_imports") as batch_op: + batch_op.add_column(sa.Column("src_unique_dataset_id", sa.String)) + batch_op.add_column(sa.Column("unique_dataset_id", UUID(as_uuid=True))) + batch_op.add_column(sa.Column("id_dataset", sa.Integer)) + # Fetch id_destination for 'synthese' from bib_destinations table + destination = Table("bib_destinations", meta, autoload=True, schema="gn_imports") + id_dest_synthese = ( + op.get_bind() + .execute(sa.select([destination.c.id_destination]).where(destination.c.code == "synthese")) + .scalar() + ) + # Fetch id_entity_observation for id_destination from bib_entities table + entity = Table("bib_entities", meta, autoload=True, schema="gn_imports") + id_entity_observation = ( + op.get_bind() + .execute(sa.select([entity.c.id_entity]).where(entity.c.id_destination == id_dest_synthese)) + .scalar() + ) + + # Fetch id_theme_general from bib_themes table + theme = Table("bib_themes", meta, autoload=True, schema="gn_imports") + id_theme_general = ( + op.get_bind() + .execute(sa.select([theme.c.id_theme]).where(theme.c.name_theme == "general_info")) + .scalar() + ) + + # Fetch id_field for 'unique_dataset_id' from bib_fields table + field = Table("bib_fields", meta, autoload=True, schema="gn_imports") + list_field_to_insert = [ + ( + { + "name_field": "unique_dataset_id", + "fr_label": "Identifiant JDD (UUID)", + "mandatory": False, + "autogenerated": False, + "display": False, + "mnemonique": None, + "source_field": "src_unique_dataset_id", + "dest_field": "unique_dataset_id", + }, + { + id_entity_observation: { + "id_theme": id_theme_general, + "order_field": 3, + "comment": "Correspondance champs standard: metadonneeId ou jddMetaId", + }, + }, + ), + ( + { + "name_field": "id_dataset", + "fr_label": "Identifiant JDD", + "mandatory": False, + "autogenerated": False, + "display": False, + "mnemonique": None, + "source_field": None, + "dest_field": "id_dataset", + }, + { + id_entity_observation: { + "id_theme": id_theme_general, + "order_field": 3, + "comment": "", + }, + }, + ), + ] + # insert_data = {"id_destination": id_dest_synthese, **field_unique_dataset_id_info} + + id_fields = [ + id_field + for id_field, in op.get_bind() + .execute( + sa.insert(field) + .values( + [{"id_destination": id_dest_synthese, **field} for field, _ in list_field_to_insert] + ) + .returning(field.c.id_field) + ) + .fetchall() + ] + + # Insert data into cor_entity_field table + cor_entity_field = Table("cor_entity_field", meta, autoload=True, schema="gn_imports") + cor_entity_field = Table("cor_entity_field", meta, autoload=True, schema="gn_imports") + op.execute( + sa.insert(cor_entity_field).values( + [ + {"id_entity": id_entity, "id_field": id_field, **props} + for id_field, field_entities in zip(id_fields, list_field_to_insert) + for id_entity, props in field_entities[1].items() + ] + ) + ) + + # Update model contentmapping to add unique_dataset_id + t_mappings = Table("t_mappings", meta, autoload=True, schema="gn_imports") + + id_t_mapping_synthese = ( + op.get_bind() + .execute(sa.select([t_mappings.c.id]).where(t_mappings.c.label == "Synthese GeoNature")) + .scalar() + ) + + update_query = sa.text( + """ + UPDATE gn_imports.t_fieldmappings + SET values = values::jsonb || '{"unique_dataset_id": "unique_dataset_id"}'::jsonb + WHERE id = :id_t_mapping_synthese + """ + ) + + op.get_bind().execute(update_query, id_t_mapping_synthese=id_t_mapping_synthese) + + +def downgrade(): + meta = MetaData(bind=op.get_bind()) + + # Drop columns from t_imports_synthese table + with op.batch_alter_table("t_imports_synthese", schema="gn_imports") as batch_op: + batch_op.drop_column("unique_dataset_id") + batch_op.drop_column("src_unique_dataset_id") + batch_op.drop_column("id_dataset") + + # Fetch id_destination for 'synthese' from bib_destinations table + destination = Table("bib_destinations", meta, autoload=True, schema="gn_imports") + id_dest_synthese = ( + op.get_bind() + .execute(sa.select([destination.c.id_destination]).where(destination.c.code == "synthese")) + .scalar() + ) + + # Fetch id_entity_observation for id_destination from bib_entities table + entity = Table("bib_entities", meta, autoload=True, schema="gn_imports") + id_entity_observation = ( + op.get_bind() + .execute(sa.select([entity.c.id_entity]).where(entity.c.id_destination == id_dest_synthese)) + .scalar() + ) + + # Fetch id_fields inserted into bib_fields table + field = Table("bib_fields", meta, autoload=True, schema="gn_imports") + id_fields = ( + op.get_bind() + .execute( + sa.select([field.c.id_field]).where( + sa.or_( + sa.and_( + field.c.name_field == "unique_dataset_id", + field.c.id_destination == id_dest_synthese, + ), + sa.and_( + field.c.name_field == "id_dataset", + field.c.id_destination == id_dest_synthese, + ), + ) + ) + ) + .scalars() + .all() + ) + + # Delete rows from cor_entity_field based on matching list of id_fields + cor_entity_field = Table("cor_entity_field", meta, autoload=True, schema="gn_imports") + op.execute( + cor_entity_field.delete().where( + sa.and_( + cor_entity_field.c.id_entity == id_entity_observation, + cor_entity_field.c.id_field.in_(id_fields), + ) + ) + ) + + op.execute(field.delete().where(field.c.id_field.in_(id_fields))) + + t_mappings = Table("t_mappings", meta, autoload=True, schema="gn_imports") + + # Get the ID of the "Synthese GeoNature" mapping + id_t_mapping_synthese = ( + op.get_bind() + .execute(sa.select([t_mappings.c.id]).where(t_mappings.c.label == "Synthese GeoNature")) + .scalar() + ) + + revert_query = sa.text( + """ + UPDATE gn_imports.t_fieldmappings + SET values = values::jsonb - 'unique_dataset_id' + WHERE id = :id_t_mapping_synthese + """ + ) + + op.get_bind().execute(revert_query, id_t_mapping_synthese=id_t_mapping_synthese) diff --git a/backend/geonature/migrations/versions/imports/6f60b0b934b1_erroneous_rows.py b/backend/geonature/migrations/versions/imports/6f60b0b934b1_erroneous_rows.py new file mode 100644 index 0000000000..0ce18e2602 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/6f60b0b934b1_erroneous_rows.py @@ -0,0 +1,62 @@ +"""erroneous rows + +Revision ID: 6f60b0b934b1 +Revises: 0ff8fc0b4233 +Create Date: 2022-06-20 17:48:33.848166 + +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.types import ARRAY + + +# revision identifiers, used by Alembic. +revision = "6f60b0b934b1" +down_revision = "cadfdaa42430" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + schema="gn_imports", + table_name="t_imports", + column=sa.Column( + "erroneous_rows", + ARRAY(sa.Integer), + ), + ) + op.execute( + """ + WITH cte AS ( + SELECT + id_import, + array_agg(line_no ORDER BY line_no) erroneous_rows + FROM + gn_imports.t_imports_synthese + WHERE + valid = FALSE + GROUP BY + id_import + ) + UPDATE + gn_imports.t_imports i + SET + erroneous_rows = cte.erroneous_rows + FROM + cte + WHERE + i.id_import = cte.id_import + AND + i.processing = TRUE + """ + ) + + +def downgrade(): + op.drop_column( + schema="gn_imports", + table_name="t_imports", + column_name="erroneous_rows", + ) diff --git a/backend/geonature/migrations/versions/imports/74058f69828a_remove_t_imports_is_finished.py b/backend/geonature/migrations/versions/imports/74058f69828a_remove_t_imports_is_finished.py new file mode 100644 index 0000000000..0cb7f3b13f --- /dev/null +++ b/backend/geonature/migrations/versions/imports/74058f69828a_remove_t_imports_is_finished.py @@ -0,0 +1,49 @@ +"""remove t_imports.is_finished + +Revision ID: 74058f69828a +Revises: 61e11414f177 +Create Date: 2022-04-27 13:51:46.622094 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "74058f69828a" +down_revision = "61e11414f177" +branch_labels = None +depends_on = None + + +def upgrade(): + op.drop_column( + schema="gn_imports", + table_name="t_imports", + column_name="is_finished", + ) + + +def downgrade(): + op.add_column( + schema="gn_imports", + table_name="t_imports", + column=sa.Column( + "is_finished", + sa.Boolean, + ), + ) + op.execute( + """ + UPDATE + gn_imports.t_imports i + SET + is_finished = EXISTS ( + SELECT * + FROM gn_synthese.synthese synthese + JOIN gn_synthese.t_sources source ON synthese.id_source = source.id_source + WHERE source.name_source = 'Import(id=' || i.id_import || ')' + ) + """ + ) diff --git a/backend/geonature/migrations/versions/imports/75e78027227d_import_module.py b/backend/geonature/migrations/versions/imports/75e78027227d_import_module.py new file mode 100644 index 0000000000..9a293a535e --- /dev/null +++ b/backend/geonature/migrations/versions/imports/75e78027227d_import_module.py @@ -0,0 +1,50 @@ +"""add import module + +Revision ID: 75e78027227d +Revises: +Create Date: 2023-12-18 + +""" + +from alembic import op + + +# revision identifiers, used by Alembic. +revision = "75e78027227d" +down_revision = None +branch_labels = ("import",) +depends_on = None + + +schema = "gn_imports" +archive_schema = "gn_import_archives" + + +def upgrade(): + op.execute( + """ + INSERT INTO + gn_commons.t_modules ( + module_code, + module_label, + module_picto, + module_path, + module_target, + active_frontend, + active_backend + ) + VALUES ( + 'IMPORT', + 'Import', + 'fa-upload', + 'import', + '_self', + TRUE, + TRUE + ) + """ + ) + + +def downgrade(): + op.execute("DELETE FROM gn_commons.t_modules WHERE module_code = 'IMPORT'") diff --git a/backend/geonature/migrations/versions/imports/75f0f9906bf1_add_columns.py b/backend/geonature/migrations/versions/imports/75f0f9906bf1_add_columns.py new file mode 100644 index 0000000000..1ad8738941 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/75f0f9906bf1_add_columns.py @@ -0,0 +1,81 @@ +"""add_columns + +Revision ID: 75f0f9906bf1 +Revises: 2ed6a7ee5250 +Create Date: 2021-04-27 10:02:53.798753 + +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.types import ARRAY + + +# revision identifiers, used by Alembic. +revision = "75f0f9906bf1" +down_revision = "2ed6a7ee5250" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + table_name="t_imports", + column=sa.Column( + "columns", + ARRAY(sa.Unicode), + ), + schema="gn_imports", + ) + op.execute( + """ + UPDATE gn_imports.t_user_error_list + SET id_rows = ARRAY(SELECT generate_series(1, gn_imports.t_imports.source_count)) + FROM gn_imports.t_imports + WHERE + gn_imports.t_user_error_list.id_import = gn_imports.t_imports.id_import + AND gn_imports.t_user_error_list.id_rows=ARRAY['ALL']; + """ + ) + op.execute( + """ + ALTER TABLE gn_imports.t_user_error_list + ALTER COLUMN id_rows TYPE integer[] USING id_rows::integer[] + """ + ) + op.drop_column( + table_name="t_mappings_fields", + column_name="is_added", + schema="gn_imports", + ) + op.drop_column( + table_name="t_mappings_fields", + column_name="is_selected", + schema="gn_imports", + ) + + +def downgrade(): + op.execute( + """ + ALTER TABLE gn_imports.t_mappings_fields + ADD COLUMN is_selected BOOLEAN NOT NULL DEFAULT TRUE + """ + ) + op.execute( + """ + ALTER TABLE gn_imports.t_mappings_fields + ADD COLUMN is_added BOOLEAN NOT NULL DEFAULT FALSE + """ + ) + op.execute( + """ + ALTER TABLE gn_imports.t_user_error_list + ALTER COLUMN id_rows TYPE text[] USING id_rows::text[] + """ + ) + op.drop_column( + table_name="t_imports", + column_name="columns", + schema="gn_imports", + ) diff --git a/backend/geonature/migrations/versions/imports/8611f7aab8dc_allow_multi_select_mapping.py b/backend/geonature/migrations/versions/imports/8611f7aab8dc_allow_multi_select_mapping.py new file mode 100644 index 0000000000..fdac350082 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/8611f7aab8dc_allow_multi_select_mapping.py @@ -0,0 +1,49 @@ +"""allow multi select mapping + +Revision ID: 8611f7aab8dc +Revises: a89a99f68203 +Create Date: 2023-07-27 11:18:00.424394 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "8611f7aab8dc" +down_revision = "a89a99f68203" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + """ + ALTER TABLE + gn_imports.bib_fields + ADD COLUMN + multi BOOLEAN NOT NULL DEFAULT FALSE + """ + ) + op.execute( + """ + UPDATE + gn_imports.bib_fields + SET + multi = TRUE + WHERE + name_field = 'additional_data' + """ + ) + + +def downgrade(): + op.execute( + """ + ALTER TABLE + gn_imports.bib_fields + DROP COLUMN + multi + """ + ) diff --git a/backend/geonature/migrations/versions/imports/906231e8f8e0_remove_temporary_tables.py b/backend/geonature/migrations/versions/imports/906231e8f8e0_remove_temporary_tables.py new file mode 100644 index 0000000000..09088d6c98 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/906231e8f8e0_remove_temporary_tables.py @@ -0,0 +1,481 @@ +"""remove temporary tables + +Revision ID: 906231e8f8e0 +Revises: 6470a2141c83 +Create Date: 2022-03-31 12:38:11.170056 + +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.types import ARRAY +from sqlalchemy.dialects.postgresql import HSTORE, JSONB, UUID +from geoalchemy2 import Geometry + + +# revision identifiers, used by Alembic. +revision = "906231e8f8e0" +down_revision = "6470a2141c83" +branch_labels = None +depends_on = None + + +def upgrade(): + op.drop_column(table_name="dict_fields", column_name="nomenclature", schema="gn_imports") + + op.alter_column( + table_name="dict_fields", + column_name="synthese_field", + new_column_name="is_synthese_field", + schema="gn_imports", + ) + op.add_column( + table_name="dict_fields", + column=sa.Column( + "source_field", + sa.Unicode, + ), + schema="gn_imports", + ) + op.add_column( + table_name="dict_fields", + column=sa.Column( + "synthese_field", + sa.Unicode, + ), + schema="gn_imports", + ) + op.execute( + """ + WITH cte as ( + SELECT + column_name, + data_type + FROM + information_schema.columns + WHERE + table_schema = 'gn_synthese' + AND + table_name = 'synthese' + ) + UPDATE + gn_imports.dict_fields f + SET + source_field = 'src_' || f.name_field + FROM + cte + WHERE + ( + f.is_synthese_field IS FALSE + AND + autogenerated IS FALSE + ) + OR + f.mnemonique IS NOT NULL + OR + ( + cte.column_name = f.name_field + AND + cte.data_type NOT IN ('text', 'character', 'character varying', 'jsonb') + AND + f.name_field NOT IN ('the_geom_4326', 'the_geom_point', 'the_geom_local') + ) + """ + ) + op.drop_constraint( + constraint_name="chk_mandatory", + table_name="dict_fields", + schema="gn_imports", + ) + op.execute( + """ + UPDATE + gn_imports.dict_fields + SET + synthese_field = name_field + WHERE + is_synthese_field IS TRUE + """ + ) + op.execute( + """ + INSERT INTO + gn_imports.dict_fields + ( + name_field, + fr_label, + "comment", + source_field, + synthese_field, + is_synthese_field, + mandatory, + autogenerated, + display, + id_theme, + order_field + ) + VALUES + ( + 'datetime_min', + 'Date début', + 'Date de début de l’observation.', + 'date_min', + 'date_min', + TRUE, + FALSE, + FALSE, + FALSE, + (SELECT id_theme FROM gn_imports.dict_fields WHERE name_field = 'date_min'), + (SELECT order_field FROM gn_imports.dict_fields WHERE name_field = 'date_min') + ), + ( + 'datetime_max', + 'Date fin', + 'Date de fin de l’observation.', + 'date_max', + 'date_max', + TRUE, + FALSE, + FALSE, + FALSE, + (SELECT id_theme FROM gn_imports.dict_fields WHERE name_field = 'date_max'), + (SELECT order_field FROM gn_imports.dict_fields WHERE name_field = 'date_max') + ) + """ + ) + op.execute( + """ + UPDATE + gn_imports.dict_fields + SET + is_synthese_field = FALSE, + synthese_field = NULL + WHERE + name_field IN ('date_min', 'date_max') + """ + ) + op.drop_column(table_name="dict_fields", column_name="is_synthese_field", schema="gn_imports") + + op.create_table( + "t_imports_synthese", + sa.Column( + "id_import", + sa.Integer, + sa.ForeignKey("gn_imports.t_imports.id_import", ondelete="CASCADE"), + primary_key=True, + ), + sa.Column("line_no", sa.Integer, primary_key=True), + sa.Column("valid", sa.Boolean, nullable=False, server_default=sa.false()), + ### source fields + # non-synthese fields: used to populate synthese fields + sa.Column("src_WKT", sa.Unicode), + sa.Column("src_codecommune", sa.Unicode), + sa.Column("src_codedepartement", sa.Unicode), + sa.Column("src_codemaille", sa.Unicode), + sa.Column("src_hour_max", sa.Unicode), + sa.Column("src_hour_min", sa.Unicode), + sa.Column("src_latitude", sa.Unicode), + sa.Column("src_longitude", sa.Unicode), + # synthese fields + sa.Column("src_unique_id_sinp", sa.Unicode), + sa.Column("src_unique_id_sinp_grp", sa.Unicode), + # nomenclature fields + sa.Column("src_id_nomenclature_geo_object_nature", sa.Unicode), + sa.Column("src_id_nomenclature_grp_typ", sa.Unicode), + sa.Column("src_id_nomenclature_obs_technique", sa.Unicode), + sa.Column("src_id_nomenclature_bio_status", sa.Unicode), + sa.Column("src_id_nomenclature_bio_condition", sa.Unicode), + sa.Column("src_id_nomenclature_naturalness", sa.Unicode), + sa.Column("src_id_nomenclature_valid_status", sa.Unicode), + sa.Column("src_id_nomenclature_exist_proof", sa.Unicode), + sa.Column("src_id_nomenclature_diffusion_level", sa.Unicode), + sa.Column("src_id_nomenclature_life_stage", sa.Unicode), + sa.Column("src_id_nomenclature_sex", sa.Unicode), + sa.Column("src_id_nomenclature_obj_count", sa.Unicode), + sa.Column("src_id_nomenclature_type_count", sa.Unicode), + sa.Column("src_id_nomenclature_sensitivity", sa.Unicode), + sa.Column("src_id_nomenclature_observation_status", sa.Unicode), + sa.Column("src_id_nomenclature_blurring", sa.Unicode), + sa.Column("src_id_nomenclature_source_status", sa.Unicode), + sa.Column("src_id_nomenclature_info_geo_type", sa.Unicode), + sa.Column("src_id_nomenclature_behaviour", sa.Unicode), + sa.Column("src_id_nomenclature_biogeo_status", sa.Unicode), + sa.Column("src_id_nomenclature_determination_method", sa.Unicode), + sa.Column("src_count_min", sa.Unicode), + sa.Column("src_count_max", sa.Unicode), + sa.Column("src_cd_nom", sa.Unicode), + sa.Column("src_cd_hab", sa.Unicode), + sa.Column("src_altitude_min", sa.Unicode), + sa.Column("src_altitude_max", sa.Unicode), + sa.Column("src_depth_min", sa.Unicode), + sa.Column("src_depth_max", sa.Unicode), + sa.Column("src_precision", sa.Unicode), + sa.Column("src_id_area_attachment", sa.Unicode), + sa.Column("src_date_min", sa.Unicode), + sa.Column("src_date_max", sa.Unicode), + sa.Column("src_id_digitiser", sa.Unicode), + sa.Column("src_meta_validation_date", sa.Unicode), + sa.Column("src_meta_create_date", sa.Unicode), + sa.Column("src_meta_update_date", sa.Unicode), + # un-mapped fields + sa.Column("extra_fields", HSTORE), + ### synthese fields + sa.Column("unique_id_sinp", UUID(as_uuid=True)), # + sa.Column("unique_id_sinp_grp", UUID(as_uuid=True)), # + sa.Column("entity_source_pk_value", sa.Unicode), # + sa.Column("grp_method", sa.Unicode(length=255)), # + # nomenclature fields + sa.Column( + "id_nomenclature_geo_object_nature", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_grp_typ", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_obs_technique", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_bio_status", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_bio_condition", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_naturalness", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_valid_status", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_exist_proof", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_diffusion_level", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_life_stage", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_sex", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_obj_count", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_type_count", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_sensitivity", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_observation_status", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_blurring", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_source_status", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_info_geo_type", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_behaviour", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_biogeo_status", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + sa.Column( + "id_nomenclature_determination_method", + sa.Integer, + sa.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + ), # + # others fields + sa.Column("reference_biblio", sa.Unicode), # + sa.Column("count_min", sa.Integer), # + sa.Column("count_max", sa.Integer), # + sa.Column("cd_nom", sa.Integer, sa.ForeignKey("taxonomie.taxref.cd_nom")), # + sa.Column("cd_hab", sa.Integer, sa.ForeignKey("ref_habitats.habref.cd_hab")), # + sa.Column("nom_cite", sa.Unicode), # + sa.Column("meta_v_taxref", sa.Unicode), # + # sa.Column("sample_number_proof", sa.UnicodeText), ####################### + sa.Column("digital_proof", sa.UnicodeText), # + sa.Column("non_digital_proof", sa.UnicodeText), # + sa.Column("altitude_min", sa.Integer), # + sa.Column("altitude_max", sa.Integer), # + sa.Column("depth_min", sa.Integer), # + sa.Column("depth_max", sa.Integer), # + sa.Column("place_name", sa.Unicode), # + sa.Column("the_geom_4326", Geometry("GEOMETRY", 4326)), # + sa.Column("the_geom_point", Geometry("GEOMETRY", 4326)), # FIXME useful? + sa.Column("the_geom_local", Geometry("GEOMETRY")), # FIXME useful? + sa.Column("precision", sa.Integer), # + # sa.Column("id_area_attachment", sa.Integer), ####################### + sa.Column("date_min", sa.DateTime), # + sa.Column("date_max", sa.DateTime), # + sa.Column("validator", sa.Unicode), # + sa.Column("validation_comment", sa.Unicode), # + sa.Column("observers", sa.Unicode), # + sa.Column("determiner", sa.Unicode), # + sa.Column("id_digitiser", sa.Integer, sa.ForeignKey("utilisateurs.t_roles.id_role")), # + sa.Column("comment_context", sa.UnicodeText), # + sa.Column("comment_description", sa.UnicodeText), # + sa.Column("additional_data", JSONB), # + sa.Column("meta_validation_date", sa.DateTime), + sa.Column("meta_create_date", sa.DateTime), + sa.Column("meta_update_date", sa.DateTime), + schema="gn_imports", + ) + + op.rename_table( + old_table_name="t_user_errors", + new_table_name="dict_errors", + schema="gn_imports", + ) + op.rename_table( + old_table_name="t_user_error_list", + new_table_name="t_user_errors", + schema="gn_imports", + ) + + +def downgrade(): + op.rename_table( + old_table_name="t_user_errors", + new_table_name="t_user_error_list", + schema="gn_imports", + ) + op.rename_table( + old_table_name="dict_errors", + new_table_name="t_user_errors", + schema="gn_imports", + ) + op.drop_table( + table_name="t_imports_synthese", + schema="gn_imports", + ) + + op.add_column( + table_name="dict_fields", + column=sa.Column( + "is_synthese_field", + sa.Boolean, + ), + schema="gn_imports", + ) + op.execute( + """ + UPDATE + gn_imports.dict_fields + SET + is_synthese_field = (synthese_field IS NOT NULL) + """ + ) + op.execute( + """ + DELETE FROM + gn_imports.dict_fields + WHERE + name_field IN ('datetime_min', 'datetime_max') + """ + ) + op.execute( + """ + UPDATE + gn_imports.dict_fields + SET + is_synthese_field = TRUE + WHERE + name_field IN ('date_min', 'date_max') + """ + ) + op.execute( + """ + ALTER TABLE gn_imports.dict_fields ADD CONSTRAINT chk_mandatory CHECK ( + CASE + WHEN ((name_field)::text = ANY ((ARRAY['date_min', 'longitude', 'latitude', 'nom_cite', 'cd_nom', 'wkt'])::text[])) THEN (mandatory = true) + ELSE NULL::boolean + END) + """ + ) + op.drop_column( + table_name="dict_fields", + column_name="source_field", + schema="gn_imports", + ) + op.drop_column( + table_name="dict_fields", + column_name="synthese_field", + schema="gn_imports", + ) + op.alter_column( + table_name="dict_fields", + column_name="is_synthese_field", + new_column_name="synthese_field", + schema="gn_imports", + ) + + op.add_column( + table_name="dict_fields", + column=sa.Column("nomenclature", sa.Boolean, nullable=False, server_default=sa.false()), + schema="gn_imports", + ) + op.execute( + """ + WITH cte AS ( + SELECT + id_field, + mnemonique IS NOT NULL AS nomenclature + FROM + gn_imports.dict_fields + ) + UPDATE + gn_imports.dict_fields f + SET + nomenclature = cte.nomenclature + FROM + cte + WHERE + f.id_field = cte.id_field + """ + ) diff --git a/backend/geonature/migrations/versions/imports/__init__.py b/backend/geonature/migrations/versions/imports/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/geonature/migrations/versions/imports/a11c9a2db7bb_set_import_module_type.py b/backend/geonature/migrations/versions/imports/a11c9a2db7bb_set_import_module_type.py new file mode 100644 index 0000000000..11c13c2063 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/a11c9a2db7bb_set_import_module_type.py @@ -0,0 +1,43 @@ +"""set import module type + +Revision ID: a11c9a2db7bb +Revises: 65defbe5027b +Create Date: 2022-07-05 18:15:09.885031 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "a11c9a2db7bb" +down_revision = "65defbe5027b" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + """ + UPDATE + gn_commons.t_modules + SET + type = 'import' + WHERE + module_code = 'IMPORT' + """ + ) + + +def downgrade(): + op.execute( + """ + UPDATE + gn_commons.t_modules + SET + type = NULL + WHERE + module_code = 'IMPORT' + """ + ) diff --git a/backend/geonature/migrations/versions/imports/a81f74d0a518_insert_import_sample_data.py b/backend/geonature/migrations/versions/imports/a81f74d0a518_insert_import_sample_data.py new file mode 100644 index 0000000000..386116d6fa --- /dev/null +++ b/backend/geonature/migrations/versions/imports/a81f74d0a518_insert_import_sample_data.py @@ -0,0 +1,192 @@ +"""insert_import_sample_data + +Revision ID: a81f74d0a518 +Revises: 2b0b3bd0248c +Create Date: 2024-06-04 14:57:59.428947 + +""" + +import importlib + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "a81f74d0a518" +down_revision = None +branch_labels = ("import-samples",) +depends_on = "2b0b3bd0248c" + + +def upgrade(): + operations = importlib.resources.read_text( + "geonature.migrations.data.imports", "sample_data.sql" + ) + op.execute(operations) + + +def downgrade(): + # Delete the data from the gn_imports.t_imports table + op.execute( + """ + DELETE FROM gn_imports.t_imports + WHERE id_dataset IN ( + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = 'a1b2c3d4-e5f6-4a3b-2c1d-e6f5a4b3c2d1'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '5f45d560-1ce3-420c-b45c-3d589eedaee1') + ); + """ + ) + + # Delete the data from the gn_meta.cor_dataset_protocol table + op.execute( + """ + DELETE FROM gn_meta.cor_dataset_protocol + WHERE id_dataset IN ( + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc') + ); + """ + ) + + # Delete the data from the gn_meta.cor_dataset_territory table + op.execute( + """ + DELETE FROM gn_meta.cor_dataset_territory + WHERE id_dataset IN ( + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc') + ); + """ + ) + + # Delete the data from the gn_meta.cor_dataset_actor table + op.execute( + """ + DELETE FROM gn_meta.cor_dataset_actor + WHERE id_dataset IN ( + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = 'a1b2c3d4-e5f6-4a3b-2c1d-e6f5a4b3c2d1'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '5f45d560-1ce3-420c-b45c-3d589eedaee1') + ); + """ + ) + + # Delete the data from the gn_meta.cor_acquisition_framework_actor table + op.execute( + """ + DELETE FROM gn_meta.cor_acquisition_framework_actor + WHERE id_acquisition_framework = (SELECT id_acquisition_framework FROM gn_meta.t_acquisition_frameworks WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43'); + """ + ) + + # Delete the data from the gn_meta.cor_acquisition_framework_objectif table + op.execute( + """ + DELETE FROM gn_meta.cor_acquisition_framework_objectif + WHERE id_acquisition_framework = (SELECT id_acquisition_framework FROM gn_meta.t_acquisition_frameworks WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43'); + """ + ) + + # Delete the data from the gn_meta.cor_acquisition_framework_voletsinp table + op.execute( + """ + DELETE FROM gn_meta.cor_acquisition_framework_voletsinp + WHERE id_acquisition_framework = (SELECT id_acquisition_framework FROM gn_meta.t_acquisition_frameworks WHERE unique_acquisition_framework_id = '5b054340-210c-4350-9034-300543210c43'); + """ + ) + + # Delete the data from the gn_commons.cor_module_dataset table + op.execute( + """ + DELETE FROM gn_commons.cor_module_dataset + WHERE id_dataset IN ( + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '9f86d081-8292-466e-9e7b-16f3960d255f'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '2f543d86-ec4e-4f1a-b4d9-123456789abc'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = 'a1b2c3d4-e5f6-4a3b-2c1d-e6f5a4b3c2d1'), + (SELECT id_dataset FROM gn_meta.t_datasets WHERE unique_dataset_id = '5f45d560-1ce3-420c-b45c-3d589eedaee1') + ); + """ + ) + + # Delete the data from the gn_meta.t_datasets table + op.execute( + """ + DELETE FROM gn_meta.t_datasets + WHERE unique_dataset_id IN ('9f86d081-8292-466e-9e7b-16f3960d255f', '2f543d86-ec4e-4f1a-b4d9-123456789abc', 'a1b2c3d4-e5f6-4a3b-2c1d-e6f5a4b3c2d1', '5f45d560-1ce3-420c-b45c-3d589eedaee1'); + """ + ) + + # Delete the data from the gn_meta.t_acquisition_frameworks table + op.execute( + """ + DELETE FROM gn_meta.t_acquisition_frameworks + WHERE unique_acquisition_framework_id IN ('5b054340-210c-4350-9034-300543210c43', '7a2b3c4d-5e6f-4a3b-2c1d-e6f5a4b3c2d1'); + """ + ) + + # Step 1: Create a temporary table to hold the filtered imports + op.execute( + """ + CREATE TEMP TABLE temp_filtered_imports AS + SELECT ti.id_import + FROM gn_imports.t_imports ti + JOIN gn_meta.t_datasets td ON ti.id_dataset = td.id_dataset + WHERE td.dataset_name ILIKE '%JDD-TEST-IMPORT%'; + """ + ) + + # Step 2: Delete the relevant records from cor_role_import + op.execute( + """ + DELETE FROM gn_imports.cor_role_import cri + USING temp_filtered_imports tfi + WHERE cri.id_import in (tfi.id_import); + """ + ) + + # Clean up temporary table + op.execute("DROP TABLE temp_filtered_imports;") + + ## Clean users test + # Delete permissions for admin-test-import + op.execute( + """ + DELETE FROM gn_permissions.t_permissions + WHERE id_role = (SELECT id_role FROM utilisateurs.t_roles WHERE identifiant = 'admin-test-import'); + """ + ) + + # Delete permissions for agent-test-import + op.execute( + """ + DELETE FROM gn_permissions.t_permissions + WHERE id_role = (SELECT id_role FROM utilisateurs.t_roles WHERE identifiant = 'agent-test-import'); + """ + ) + + # Delete notifications for admin-test-import + op.execute( + """ + DELETE FROM gn_notifications.t_notifications + WHERE id_role = (SELECT id_role FROM utilisateurs.t_roles WHERE identifiant = 'admin-test-import'); + """ + ) + + # Delete notifications for agent-test-import + op.execute( + """ + DELETE FROM gn_notifications.t_notifications + WHERE id_role = (SELECT id_role FROM utilisateurs.t_roles WHERE identifiant = 'agent-test-import'); + """ + ) + + # Delete the roles + op.execute( + """ + DELETE FROM utilisateurs.t_roles WHERE identifiant IN ('admin-test-import', 'agent-test-import'); + """ + ) diff --git a/backend/geonature/migrations/versions/imports/a89a99f68203_declare_available_permissions.py b/backend/geonature/migrations/versions/imports/a89a99f68203_declare_available_permissions.py new file mode 100644 index 0000000000..4cabd732de --- /dev/null +++ b/backend/geonature/migrations/versions/imports/a89a99f68203_declare_available_permissions.py @@ -0,0 +1,164 @@ +"""declare available permissions + +Revision ID: a89a99f68203 +Revises: 5158afe602d2 +Create Date: 2023-06-14 11:40:29.580680 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "a89a99f68203" +down_revision = "5158afe602d2" +branch_labels = None +depends_on = ("f1dd984bff97",) + + +def upgrade(): + op.execute( + """ + INSERT INTO gn_permissions.t_objects (code_object, description_object) + VALUES ( + 'IMPORT', + 'Imports de données' + ) + """ + ) + op.execute( + """ + INSERT INTO gn_permissions.cor_object_module + (id_object, id_module) + VALUES( + (SELECT id_object FROM gn_permissions.t_objects WHERE code_object = 'IMPORT'), + (SELECT id_module FROM gn_commons.t_modules WHERE module_code = 'IMPORT') + ) + """ + ) + op.execute( + """ + INSERT INTO + gn_permissions.t_permissions_available ( + id_module, + id_object, + id_action, + label, + scope_filter + ) + SELECT + m.id_module, + o.id_object, + a.id_action, + v.label, + v.scope_filter + FROM + ( + VALUES + ('IMPORT', 'IMPORT', 'C', True, 'Créer des imports') + ,('IMPORT', 'IMPORT', 'R', True, 'Voir les imports') + ,('IMPORT', 'IMPORT', 'U', True, 'Modifier des imports') + ,('IMPORT', 'IMPORT', 'D', True, 'Supprimer des imports') + ,('IMPORT', 'MAPPING', 'C', True, 'Créer des mappings') + ,('IMPORT', 'MAPPING', 'R', True, 'Voir les mappings') + ,('IMPORT', 'MAPPING', 'U', True, 'Modifier des mappings') + ,('IMPORT', 'MAPPING', 'D', True, 'Supprimer des mappings') + ) AS v (module_code, object_code, action_code, scope_filter, label) + JOIN + gn_commons.t_modules m ON m.module_code = v.module_code + JOIN + gn_permissions.t_objects o ON o.code_object = v.object_code + JOIN + gn_permissions.bib_actions a ON a.code_action = v.action_code + """ + ) + op.execute( + """ + WITH new_all_permissions AS ( + SELECT + p.id_role, + p.id_action, + p.id_module, + (SELECT id_object FROM gn_permissions.t_objects WHERE code_object = 'IMPORT') as id_object, + p.scope_value, + p.sensitivity_filter + FROM + gn_permissions.t_permissions p + JOIN + gn_commons.t_modules m + USING (id_module) + JOIN + gn_permissions.t_objects o + USING (id_object) + WHERE + m.module_code = 'IMPORT' + AND o.code_object = 'ALL' + ) + INSERT INTO gn_permissions.t_permissions + (id_role, id_action, id_module, id_object, scope_value, sensitivity_filter) + SELECT * FROM new_all_permissions + """ + ) + op.execute( + """ + WITH bad_permissions AS ( + SELECT + p.id_permission + FROM + gn_permissions.t_permissions p + JOIN gn_commons.t_modules m + USING (id_module) + WHERE + m.module_code = 'IMPORT' + EXCEPT + SELECT + p.id_permission + FROM + gn_permissions.t_permissions p + JOIN gn_permissions.t_permissions_available pa ON + (p.id_module = pa.id_module + AND p.id_object = pa.id_object + AND p.id_action = pa.id_action) + ) + DELETE + FROM + gn_permissions.t_permissions p + USING bad_permissions bp + WHERE + bp.id_permission = p.id_permission; + """ + ) + + +def downgrade(): + op.execute( + """ + DELETE FROM + gn_permissions.t_permissions_available pa + USING + gn_commons.t_modules m + WHERE + pa.id_module = m.id_module + AND + module_code = 'IMPORT' + """ + ) + op.execute( + """ + DELETE FROM + gn_permissions.t_permissions p + USING + gn_commons.t_modules m, + gn_permissions.t_objects o + WHERE + p.id_module = m.id_module + AND + module_code = 'IMPORT' + AND + p.id_object = o.id_object + AND + code_object = 'IMPORT' + """ + ) + op.execute("DELETE FROM gn_permissions.t_objects WHERE code_object = 'IMPORT'") diff --git a/backend/geonature/migrations/versions/imports/bf80cb5679be_remove_step_field.py b/backend/geonature/migrations/versions/imports/bf80cb5679be_remove_step_field.py new file mode 100644 index 0000000000..069649f757 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/bf80cb5679be_remove_step_field.py @@ -0,0 +1,49 @@ +"""remove step field + +Revision ID: bf80cb5679be +Revises: 75f0f9906bf1 +Create Date: 2022-02-01 16:28:34.090996 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "bf80cb5679be" +down_revision = "75f0f9906bf1" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + """ + ALTER TABLE gn_imports.t_imports + DROP COLUMN step + """ + ) + + op.execute( + """ + ALTER TABLE gn_imports.t_user_error_list + DROP COLUMN step + """ + ) + + +def downgrade(): + op.execute( + """ + ALTER TABLE gn_imports.t_imports + ADD COLUMN step integer + """ + ) + + op.execute( + """ + ALTER TABLE gn_imports.t_user_error_list + ADD COLUMN step VARCHAR(20) null + """ + ) diff --git a/backend/geonature/migrations/versions/imports/bfc90691737d_add_id_entity_in_users_errors.py b/backend/geonature/migrations/versions/imports/bfc90691737d_add_id_entity_in_users_errors.py new file mode 100644 index 0000000000..1a309f1092 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/bfc90691737d_add_id_entity_in_users_errors.py @@ -0,0 +1,85 @@ +"""Add id_entity in users errors + +Revision ID: bfc90691737d +Revises: 2b0b3bd0248c +Create Date: 2024-02-15 16:20:57.049889 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "bfc90691737d" +down_revision = "02e9b8758709" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + schema="gn_imports", + table_name="t_user_errors", + column=sa.Column( + "id_entity", + sa.Integer, + ), + ) + op.create_foreign_key( + constraint_name="t_user_errors_id_entity_fkey", + source_schema="gn_imports", + source_table="t_user_errors", + local_cols=["id_entity"], + referent_schema="gn_imports", + referent_table="bib_entities", + remote_cols=["id_entity"], + onupdate="CASCADE", + ondelete="CASCADE", + ) + op.drop_constraint( + schema="gn_imports", + table_name="t_user_errors", + constraint_name="t_user_errors_un", + ) + op.create_index( + index_name="t_user_errors_un", + schema="gn_imports", + table_name="t_user_errors", + columns=("id_import", "id_error", "column_error"), + unique=True, + postgresql_where="id_entity IS NULL", + ) + op.create_index( + index_name="t_user_errors_entity_un", + schema="gn_imports", + table_name="t_user_errors", + columns=("id_import", "id_entity", "id_error", "column_error"), + unique=True, + postgresql_where="id_entity IS NOT NULL", + ) + + +def downgrade(): + op.drop_index( + schema="gn_imports", + table_name="t_user_errors", + index_name="t_user_errors_entity_un", + ) + op.drop_index( + schema="gn_imports", + table_name="t_user_errors", + index_name="t_user_errors_un", + ) + op.create_unique_constraint( + constraint_name="t_user_errors_un", + schema="gn_imports", + table_name="t_user_errors", + columns=("id_import", "id_error", "column_error"), + ) + op.drop_constraint( + schema="gn_imports", + table_name="t_user_errors", + constraint_name="t_user_errors_id_entity_fkey", + ) + op.drop_column(schema="gn_imports", table_name="t_user_errors", column_name="id_entity") diff --git a/backend/geonature/migrations/versions/imports/cadfdaa42430_add_task_id.py b/backend/geonature/migrations/versions/imports/cadfdaa42430_add_task_id.py new file mode 100644 index 0000000000..6816943cb1 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/cadfdaa42430_add_task_id.py @@ -0,0 +1,36 @@ +"""add task_uuid + +Revision ID: cadfdaa42430 +Revises: 627b7968a55b +Create Date: 2022-05-16 14:34:03.746276 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "cadfdaa42430" +down_revision = "627b7968a55b" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + schema="gn_imports", + table_name="t_imports", + column=sa.Column( + "task_id", + sa.String(155), + ), + ) + + +def downgrade(): + op.drop_column( + schema="gn_imports", + table_name="t_imports", + column_name="task_id", + ) diff --git a/backend/geonature/migrations/versions/imports/d6bf8eaf088c_add_update_permission.py b/backend/geonature/migrations/versions/imports/d6bf8eaf088c_add_update_permission.py new file mode 100644 index 0000000000..db85b646b3 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/d6bf8eaf088c_add_update_permission.py @@ -0,0 +1,64 @@ +"""add missing update permission for those that had installed 2.2.0 or 2.2.1 versions + +Revision ID: d6bf8eaf088c +Revises: 8611f7aab8dc +Create Date: 2023-09-18 11:29:42.145359 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "d6bf8eaf088c" +down_revision = "8611f7aab8dc" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + """ + INSERT INTO + gn_permissions.t_permissions_available ( + id_module, + id_object, + id_action, + label, + scope_filter + ) + SELECT + m.id_module, + o.id_object, + a.id_action, + v.label, + v.scope_filter + FROM + ( + VALUES + ('IMPORT', 'IMPORT', 'U', True, 'Modifier des imports') + ) AS v (module_code, object_code, action_code, scope_filter, label) + JOIN + gn_commons.t_modules m ON m.module_code = v.module_code + JOIN + gn_permissions.t_objects o ON o.code_object = v.object_code + JOIN + gn_permissions.bib_actions a ON a.code_action = v.action_code + WHERE + NOT EXISTS ( + SELECT + label + FROM + gn_permissions.t_permissions_available av + WHERE + av.id_module = m.id_module AND + av.id_object = o.id_object AND + av.id_action = a.id_action + ); + """ + ) + + +def downgrade(): + pass diff --git a/backend/geonature/migrations/versions/imports/ea67bf7b6888_remove_cd_fk.py b/backend/geonature/migrations/versions/imports/ea67bf7b6888_remove_cd_fk.py new file mode 100644 index 0000000000..5be4e91cc9 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/ea67bf7b6888_remove_cd_fk.py @@ -0,0 +1,51 @@ +"""remove cd fk + +Revision ID: ea67bf7b6888 +Revises: d6bf8eaf088c +Create Date: 2023-09-27 15:37:19.286693 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "ea67bf7b6888" +down_revision = "d6bf8eaf088c" +branch_labels = None +depends_on = None + + +def upgrade(): + op.drop_constraint( + schema="gn_imports", + table_name="t_imports_synthese", + constraint_name="t_imports_synthese_cd_nom_fkey", + ) + op.drop_constraint( + schema="gn_imports", + table_name="t_imports_synthese", + constraint_name="t_imports_synthese_cd_hab_fkey", + ) + + +def downgrade(): + op.create_foreign_key( + constraint_name="t_imports_synthese_cd_nom_fkey", + source_schema="gn_imports", + source_table="t_imports_synthese", + local_cols=["cd_nom"], + referent_schema="taxonomie", + referent_table="taxref", + remote_cols=["cd_nom"], + ) + op.create_foreign_key( + constraint_name="t_imports_synthese_cd_hab_fkey", + source_schema="gn_imports", + source_table="t_imports_synthese", + local_cols=["cd_hab"], + referent_schema="ref_habitats", + referent_table="habref", + remote_cols=["cd_hab"], + ) diff --git a/backend/geonature/migrations/versions/imports/eb217f32d7d7_add_id_area_attachment.py b/backend/geonature/migrations/versions/imports/eb217f32d7d7_add_id_area_attachment.py new file mode 100644 index 0000000000..8593c19a83 --- /dev/null +++ b/backend/geonature/migrations/versions/imports/eb217f32d7d7_add_id_area_attachment.py @@ -0,0 +1,89 @@ +"""add id_area_attachment + +Revision ID: eb217f32d7d7 +Revises: 74058f69828a +Create Date: 2022-04-28 16:48:46.664645 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "eb217f32d7d7" +down_revision = "74058f69828a" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + schema="gn_imports", + table_name="t_imports_synthese", + column=sa.Column( + "id_area_attachment", + sa.Integer, + sa.ForeignKey("ref_geo.l_areas.id_area"), + ), + ) + op.execute( + """ + INSERT INTO gn_imports.dict_fields ( + name_field, + synthese_field, + fr_label, + mandatory, + autogenerated, + id_theme, + order_field, + display + ) + VALUES ( + 'id_area_attachment', + 'id_area_attachment', + 'Rattachement géographique de l’observation', + FALSE, + FALSE, + (SELECT id_theme FROM gn_imports.dict_fields WHERE name_field = 'WKT'), + (SELECT order_field FROM gn_imports.dict_fields WHERE name_field = 'WKT'), + FALSE + ) + """ + ) + op.execute( + """ + UPDATE + gn_imports.dict_fields + SET + mandatory = FALSE + WHERE + name_field IN ('WKT', 'codecommune', 'codemaille', 'codedepartement', 'longitude', 'latitude') + """ + ) + + +def downgrade(): + op.execute( + """ + UPDATE + gn_imports.dict_fields + SET + mandatory = TRUE + WHERE + name_field IN ('WKT', 'codecommune', 'codemaille', 'codedepartement', 'longitude', 'latitude') + """ + ) + op.execute( + """ + DELETE FROM + gn_imports.dict_fields + WHERE + name_field = 'id_area_attachment' + """ + ) + op.drop_column( + schema="gn_imports", + table_name="t_imports_synthese", + column_name="id_area_attachment", + ) diff --git a/backend/geonature/migrations/versions/imports/f394a5edcb56_on_delete_source_set_null.py b/backend/geonature/migrations/versions/imports/f394a5edcb56_on_delete_source_set_null.py new file mode 100644 index 0000000000..02cd836e7b --- /dev/null +++ b/backend/geonature/migrations/versions/imports/f394a5edcb56_on_delete_source_set_null.py @@ -0,0 +1,44 @@ +"""on delete source set null + +Revision ID: f394a5edcb56 +Revises: 0ff8fc0b4233 +Create Date: 2022-06-22 11:40:05.678227 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "f394a5edcb56" +down_revision = "5c31e356cedc" +branch_labels = None +depends_on = None + + +def replace_constraint(ondelete): + op.drop_constraint( + constraint_name="fk_gn_imports_t_import_id_source_synthese", + schema="gn_imports", + table_name="t_imports", + ) + op.create_foreign_key( + constraint_name="fk_gn_imports_t_import_id_source_synthese", + source_schema="gn_imports", + source_table="t_imports", + local_cols=["id_source_synthese"], + referent_schema="gn_synthese", + referent_table="t_sources", + remote_cols=["id_source"], + onupdate="CASCADE", + ondelete=ondelete, + ) + + +def upgrade(): + replace_constraint(ondelete="SET NULL") + + +def downgrade(): + replace_constraint(ondelete="CASCADE") diff --git a/backend/geonature/tasks/__init__.py b/backend/geonature/tasks/__init__.py index 1fb7857e9c..8b13789179 100644 --- a/backend/geonature/tasks/__init__.py +++ b/backend/geonature/tasks/__init__.py @@ -1,4 +1 @@ -from celery.signals import task_postrun -from geonature.utils.env import db -from geonature.utils.celery import celery_app diff --git a/backend/geonature/templates/layout.html b/backend/geonature/templates/layout.html index c16398ac43..4454b1ffea 100644 --- a/backend/geonature/templates/layout.html +++ b/backend/geonature/templates/layout.html @@ -74,8 +74,7 @@
-
diff --git a/contrib/gn_module_occhab/frontend/app/components/occhab-map-list/occhab-map-list.component.scss b/contrib/gn_module_occhab/frontend/app/components/occhab-map-list/occhab-map-list.component.scss index 27311ea2f9..9c619d06e4 100644 --- a/contrib/gn_module_occhab/frontend/app/components/occhab-map-list/occhab-map-list.component.scss +++ b/contrib/gn_module_occhab/frontend/app/components/occhab-map-list/occhab-map-list.component.scss @@ -37,7 +37,6 @@ z-index: 1000; bottom: 1%; right: 1%; - position: absolute; } :host ::ng-deep ngx-datatable { diff --git a/contrib/gn_module_occhab/frontend/app/components/occhab-map-list/occhab-map-list.component.ts b/contrib/gn_module_occhab/frontend/app/components/occhab-map-list/occhab-map-list.component.ts index 093c809efe..aadcb210a9 100644 --- a/contrib/gn_module_occhab/frontend/app/components/occhab-map-list/occhab-map-list.component.ts +++ b/contrib/gn_module_occhab/frontend/app/components/occhab-map-list/occhab-map-list.component.ts @@ -1,29 +1,36 @@ -import { Component, OnInit, ViewChild, HostListener } from '@angular/core'; -import { OcchabStoreService } from '../../services/store.service'; -import { MapListService } from '@geonature_common/map-list/map-list.service'; -import { OccHabDataService } from '../../services/data.service'; -import { DatatableComponent } from '@swimlane/ngx-datatable'; -import { OccHabModalDownloadComponent } from './modal-download.component'; -import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { CommonService } from '@geonature_common/service/common.service'; -import * as moment from 'moment'; -import { ConfigService } from '@geonature/services/config.service'; -import { OccHabMapListService } from '../../services/occhab-map-list.service'; +import { Component, OnInit, ViewChild, HostListener } from "@angular/core"; +import { OcchabStoreService } from "../../services/store.service"; +import { MapListService } from "@geonature_common/map-list/map-list.service"; +import { OccHabDataService } from "../../services/data.service"; +import { DatatableComponent } from "@swimlane/ngx-datatable"; +import { OccHabModalDownloadComponent } from "./modal-download.component"; +import { NgbModal, NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; +import { CommonService } from "@geonature_common/service/common.service"; +import * as moment from "moment"; +import { ConfigService } from "@geonature/services/config.service"; +import { OccHabMapListService } from "../../services/occhab-map-list.service"; +import { ModuleService } from "@geonature/services/module.service"; +import { CruvedStoreService } from "@geonature_common/service/cruved-store.service"; @Component({ - selector: 'pnx-occhab-map-list', - templateUrl: 'occhab-map-list.component.html', - styleUrls: ['./occhab-map-list.component.scss', '../responsive-map.scss'], + selector: "pnx-occhab-map-list", + templateUrl: "occhab-map-list.component.html", + styleUrls: ["./occhab-map-list.component.scss", "../responsive-map.scss"], providers: [NgbActiveModal], }) export class OccHabMapListComponent implements OnInit { - @ViewChild('dataTable') dataTable: DatatableComponent; + @ViewChild("dataTable") dataTable: DatatableComponent; public rowNumber: number; public dataLoading = true; public deleteOne: any; + public destinationImportCode: string; + public isCollapseFilter = true; + public userCruved: any; + public canImport: boolean = false; + constructor( public storeService: OcchabStoreService, private _occHabDataService: OccHabDataService, @@ -31,16 +38,31 @@ export class OccHabMapListComponent implements OnInit { private _ngbModal: NgbModal, private _commonService: CommonService, public config: ConfigService, - public mapListFormService: OccHabMapListService + public mapListFormService: OccHabMapListService, + private _moduleService: ModuleService, + public cruvedStore: CruvedStoreService ) {} ngOnInit() { + // get user cruved + const currentModule = this._moduleService.currentModule; + this.userCruved = currentModule.cruved; + const cruvedImport = + this.cruvedStore.cruved.IMPORT.module_objects.IMPORT.cruved; + const canCreateImport = cruvedImport.C > 0; + const canCreateOcchab = this.userCruved.C > 0; + + this.canImport = canCreateImport && canCreateOcchab; + this.destinationImportCode = currentModule.destination[0]?.code; + if (this.storeService.firstMessageMapList) { - this._commonService.regularToaster('info', 'Les 50 dernières stations saisies'); + this._commonService.regularToaster( + "info", + "Les 50 dernières stations saisies" + ); this.storeService.firstMessageMapList = false; } - this.getStations({ limit: 50 }); // get wiewport height to set the number of rows in the tabl const h = document.documentElement.clientHeight; @@ -65,21 +87,26 @@ export class OccHabMapListComponent implements OnInit { } // update the number of row per page when resize the window - @HostListener('window:resize', ['$event']) + @HostListener("window:resize", ["$event"]) onResize(event) { this.rowNumber = this.calculeteRowNumber(event.target.innerHeight); } getStations(params?) { - params['habitats'] = 1; + params["habitats"] = 1; this.dataLoading = true; this._occHabDataService.listStations(params).subscribe( (featuresCollection) => { // store the idsStation in the store service - if (featuresCollection.features.length === this.config.OCCHAB.NB_MAX_MAP_LIST) { + if ( + featuresCollection.features.length === + this.config.OCCHAB.NB_MAX_MAP_LIST + ) { this.openModal(true); } - this.storeService.idsStation = featuresCollection.features.map((feature) => feature.id); + this.storeService.idsStation = featuresCollection.features.map( + (feature) => feature.id + ); // this.stations = data; this.mapListService.tableData = []; featuresCollection.features.forEach((feature) => { @@ -94,7 +121,7 @@ export class OccHabMapListComponent implements OnInit { // error callback (e) => { if (e.status == 500) { - this._commonService.translateToaster('error', 'ErrorMessage'); + this._commonService.translateToaster("error", "ErrorMessage"); } this.dataLoading = false; } @@ -107,7 +134,7 @@ export class OccHabMapListComponent implements OnInit { openModal(tooManyObs = false) { const ref = this._ngbModal.open(OccHabModalDownloadComponent, { - size: 'lg', + size: "lg", }); ref.componentInstance.tooManyObs = tooManyObs; } @@ -119,7 +146,7 @@ export class OccHabMapListComponent implements OnInit { displayHabTooltip(row): string[] { let tooltip = []; if (row.habitats === undefined) { - tooltip.push('Aucun habitat'); + tooltip.push("Aucun habitat"); } else { for (let i = 0; i < row.habitats.length; i++) { let occ = row.habitats[i]; @@ -132,15 +159,15 @@ export class OccHabMapListComponent implements OnInit { displayObservateursTooltip(row): string[] { let tooltip = []; if (row.observers === undefined) { - if (row.observers_txt !== null && row.observers_txt.trim() !== '') { + if (row.observers_txt !== null && row.observers_txt.trim() !== "") { tooltip.push(row.observers_txt.trim()); } else { - tooltip.push('Aucun observateurs'); + tooltip.push("Aucun observateurs"); } } else { for (let i = 0; i < row.observers.length; i++) { let obs = row.observers[i]; - tooltip.push([obs.prenom_role, obs.nom_role].join(' ')); + tooltip.push([obs.prenom_role, obs.nom_role].join(" ")); } } return tooltip.sort(); @@ -148,38 +175,40 @@ export class OccHabMapListComponent implements OnInit { displayDateTooltip(element): string { return element.date_min == element.date_max - ? moment(element.date_min).format('DD-MM-YYYY') - : `Du ${moment(element.date_min).format('DD-MM-YYYY')} au ${moment(element.date_max).format( - 'DD-MM-YYYY' - )}`; + ? moment(element.date_min).format("DD-MM-YYYY") + : `Du ${moment(element.date_min).format("DD-MM-YYYY")} au ${moment( + element.date_max + ).format("DD-MM-YYYY")}`; } displayLeafletPopupCallback(feature): any { - const leafletPopup: HTMLElement = document.createElement('div'); - leafletPopup.style.maxHeight = '80vh'; - leafletPopup.style.overflowY = 'auto'; + const leafletPopup: HTMLElement = document.createElement("div"); + leafletPopup.style.maxHeight = "80vh"; + leafletPopup.style.overflowY = "auto"; - const divObservateurs = document.createElement('div'); - divObservateurs.innerHTML = ' Observateurs :
'; + const divObservateurs = document.createElement("div"); + divObservateurs.innerHTML = " Observateurs :
"; divObservateurs.innerHTML = - divObservateurs.innerHTML + this.displayObservateursTooltip(feature.properties).join(', '); + divObservateurs.innerHTML + + this.displayObservateursTooltip(feature.properties).join(", "); - const divDate = document.createElement('div'); - divDate.innerHTML = ' Date :
'; - divDate.innerHTML = divDate.innerHTML + this.displayDateTooltip(feature.properties); + const divDate = document.createElement("div"); + divDate.innerHTML = " Date :
"; + divDate.innerHTML = + divDate.innerHTML + this.displayDateTooltip(feature.properties); - const divHab = document.createElement('div'); - divHab.innerHTML = ' Habitats :
'; + const divHab = document.createElement("div"); + divHab.innerHTML = " Habitats :
"; - divHab.style.marginTop = '5px'; - let taxons = this.displayHabTooltip(feature.properties).join('
'); + divHab.style.marginTop = "5px"; + let taxons = this.displayHabTooltip(feature.properties).join("
"); divHab.innerHTML = divHab.innerHTML + taxons; leafletPopup.appendChild(divObservateurs); leafletPopup.appendChild(divDate); leafletPopup.appendChild(divHab); - feature.properties['leaflet_popup'] = leafletPopup; + feature.properties["leaflet_popup"] = leafletPopup; return feature; } diff --git a/contrib/gn_module_occhab/frontend/app/services/form-service.ts b/contrib/gn_module_occhab/frontend/app/services/form-service.ts index efa2b81ffc..e933a04170 100644 --- a/contrib/gn_module_occhab/frontend/app/services/form-service.ts +++ b/contrib/gn_module_occhab/frontend/app/services/form-service.ts @@ -20,6 +20,7 @@ export class OcchabFormService { public typoHabControl = new UntypedFormControl(); public selectedTypo: any; public currentEditingHabForm = null; + public currentHabCopy = null; constructor( private _fb: UntypedFormBuilder, private _dateParser: NgbDateParserFormatter, @@ -137,7 +138,11 @@ export class OcchabFormService { * @param index: index of the habitat to edit */ editHab(index) { + const habArrayForm = this.stationForm.controls.habitats as UntypedFormArray; this.currentEditingHabForm = index; + this.currentHabCopy = { + ...habArrayForm.controls[this.currentEditingHabForm].value, + }; } /** Cancel the current hab @@ -145,8 +150,12 @@ export class OcchabFormService { * we keep the order */ cancelHab() { - this.deleteHab(this.currentEditingHabForm); - this.currentEditingHabForm = null; + if (this.currentEditingHabForm !== null) { + const habArrayForm = this.stationForm.controls.habitats as UntypedFormArray; + habArrayForm.controls[this.currentEditingHabForm].setValue(this.currentHabCopy); + this.currentHabCopy = null; + this.currentEditingHabForm = null; + } } /** diff --git a/contrib/gn_module_occhab/frontend/app/services/occhab-map-list.service.ts b/contrib/gn_module_occhab/frontend/app/services/occhab-map-list.service.ts index 624b4a1d65..5ae65e1693 100644 --- a/contrib/gn_module_occhab/frontend/app/services/occhab-map-list.service.ts +++ b/contrib/gn_module_occhab/frontend/app/services/occhab-map-list.service.ts @@ -1,8 +1,8 @@ -import { Injectable } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { MapListService } from '@geonature_common/map-list/map-list.service'; -import { OccHabDataService } from '../services/data.service'; -import * as moment from 'moment'; +import { Injectable } from "@angular/core"; +import { UntypedFormBuilder, UntypedFormGroup } from "@angular/forms"; +import { MapListService } from "@geonature_common/map-list/map-list.service"; +import { OccHabDataService } from "../services/data.service"; +import * as moment from "moment"; @Injectable() export class OccHabMapListService { @@ -14,6 +14,7 @@ export class OccHabMapListService { ) { this.searchForm = this._fb.group({ id_dataset: null, + id_import: null, date_low: null, date_up: null, habitat: null, diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3a1cc81c85..951a92896a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,9 +1,57 @@ CHANGELOG ========= -2.14.2 (2024-05-28) +2.15.0 (unreleased) ------------------- +TH v2 (intégré à GN et son module Admin), Import v3 (multi-destination, import Occhab et intégré au coeur de GN), authentification externe + +**🚀 Nouveautés** + +- Intégration de TaxHub à GeoNature (#3150 + voir la note de version de TaxHub 2.0.0 - LIEN) +- Intégration du module Import dans le coeur de GeoNature et refonte de celui-ci pour qu'il puisse importer dans d'autres modules que Synthèse (https://github.com/PnX-SI/gn_module_import/issues/303) +- Ajout de la possibilité d'importer des données depuis des fichiers vers le module Occhab +- Autres évolutions du module Import à mentionner ici... (évolution des controles ? Import GeoJSON ? Graphiques génériques ? Meilleure gestion des formats de date ? Amélioration export PDF ? Import multi-JDD ?) +- Ajout de tests frontend automatisés sur le module Import +- Evolution du fonctionnement des permissions sur le module Import pour gérer son nouveau fonctionnement multi-destination (Action C ajoutée au module Synthèse, JDD à associer aux modules de destination...). Renvoyer vers la doc sur le sujet ? +- Intégration et enrichissement de la documentation du module Import : https://docs.geonature.fr/xxxxxx +- Amélioration export Occhab +- Possibilité de se connecter à GeoNature avec d'autres fournisseurs d'identité (#3111, https://github.com/PnX-SI/UsersHub-authentification-module/pull/93) + +**🐛 Corrections** + +- Correction de l'URL des modules externes dans le menu latéral (#3093) + +**⚠️ Notes de version** + +Si vous mettez à jour GeoNature : + +- L'application TaxHub a été integrée dans le module Admin de GeoNature (voir documentation TH) et accessible depuis le menu latéral : + - Les permissions basées sur les profils 1-6 ont été rapatriées et adaptées dans le modèle de permissions de GeoNature. + TaxHub est désormais un "module" GeoNature et dispose des objets de permissions `TAXONS`, `THEMES`, `LISTES` et `ATTRIBUTS` (voir doc GeoNature pour la description des objets). Les personnes ayant anciennement des droits 6 dans TaxHub ont toutes les permissions sur les objets pré-cités. Les personnes ayant des droits inférieurs à 6 et ayant un compte sur TaxHub ont maintenant des permissions sur l'objet `TAXON` (voir et éditer des taxons = ajouter des médias et des attributs) + - L'API de Taxhub est désormais disponible à l'URL `/api/taxhub/api>` (le dernier /api est une rétrocompatibilité et sera enlevé de manière transparante dans les prochaines versions) + - Le paramètre `API_TAXHUB` est désormais obsolète (déduit de `API_ENDPOINT`) et peut être retiré du fichier de configuration de GeoNature + - Si vous utilisez Occtax-mobile, veillez à modifier le paramètre `taxhub_url` du fichier `/geonature/backend/media/mobile/occtax/settings.json`, pour mettre la valeur `/api/taxhub>` + - Une redirection Apache automatique de l'URL de TaxHub et des médias est disponible à l'adresse suivante : XXXX + - ATLAS a tester -> modification URL des médias + - suppression de la branche alembic taxhub : `geonature db downgrade taxhub@base` + - désinstaller TH de votre serveur ? + - L'intégration de TaxHub dans GeoNature entraine la suppression du service systemd et la conf apache spécifique à TaxHub. Les logs de TH sont également centralisés dans le fichier de log de GeoNature + +- Le module Import a été intégré dans le coeur de GeoNature + - si vous aviez installé le module externe Import, XXXXX + - si vous n'aviez pas installé le module externe Import, il sera disponible après la mise à jour vers cette nouvelle version de GeoNature. Vous pouvez configurer les permissions de vos utilisateurs si vous souhaitez qu'ils y accédent + - la gestion des permissions et des JDD associés aux module a évolué. La migration est gérée automatiquement lors de la mise à jour pour garantir un fonctionnement identique. Voir la documentation (XXXXXXXXX) pour en savoir plus +- La synchronisation avec le service MTD de l'INPN n'est plus intégrée dans le code de GeoNature, elle a été déplacée dans un module externe : https://github.com/PnX-SI/mtd_sync + - Si vous l'utilisiez, supprimer les variables de configuration suivantes du fichier `geonature_config.toml` : + - `XML_NAMESPACE`, `MTD_API_ENDPOINT` + - toutes les variables dans `[CAS_PUBLIC]`, `[CAS]`, `[CAS.CAS_USER_WS]`, `[MTD]` + - `ID_USER_SOCLE_1` et `ID_USER_SOCLE_2` dans la section `BDD` + - Installez le nouveau module externe à l'aide de la commande : `pip install git+https://github.com/PnX-SI/mtd_sync` + - Remplissez la configuration dans un fichier `mtd_sync.toml` + +2.14.2 (2024-05-28) + **🚀 Nouveautés** - Mise à jour de dépendances critiques (#3054) @@ -148,6 +196,7 @@ Si vous mettez à jour GeoNature : - [Occtax] Tri alphabétique de la liste des "Mes lieux" (#2805, par @DonovanMaillard) - [Documentation] Corrections et compléments de la documentation d'administrateur (#2812, par @marie-laure-cen) + 2.13.3 (2023-10-17) ------------------- diff --git a/docs/FAQ.rst b/docs/FAQ.rst index 3291106746..9706312da8 100644 --- a/docs/FAQ.rst +++ b/docs/FAQ.rst @@ -7,7 +7,7 @@ Problèmes liés au frontend Changement d'URL de GeoNature """"""""""""""""""""""""""""" -Si vous souhaitez changer l'URL de l'API de GeoNature, il est nécessaire d'indiquer les nouvelles adresses dans le fichier de configuration principale (``geonature/config/geonature_config.toml`` pour GeoNature, ainsi que ceux de TaxHub et UsersHub) ainsi que le fichier ``frontend/src/assets/config.json`` précisant l'URL de l'API au frontend. Pour mettre à jour ce fichier automatiquement, relancer le script ``install/05_install_frontend.sh``. +Si vous souhaitez changer l'URL de l'API de GeoNature, il est nécessaire d'indiquer les nouvelles adresses dans le fichier de configuration principale (``geonature/config/geonature_config.toml`` pour GeoNature, ainsi que celle de UsersHub) ainsi que le fichier ``frontend/src/assets/config.json`` précisant l'URL de l'API au frontend. Pour mettre à jour ce fichier automatiquement, relancer le script ``install/05_install_frontend.sh``. Message d'erreur lors de la compilation du frontend """"""""""""""""""""""""""""""""""""""""""""""""""" @@ -50,14 +50,12 @@ Vous pouvez essayer de stopper les backends durant le build du frontend : $ sudo systemctl stop geonature $ sudo systemctl stop geonature-worker - $ sudo systemctl stop taxhub $ sudo systemctl stop usershub $ cd frontend $ nvm use $ npm run build $ sudo systemctl start geonature $ sudo systemctl start geonature-worker - $ sudo systemctl start taxhub $ sudo systemctl start usershub diff --git a/docs/admin-manual.rst b/docs/admin-manual.rst index 8b67bb9aad..4c1690fd39 100644 --- a/docs/admin-manual.rst +++ b/docs/admin-manual.rst @@ -7,7 +7,7 @@ Architecture GeoNature possède une architecture modulaire et s'appuie sur plusieurs "services" indépendants pour fonctionner : - UsersHub et son sous-module d'authentification Flask (https://github.com/PnX-SI/UsersHub-authentification-module) sont utilisés pour gérer le schéma de BDD ``ref_users`` (actuellement nommé ``utilisateurs``) et l'authentification. UsersHub permet une gestion centralisée de ses utilisateurs (listes, organismes, applications), utilisable par les différentes applications de son système d'informations. -- TaxHub (https://github.com/PnX-SI/TaxHub) est utilisé pour la gestion du schéma de BDD ``ref_taxonomy`` (actuellement nommé ``taxonomie``). L'API de TaxHub est utilisée pour récupérer des informations sur les espèces et la taxonomie en général. +- TaxHub (https://github.com/PnX-SI/TaxHub) est utilisé pour la gestion du schéma de BDD ``ref_taxonomy`` (actuellement nommé ``taxonomie``). L'API de TaxHub est utilisée pour récupérer des informations sur les espèces et la taxonomie en général. TaxHub est intégré à GeoNature depuis sa version 2.15. - Un sous-module Flask (https://github.com/PnX-SI/Nomenclature-api-module/) a été créé pour une gestion centralisée des nomenclatures (https://github.com/PnX-SI/Nomenclature-api-module/), il pilote le schéma ``ref_nomenclature``. - ``ref_geo`` est le schéma de base de données qui gère le référentiel géographique. Il est utilisé pour gérer les zonages, les communes, le MNT, le calcul automatique d'altitude et les intersections spatiales. @@ -357,11 +357,6 @@ Cette section liste les branches Alembic disponibles et leur impact sur la base * ``habitats_inpn_data`` : Insère le référentiel HABREF de l’INPN en base. Fourni par Habref-api-module. * ``ref_geo`` : Créé le schéma ``ref_geo``. Fourni par RefGeo. -Si vous utilisez TaxHub, vous pouvez être intéressé par les branches suivantes : - -* ``taxhub`` : Déclare l’application TaxHub dans la liste des applications. Fourni par TaxHub. -* ``taxhub-admin`` : Associe le groupe « Grp_admin » issue des données d’exemple à l’application UsersHub et au profil « Administrateur » permettant aux utilisateurs du groupe de se connecter à TaxHub. Fourni par TaxHub. - Si vous utilisez UsersHub, vous pouvez être intéressé par les branches suivantes : * ``usershub`` : Déclare l’application UsersHub dans la liste des applications. Fourni par UsersHub. @@ -556,7 +551,7 @@ Les paramètres généraux dans la table ``gn_profiles.t_parameters`` : Les deux premiers paramètres permettent de filtrer les données dans la vue ``gn_profiles.v_synthese_for_profiles``. Cette vue comporte les données de la synthèse qui répondent aux paramètres et qui alimenteront les profils de taxons. Les clauses WHERE de cette vue peuvent être adaptées pour filtrer les données sur davantage de critères et répondre aux besoins plus spécifiques, mais sa structure doit rester inchangée. -Les paramètres définis par taxon le sont dans la table ``gn_profiles.cor_taxons_profiles_parameters`` : +Les paramètres définis par taxon le sont dans la table ``gn_profiles.cor_taxons_parameters`` : Les profils peuvent être calculés avec des règles différentes en fonction des taxons. Ceux-ci sont définis au niveau du cd_nom, à n'importe quel rang (espèce, famille, règne etc). Ils seront appliqués de manière récursive à tous les taxons situés "sous" le cd_ref paramétré. @@ -564,7 +559,7 @@ Dans le cas où un taxon hérite de plusieurs règles (une définie pour son ord Par exemple, s'il existe des paramètres pour le phylum "Animalia" (cd_nom 183716) et d'autres pour le renard (cd_nom 60585), les paramètres du renard seront appliqués en priorité pour cette espèce, mais les paramètres Animalia s'appliqueront à tous les autres animaux. -Les règles appliquables à chaque taxon sont récupérées par la fonction ``gn_profiles.get_profiles_parameters(cdnom)``. +Les règles appliquables à chaque taxon sont récupérées par la fonction ``gn_profiles.get_parameters(cdnom)``. Pour chaque cd_nom, il est ainsi possible de définir les paramètres suivants : @@ -975,7 +970,6 @@ Logs * Logs d’installation de GeoNature : ``geonature/install/install.log`` * Logs de GeoNature : ``/var/log/geonature/geonature.log`` * Logs du worker Celery : ``/var/log/geonature/geonature-worker.log`` -* Logs de TaxHub : ``/var/log/taxhub.log`` * Logs de UsersHub : ``/var/log/usershub.log`` Commandes GeoNature @@ -1012,18 +1006,15 @@ Démarrer / arrêter les API * Redémarrer GeoNature : ``systemctl restart geonature`` * Vérifier l’état de GeoNature : ``systemctl status geonature`` -Les mêmes commandes sont disponibles pour TaxHub en remplacant ``geonature`` par ``taxhub``. - Supervision des services """""""""""""""""""""""" -- Vérifier que les applications GeoNature et TaxHub sont accessibles en http +- Vérifier que l'application GeoNature est accessible en http - Vérifier que leurs services (API) sont lancés et fonctionnent correctement (tester les deux routes ci-dessous). - Exemple de route locale pour tester l'API GeoNature : http://127.0.0.1:8000/occtax/defaultNomenclatures qui ne doit pas renvoyer de 404. URL absolue : https://urlgeonature/api/occtax/defaultNomenclatures - - Exemple de route locale pour tester l'API TaxHub : http://127.0.0.1:5000/api/taxref/regnewithgroupe2 qui ne doit pas renvoyer de 404. URL absolue : https://urltaxhub/api/taxref/regnewithgroupe2 -- Vérifier que les fichiers de logs de TaxHub et GeoNature ne sont pas trop volumineux pour la capacité du serveur +- Vérifier que le fichier de logs de GeoNature n'est pas trop volumineux pour la capacité du serveur - Vérifier que les services nécessaires au fonctionnement de l'application tournent bien (Apache, PostgreSQL) Maintenance @@ -1051,7 +1042,7 @@ Attention : ne pas stopper le backend (des opérations en BDD en cours pourraien - Redémarrage de PostgreSQL - Si vous effectuez des manipulations de PostgreSQL qui nécessitent un redémarrage du SGBD (``sudo service postgresql restart``), il faut impérativement lancer un redémarrage des API GeoNature et TaxHub pour que celles-ci continuent de fonctionner. Pour cela, lancez les commandes ``sudo systemctl restart geonature`` et ``sudo systemctl restart taxhub`` (GeoNature 2.8+). + Si vous effectuez des manipulations de PostgreSQL qui nécessitent un redémarrage du SGBD (``sudo service postgresql restart``), il faut impérativement lancer un redémarrage de l'API GeoNature pour que celle-ci continue de fonctionner. Pour cela, lancez la commande ``sudo systemctl restart geonature`` (GeoNature 2.8+). **NB**: Ne pas faire ces manipulations sans avertir les utilisateurs d'une perturbation temporaire des applications. @@ -1441,7 +1432,7 @@ Vous pouvez aussi vous inspirer des exemples avancés de migration des données .. include:: import-level-2.rst -Comptes utilisateurs +Authentification -------------------- Demande de création de compte @@ -1618,6 +1609,11 @@ Un lien GeoNature peut déclencher automatiquement une connexion avec l’utilis Exemple : ``_ + +.. include:: admin/authentication_custom.rst + + + .. include:: sensitivity.rst @@ -1700,31 +1696,28 @@ Le paramètre ``id_observers_list`` permet de changer la liste d'observateurs pr Par défaut, l'ensemble des observateurs de la liste 9 (observateurs faune/flore) sont affichés. Personnaliser la liste des taxons et habitats saisissables dans le module -````````````````````````````````````````````````````````````` +````````````````````````````````````````````````````````````````````````` -Le module est fourni avec une liste restreinte de taxons (8 seulement). C'est à l'administrateur de changer ou de remplir cette liste. +Il est possible de limiter la liste des taxons saisissables dans Occtax, en renseignant le paramètre ``id_taxon_list``. Celui-ci n'est pas défini par défaut et c'est donc tout Taxref qui est proposé à la saisie par défaut. -Le paramètre ``id_taxon_list = 100`` correspond à un ID de liste de la table ``taxonomie.bib_listes`` (L'ID 100 correspond à la liste "Saisie Occtax"). Vous pouvez changer ce paramètre avec l'ID de liste que vous souhaitez, ou bien garder cet ID et changer le contenu de cette liste. +Une liste restreinte de taxons (8 seulement) est proposée par défaut (``id_taxon_list = 100``). L'administrateur peut changer, compléter ou supprimer cette liste. -Voici les requêtes SQL pour remplir la liste 100 avec tous les taxons de Taxref à partir du rang ``genre`` : +Le paramètre ``id_taxon_list = 100`` correspond donc à un ID de liste de la table ``taxonomie.bib_listes`` (L'ID 100 correspond à la liste "Saisie Occtax"). -Il faut d'abord remplir la table ``taxonomie.bib_noms`` (table des taxons de sa structure), puis remplir la liste 100, avec l'ensemble des taxons de ``bib_noms`` : +Voici un exemple de requête SQL pour remplir la liste 100 avec tous les taxons de flore de Taxref à partir du rang ``genre`` : .. code-block:: sql - DELETE FROM taxonomie.cor_nom_liste; - DELETE FROM taxonomie.bib_noms; - - INSERT INTO taxonomie.bib_noms(cd_nom,cd_ref,nom_francais) - SELECT cd_nom, cd_ref, nom_vern + INSERT INTO taxonomie.cor_nom_liste (id_liste,id_nom) + WITH tx as (select cd_nom, cd_ref, nom_vern FROM taxonomie.taxref WHERE id_rang NOT IN ('Dumm','SPRG','KD','SSRG','IFRG','PH','SBPH','IFPH','DV','SBDV','SPCL','CLAD','CL', - 'SBCL','IFCL','LEG','SPOR','COH','OR','SBOR','IFOR','SPFM','FM','SBFM','TR','SSTR'); - - INSERT INTO taxonomie.cor_nom_liste (id_liste,id_nom) - SELECT 100,n.id_nom FROM taxonomie.bib_noms n; + 'SBCL','IFCL','LEG','SPOR','COH','OR','SBOR','IFOR','SPFM','FM','SBFM','TR','SSTR') ) + SELECT 100,tr.cd_nom FROM taxonomie.taxref tr + join tx on tx.cd_nom = tr.cd_nom + where tr.regne = 'Plantae'; -Il est également possible d'éditer des listes à partir de l'application TaxHub. +Il est également possible de gérer les listes de taxons avec le module TaxHub. Il est de même possible de restreindre la liste d'habitats proposés dans le module : @@ -1736,7 +1729,7 @@ Avec ``ID_LIST_HABITAT`` faisant référence aux listes définies dans ``ref_hab .. code-block:: sql - -- Création d'une liste restreinte d'habitats pour OccTax + -- Création d'une liste restreinte d'habitats pour Occtax -- (typologie EUNIS de niveau 2) INSERT INTO ref_habitats.cor_list_habitat clh( cd_hab, @@ -1955,7 +1948,7 @@ Les champs additionnels ne sont pas créés comme des colonnes à part entière, Actuellement seul le module Occtax implémente la gestion de ces champs additionnels. -Le backoffice de GeoNature offre une interface de création et de gestion de ces champs additionnels. +Le module "Admin" de GeoNature offre une interface de création et de gestion de ces champs additionnels. Un champ additionnel est définit par: - son nom (nom dans la base de données) @@ -2000,15 +1993,21 @@ Le champs "Attribut additionnels" permet d'ajouter des éléments de configurati - Ajouter un sous-titre descriptif : `{"help" : "mon sous titre"}` - Ajouter des valeurs min/max pour un input `number` : `{"min": 1, "max": 10}` +TaxHub +"""""" + +Module de gestion des taxons (basé sur TaxHub) permettant de faire des listes de taxons ainsi que d'ajouter des attributs et des médias aux taxons. +Voir la documentation de TaxHub : https://taxhub.readthedocs.io/fr/ + Module OCCHAB ------------- Installer le module """"""""""""""""""" -Le module OCCHAB fait parti du coeur de GeoNature. Son installation est au choix de l'administrateur. +Le module OCCHAB fait partie du coeur de GeoNature. Son installation est au choix de l'administrateur. -Pour l'installer, lancer les commande suivante: +Pour l'installer, lancer les commandes suivantes : .. code-block:: console @@ -2022,8 +2021,8 @@ Base de données Le module s'appuie sur deux schémas : -- ``ref_habitats`` correspond au référentiel habitat du MNHN, -- ``pr_occhab`` correspond au schéma qui contient les données d'occurrence d'habitat, basé sur standard du MNHN. +- ``ref_habitats`` correspond au référentiel habitat du SINP, +- ``pr_occhab`` correspond au schéma qui contient les données d'occurrence d'habitat, basé sur standard du SINP. Configuration """"""""""""" @@ -2099,15 +2098,15 @@ Il est aussi possible de passer plusieurs ``type_code`` regroupés dans un même **2.** Configurer les champs des exports -Dans tous les exports, l'ordre et le nom des colonnes sont basés sur la vue servant l'export. Il est possible de les modifier en éditant le SQL des vues en respectant bien les consignes ci-dessous. +Dans tous les exports, l'ordre et le nom des colonnes sont basés sur la vue SQL servant l'export. **Export des observations** -Les exports (CSV, GeoJson, Shapefile) sont basés sur la vue ``gn_synthese.v_synthese_for_export``. +Les exports (CSV, GeoJSON, Geopackage, Shapefile) sont basés sur la vue ``gn_synthese.v_synthese_for_export``. Il est possible de ne pas intégrer certains champs présents dans cette vue d'export. Pour cela modifier le paramètre ``EXPORT_COLUMNS``. -Enlevez la ligne de la colonne que vous souhaitez désactiver. Les noms de colonne de plus de 10 caractères seront tronqués dans le fichier shapefile. +Enlevez la ligne de la colonne que vous souhaitez désactiver. Les noms de colonne de plus de 10 caractères seront tronqués dans l'export au format shapefile. :: @@ -2214,20 +2213,20 @@ Selon les permissions de l'utilisation sur l'action "Export" du module Synthèse **Export des métadonnées** -En plus des observations brutes, il est possible d'effectuer un export des métadonnées associées aux observations. L'export est au format CSV et est construit à partir de la table ``gn_synthese.v_metadata_for_export``. Vous pouvez modifier le SQL de création de cette vue pour customiser votre export (niveau SQL avancé). +En plus des observations brutes, il est possible d'effectuer un export des métadonnées associées aux observations. L'export est au format CSV et est construit à partir de la vue ``gn_synthese.v_metadata_for_export``. -Deux champs sont cependant obligatoire dans la vue : +Deux champs sont cependant obligatoires dans cette vue : - ``jdd_id`` (qui correspond à l'id du JDD de la table ``gn_meta.t_datasets``). Le nom de ce champs est modifiable. Si vous le modifiez, éditez la variable ``EXPORT_METADATA_ID_DATASET_COL``. - ``acteurs``: Le nom de ce champs est modifiable. Si vous le modifiez, éditez la variable ``EXPORT_METADATA_ACTOR_COL`` **Export des statuts taxonomiques (réglementations)** -Cet export n'est pas basé sur une vue, il n'est donc pas possible de l'adapter. +Cet export n'est pas basé sur une vue. **3.** Configurer les seuils du nombre de données pour la recherche et les exports -Par défaut et pour des questions de performance (du navigateur et du serveur) on limite à 50000 le nombre de résultat affiché sur la carte et le nombre d'observations dans les exports. +Par défaut et pour des questions de performance (du navigateur et du serveur) on limite à 50000 le nombre de résultats affichés sur la carte et le nombre d'observations dans les exports. Ces seuils sont modifiables respectivement par les variables ``NB_MAX_OBS_MAP`` et ``NB_MAX_OBS_EXPORT`` : @@ -2303,9 +2302,8 @@ Une commande dans TaxHub permet de désactiver automatiquement les textes en deh :: - cd ~/taxhub - source venv/bin/activate - flask taxref enable-bdc-statut-text -d -d --clean + source ~/geonature/backend/venv/bin/activate + geonature taxref enable-bdc-statut-text -d -d --clean **6.** Définir des filtres par défaut @@ -2385,19 +2383,23 @@ Liste des propriétés disponibles : Validation automatique """""""""""""""""""""" + Depuis la version 2.14, il est possible d'activer la validation automatique d'observations. Activation `````````` -L'activation de la validation automatique s'effectue en ajoutant la ligne suivante dans le fichier de configuration du module de validation ``config/validation_config.toml``: + +L'activation de la validation automatique s'effectue en ajoutant la ligne suivante dans le fichier de configuration du module de validation ``config/validation_config.toml`` : :: AUTO_VALIDATION_ENABLED = true -Condition de validation automatique -``````````````````````````````````` -Une observation sera validée automatiquement si elle rencontre les conditions suivantes: +Conditions de validation automatique +```````````````````````````````````` + +Une observation sera validée automatiquement si elle rencontre les conditions suivantes : + * Son statut de validation est ``En attente de validation`` * Si le score calculé à partir du profil de taxons est de 3. Se référer à la section `Profils de taxons`_ pour plus d'informations. @@ -2408,7 +2410,7 @@ Si ces conditions sont remplies, alors le statut de validation de l'observation Modification de la périodicité de la validation automatique ``````````````````````````````````````````````````````````` -Le processus de validation automatique est executé à une fréquence définie, par défaut toutes les heures. Si toutefois, vous souhaitez diminuer ou augmenter la durée entre chaque validation automatique, définissez cette dernière dans le fichier de configuration (``config/validation_config.toml``) dans la variable ``AUTO_VALIDATION_CRONTAB``. +Le processus de validation automatique est exécuté à une fréquence définie, par défaut toutes les heures. Si toutefois, vous souhaitez diminuer ou augmenter la durée entre chaque validation automatique, définissez cette dernière dans le fichier de configuration (``config/validation_config.toml``) dans la variable ``AUTO_VALIDATION_CRONTAB``. :: @@ -2420,4 +2422,27 @@ Ce paramètre est composé de cinq valeurs, chacune séparée par un espace: min Modification de la fonction de validation automatique ````````````````````````````````````````````````````` -Dans GeoNature, la validation automatique est effectuée par une fonction en ``PL/pgSQL`` déclarée dans le schéma ``gn_profiles``. Si toutefois, le fonctionnement de celle-ci ne correspond à vos besoins, indiquer le nom de la nouvelle fonction dans la variable ``AUTO_VALIDATION_SQL_FUNCTION``. Attention, cette fonction doit aussi être stockée dans le schema ``gn_profiles``. Pour vous aidez, n'hésitez pas à regarder la définition de la fonction par défaut nommée ``fct_auto_validation``. + +Dans GeoNature, la validation automatique est effectuée par une fonction en ``PL/pgSQL`` déclarée dans le schéma ``gn_profiles``. Si toutefois, le fonctionnement de celle-ci ne correspond pas à vos besoins, indiquez le nom de la nouvelle fonction dans la variable ``AUTO_VALIDATION_SQL_FUNCTION``. Attention, cette fonction doit aussi être stockée dans le schema ``gn_profiles``. Pour vous aider, n'hésitez pas à regarder la définition de la fonction par défaut nommée ``fct_auto_validation``. + +Module TaxHub +------------- + +Depuis la version 2.15 de GeoNature, TaxHub est integré comme un module de GeoNature. Il est disponible depuis le module "Admin" de GeoNature. + +L'emplacement de stockage des médias est contrôlé par le paramètre `MEDIA_FOLDER`. Les médias de TaxHub seront à l'emplacement `/taxhub`. Par défaut tous les médias de GeoNature sont stockés dans le répertoire de GeoNature : `/backend/media`. Via ce paramètre, il est possible de mettre un chemin absolu pour stocker les médias n'importe où ailleurs sur votre serveur. + +Gestion des permissions +``````````````````````` + +La gestion des permissions du module TaxHub est entierement gérée par le module de gestion de permissions de GeoNature. Dans le cas d'une installation standalone de TaxHub, se réferer à la documentation de TaxHub pour la gestion des permissions. + +Les permissions du module TaxHub peuvent être reglées aux trois niveaux (objets) suivants : + +- TAXONS : permet voir et modifier des taxons (ajout de médias, d'attributs et association de taxons à des listes) +- THEMES : permet de voir / créer / modifier / supprimer des thèmes. Les thèmes sont des groupes d'attributs +- LISTES : permet de voir / créer / modifier / supprimer des listes de taxons +- ATTRIBUTS : permet de voir / créer / modifier / supprimer des attributs de taxons + + +.. include:: import_doc.rst diff --git a/docs/admin/authentication_custom.rst b/docs/admin/authentication_custom.rst new file mode 100644 index 0000000000..c23e01920a --- /dev/null +++ b/docs/admin/authentication_custom.rst @@ -0,0 +1,109 @@ + +Se connecter à d'autres fournisseurs d'identités +"""""""""""""""""""""""""""""""""""""""""""""""" +Depuis la version 2.15, il est maintenant possible de se connecter à GeoNature à l'aide de fournisseurs d'identités externes (comme Google, GitHub ou INPN). +Pour cela, il est nécessaire d'implémenter le protocole de connexion pour permettre à GeoNature de communiquer avec ces fournisseurs. +Actuellement, GeoNature vient avec plusieurs protocoles de connexion implémentés, tels que : + +- OpenID +- OpenIDConnect (OAuth2.0) +- GeoNature Externe + +Ajouter un nouveau fournisseur d'identité +```````````````````````````````````````````` + +Pour ajouter un nouveau fournisseur d'identités à votre instance de GeoNature, vous devez ajouter une section ``[[AUTHENTICATION.PROVIDERS]]`` dans la partie ``AUTHENTICATION`` de votre fichier de configuration. +Chaque section doit comporter deux variables obligatoires: ``module`` et ``id_provider``. La variable ``module`` indique le chemin vers la classe Python qui implémente le protocole de connexion, tandis que ``id_provider`` indique l'identifiant unique du fournisseur d'identité. +Vous devez également ajouter les variables de configuration spécifiques au protocole de connexion correspondant. + +Dans l'exemple ci-dessous, on déclare deux fournisseurs d'identités : le premier est le fournisseur d'identité par défaut (local) et le deuxième permet de se connecter à l'INPN. + +.. code:: toml + + [AUTHENTICATION] + DEFAULT_RECONCILIATION_GROUP_ID = 2 + [[AUTHENTICATION.PROVIDERS]] + module="pypnusershub.auth.providers.default.LocalProvider" + id_provider="local_provider" + + [[AUTHENTICATION.PROVIDERS]] + module="pypnusershub.auth.providers.cas_inpn_provider.AuthenficationCASINPN" + id_provider="connexion_inpn_1" + WS_ID ="secret" + WS_PASSWORD ="secret" + + +.. note:: + Les protocoles de connexion implémentés sont les suivants : + - ``pypnusershub.auth.providers.default.LocalProvider`` : protocole de connexion par défaut dans GeoNature. + - ``pypnusershub.auth.providers.cas_inpn_provider.AuthenficationCASINPN`` : CAS de l'INPN. + - ``pypnusershub.auth.providers.openid_provider.OpenIDConnectProvider`` : OpenIDConnect. + - ``pypnusershub.auth.providers.openid_provider.OpenIDProvider`` : OpenID. + - ``pypnusershub.auth.providers.usershub_provider.ExternalUsersHubAuthProvider`` : Autre application utilisant ``UsersHub-authentification-module`` comme système d'authentification. + + Vous pouvez consulter la documentation détaillée sur le `lien suivant `_ pour obtenir la liste et les descriptions des paramètres de configuration de chaque protocole de connexion. + +.. warning:: + Soyez prudent lors de la modification de la variable de configuration ``AUTHENTICATION.PROVIDERS``. Si vous n'indiquez pas le fournisseur d'identité par défaut, vous ne pourrez plus vous connecter à GeoNature avec l'authentification par défaut. Par conséquent, si vous souhaitez également utiliser l'authentification par défaut de GeoNature en plus d'un autre fournisseur d'identité, vous devez le redéclarer dans la configuration. (voir l'exemple ci-dessus) + +Se connecter à un autre GeoNature +`````````````````````````````````` + +Si vous souhaitez ajouter le moyen de se connecter à l'aide d'un autre GeoNature, nous avons crée un module décrivant le protocole de connexion nécessaire : ``pypnusershub.auth.providers.usershub_provider.ExternalUsersHubAuthProvider``. + +Pour utiliser ce dernier, ajouter la section ``[[AUTHENTICATION.PROVIDERS]]`` suivante dans la partie ``AUTHENTICATION`` de la configuration : + +.. code:: toml + + [[AUTHENTICATION.PROVIDERS]] + module="pypnusershub.auth.providers.usershub_provider.ExternalUsersHubAuthProvider" + id_provider="autre_geonature" + login_url="/login" + logout_url="/logout" + +Créer son propre module de connexion +```````````````````````````````````` + +Si les protocoles de connexion que nous avons implémentés ne vous suffisent pas, vous pouvez ajouter votre propre protocole de connexion en utilisant la classe ``pypnusershub.auth.Authentication``. + +.. code:: python + + from marshmallow import Schema, fields + from typing import Any, Optional, Tuple, Union + + from pypnusershub.auth import Authentication, ProviderConfigurationSchema + from pypnusershub.db import models, db + from flask import Response + + + class NEW_PROVIDER(Authentication): + is_external = True # si redirection vers un portail de connexion externe + + def authenticate(self, *args, **kwargs) -> Union[Response, models.User]: + pass # doit retourner un utilisateur (User) ou rediriger (flask.Redirect) vers le portail de connexion du fournisseur d'identités + + def authorize(self): + # appeler par /auth/authorize si redirection d'un portail de connexion externe + pass # doit retourner un utilisateur + + def revoke(self): + pass # si une action spécifique doit être faite lors de la déconnexion + + def configure(self, configuration: Union[dict, Any]): + class SchemaConf(ProviderConfigurationSchema): + VAR = fields.String(required=True) + configuration = SchemaConf().load(configuration) # Si besoin d'un processus de validation + ...# Configuration du fournisseur d'identités + + +.. note:: + Plus de détails sur la classe ``pypnusershub.auth.Authentication`` sont disponibles dans la documentation de l'`API `_. + + +Désactiver l'authentification par défaut +```````````````````````````````````````` + +Si vous souhaitez désactiver l'authentification par défaut au profit d'un ou plusieurs autres fournisseurs d'identités, il suffit de ne pas déclarer celui-ci dans la section `[[AUTHENTICATION.PROVIDERS]]` dans la partie `AUTHENTICATION` de la configuration. + +.. note:: + Si un seul fournisseur d'identités (différent de l'authentification par défaut) est déclaré dans la section `[[AUTHENTICATION.PROVIDERS]]` de la configuration, l'utilisateur sera redirigé automatiquement vers le portail de connexion de ce dernier. diff --git a/docs/conf.py b/docs/conf.py index 375bc0cbfa..4a63e12b6c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,7 +61,7 @@ # General information about the project. project = "GeoNature" -copyright = "2018-2023, PnE, PnC" +copyright = "2018-2024, Parc National des Écrins, Parc National des Cévennes" author = "PnE, PnC" # The version info for the project you're documenting, acts as replacement for diff --git a/docs/development.rst b/docs/development.rst index 363cec9448..651e98c7ce 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -27,7 +27,7 @@ API GeoNature utilise : -- l'API de TaxHub (recherche taxon, règne et groupe d'un taxon...) +- l'API de TaxHub (recherche taxon, règne et groupe d'un taxon...), intégrée à GeoNature depuis sa version 2.15 - l'API du sous-module Nomenclatures (typologies et listes déroulantes) - l'API du sous-module d'authentification de UsersHub (login/logout, récupération du CRUVED d'un utilisateur) - l'API de GeoNature (get, post, update des données des différents modules, métadonnées, intersections géographiques, exports...) @@ -232,7 +232,6 @@ Il est nécessaire de changer la configuration du fichier ``config/geonature_con URL_APPLICATION = 'http://127.0.0.1:4200' API_ENDPOINT = 'http://127.0.0.1:8000' - API_TAXHUB = 'http://127.0.0.1:5000/api' N’oubliez pas les :ref:`actions à effectuer après modification de la configuration `. @@ -243,16 +242,9 @@ Autres extensions en développement Il n'est pas forcémment utile de passer toutes les extensions en mode dévelomment. Pour plus d'informations, référez-vous aux documentations dédiées : -- https://taxhub.readthedocs.io/fr/latest/installation.html#developpement +- https://taxhub.readthedocs.io/fr/latest/developpement.html - https://usershub.readthedocs.io/fr/latest/ -Si toutefois TaxHub retourne une erreur 500 et ne répond pas sur l'URL http://127.0.0.1:5000, alors vous pouvez avoir besoin de passer TaxHub en mode développement : - -.. code-block:: bash - - source ~/taxhub/venv/bin/activate - flask run - Debugger avec un navigateur *************************** diff --git a/docs/https.rst b/docs/https.rst index 7d577558b0..2e1739bd7f 100644 --- a/docs/https.rst +++ b/docs/https.rst @@ -40,7 +40,7 @@ Activer les modules ``ssl``, ``headers`` et ``rewrite`` puis redémarrer Apache sudo a2enmod headers sudo apachectl restart -Les fichiers de configuration des sites TaxHub et UsersHub ne sont pas à modifier, ils seront automatiquement associés à la configuration HTTPS. En revanche, la configuration de GeoNature doit être mise à jour. +Les fichiers de configuration du site UsersHub n'est pas à modifier, il sera automatiquement associé à la configuration HTTPS. En revanche, la configuration de GeoNature doit être mise à jour. Configuration de l'application GeoNature @@ -60,7 +60,6 @@ Modifier les éléments suivants : URL_APPLICATION = 'https://mondomaine.fr/geonature' API_ENDPOINT = 'https://mondomaine.fr/geonature/api' - API_TAXHUB = 'https://mondomaine.fr/taxhub/api' Pour que ces modifications soient prises en compte, exécuter les :ref:`actions à effecture après modification de la configuration `. diff --git a/docs/import-level-1.rst b/docs/import-level-1.rst index f282285994..5c71f2a645 100644 --- a/docs/import-level-1.rst +++ b/docs/import-level-1.rst @@ -138,20 +138,6 @@ Ajouter le champ ``entity_source_pk_value`` dans ton INSERT et ``gid`` dans le S On pourrait aussi remplir ``cor_observers_synthese`` si on le veut et si on a les observateurs présents dans les données, en les faisant correspondre avec leurs ``id_role``. -L'intégration de données dans la Synthèse peut faire apparaitre des nouveaux taxons présents sur le territoire. Si vous souhaitez les proposer à la saisie dans Occtax, il faut les ajouter dans ``taxonomie.bib_noms`` puis dans la liste "Saisie Occtax". - -.. code:: sql - - -- Remplir taxonomie.bib_noms avec les nouveaux noms présents dans la synthèse - INSERT INTO taxonomie.bib_noms (cd_nom, cd_ref) - SELECT DISTINCT s.cd_nom, t.cd_ref - FROM gn_synthese.synthese s - JOIN taxonomie.taxref t - ON s.cd_nom = t.cd_nom - WHERE not s.cd_nom IN (SELECT DISTINCT cd_nom FROM taxonomie.bib_noms); - -Il faudrait ensuite les ajouter à la liste "Saisie Occtax", pour que ces nouveaux noms soient proposés à la saisie dans le module Occtax de GeoNature. - L'installation de GeoNature intègre les communes de toute la France métropolitaine. Pour alléger la table ``ref_geo.l_areas``, il peut être pertinent de supprimer les communes en dehors du territoire de travail. Par exemple, supprimer toutes les communes en dehors du département. Pour retrouver le détail de toutes les communes du département Bouches-du-Rhône : diff --git a/docs/import-level-2.rst b/docs/import-level-2.rst index aaab550099..6d5dc72306 100644 --- a/docs/import-level-2.rst +++ b/docs/import-level-2.rst @@ -191,67 +191,7 @@ Attention, pgAdmin va tronquer le résultat. Pour obtenir l'ensemble de la requ Voir la requête d'import en synthèse à la fin de cette page. -7 - On gère les nouveaux taxons vis à vis la saisie -``````````````````````````````````````````````````` - -Gestion des taxons dans ``taxonomie.bib_noms`` et de la liste des taxons saisissables dans Occtax. - -Cette étape est optionnelle et va permettre de rajouter les nouveaux taxons intégrés dans la synthèse dans la table des taxons de votre territoire (``taxonomie.bib_noms``) et dans la liste des taxons saisissables dans Occtax (``cor_nom_liste``). - -**Création d'une table temporaire** - -.. code:: sql - - CREATE TABLE gn_imports.new_noms - ( - cd_nom integer NOT NULL, - cd_ref integer NOT NULL, - nom_fr character varying, - array_listes integer[], - CONSTRAINT new_noms_pkey PRIMARY KEY (cd_nom) - ); - -**Insertion des nouveaux taxons dans cette table et calcul des listes** - -.. code:: sql - - TRUNCATE TABLE gn_imports.new_noms; - INSERT INTO gn_imports.new_noms - SELECT DISTINCT - i.cd_nom, - t.cd_ref, - split_part(t.nom_vern, ',', 1), - array_agg(DISTINCT l.id_liste) AS array_listes - FROM gn_imports.testimport i - LEFT JOIN taxonomie.taxref t ON t.cd_nom = i.cd_nom - LEFT JOIN taxonomie.bib_listes l ON id_liste = 100 - WHERE i.cd_nom NOT IN (SELECT cd_nom FROM taxonomie.bib_noms) - GROUP BY i.cd_nom, t.cd_ref, nom_vern; - -**Insertion dans ``bib_noms``** - -.. code:: sql - - SELECT setval('taxonomie.bib_noms_id_nom_seq', (SELECT max(id_nom) FROM taxonomie.bib_noms), true); - INSERT INTO taxonomie.bib_noms(cd_nom, cd_ref, nom_francais) - SELECT cd_nom, cd_ref, nom_fr FROM gn_imports.new_noms; - -**Insertion dans ``cor_nom_liste``** - -.. code:: sql - - INSERT INTO taxonomie.cor_nom_liste (id_liste, id_nom) - SELECT unnest(array_listes) AS id_liste, n.id_nom - FROM gn_imports.new_noms tnn - JOIN taxonomie.bib_noms n ON n.cd_nom = tnn.cd_nom; - -Si on veut nettoyer et qu'on est sur de ne plus en avoir besoin - -.. code:: sql - - DROP TABLE gn_imports.new_noms; - -8 - Déplacement de la table importée (facultatif) +7 - Déplacement de la table importée (facultatif) ````````````````````````````````````````````````` On peut si on le souhaite déplacer la table vers une destination d'archivage diff --git a/docs/import_doc.rst b/docs/import_doc.rst new file mode 100644 index 0000000000..5981f6d37f --- /dev/null +++ b/docs/import_doc.rst @@ -0,0 +1,311 @@ + +Module d’import +--------------- +Ce module permet d’importer des données depuis un fichier CSV dans [GeoNature](https://github.com/PnX-SI/GeoNature). + + +Configuration du module +^^^^^^^^^^^^^^^^^^^^^^^ + +Vous pouvez modifier la configuration du module dans le fichier +`geonature_config.toml` dans le dossier `config` de GeoNature, en vous inspirant +du fichier `default_config.toml.example` et en surcouchant les paramètres que vous souhaitez +(champs affichés en interface à l'étape 1, préfixe des champs ajoutés par le module, +répertoire d'upload des fichiers, SRID, encodage, séparateurs, etc). + +Pour appliquer les modifications de la configuration du module, consultez +la [rubrique dédiée de la documentation de GeoNature](https://docs.geonature.fr/installation.html#module-config). + +Configuration avancée +""""""""""""""""""""" + +Une autre partie se fait directement dans la base de données, dans les +tables `dict_fields` et `dict_themes`, permettant de masquer, ajouter, +ou rendre obligatoire certains champs à renseigner pour l'import. Un +champs masqué sera traité comme un champs non rempli, et se verra +associer des valeurs par défaut ou une information vide. Il est +également possible de paramétrer l'ordonnancement des champs (ordre, +regroupements dans des blocs) dans l'interface du mapping de champs. A +l'instar des attributs gérés dans TaxHub, il est possible de définir +des "blocs" dans la table `gn_imports.dict_themes`, et d'y attribuer +des champs (`dict_fields`) en y définissant leur ordre d'affichage. + +Permissions du module +^^^^^^^^^^^^^^^^^^^^^ + +La gestions des permissions dans le module d'import se fait via le réglage +du CRUVED à deux niveaux : au niveau de l'objet "import" et au +niveau de l'objet "mapping". + +- Le CRUVED sur l'objet "import" permet de gérer + l'affichage des imports. Une personne ayant un R = 3 verra tous les + imports de la plateforme, un R = 2 seulement les siens et ceux de son organisme + et un R = 1 seulement les siens. +- Les jeux de données selectionnables par un utilisateur lors de la + création d'un import sont eux controlés par les permissions + sur le C de l'objet "import" (combiné au R du module "Métadonnées). +- Les mappings constituent un "objet" du module d'import disposant + de droits paramétrables pour les différents utilisateurs, + indépendamment des permissions sur les imports. Le réglage des + permissions se fait dans le module "Admin" de GeoNature ("Admin" -\> + "Permissions"). +- Certains mappings sont définis comme "public" et sont vus par tout + le monde. Seuls les administrateurs (U=3) et les propriétaires de ces + mappings peuvent les modifier. +- Par exemple, avec un C = 1, R = 2, U = 1 sur les mappings, un utilisateur pourra créer + des nouveaux mappings (des modèles d'imports), modifier ses propres + mappings et voir les mappings de son organisme ainsi que les + mappings "publics". +- Si vous modifiez un mapping sur lequel vous n'avez pas les droits, + il vous sera proposé de créer un nouveau mapping vous appartenant + avec les modifications que vous avez faites, mais sans modifier le + mapping initial. Lorsqu'on a les droits de modification sur un mapping, + il est également possible de ne pas enregistrer les modifications + faites à celui-ci pour ne pas écraser le mapping inital (le mapping + sera sauvegardé uniquement avec l’import). + +Contrôles et transformations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| Name | Description | Step | Type | ++===============================================+====================================================================================================================================================================================================================================================================+=====================+=====================+ +| file_name_error | Le nom du fichier ne contient que des chiffres | upload | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| file_name_error | Le nom du fichier commence par un chiffre | upload | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| file_name_error | Le nom du fichier fait plus de 50 caractères | upload | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| no_data | Aucune données n'est présente dans le fichier | upload | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| extension_error | L'extenstion du fichier n'est pas .csv ou .geojson | upload | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| no_file | Aucun fichier détecté | upload | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| empty | Auncun fichier envyé | upload | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| max_size | La taille du fichier est plus grande que la taille définit en paramètre | upload | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| source-error(goodtables lib) | Erreur de lecture du fichier lié à une inconsistence de données. | csv | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| format-error(goodtables lib) | Erreur de lecture (format des données incorrect). | csv | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| encoding-error(goodtables lib) | Erreur de lecture (problème d'encodage). | csv | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| blank-header(goodtables lib) | Il existe une colonne vide dans les noms de colonnes. | csv | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| duplicate-header(goodtables lib) | Plusieurs colonnes ont le même nom | csv | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| blank-row(goodtables lib) | Une ligne doit avoir au moins une colonne non vide. | csv | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| duplicate-row(goodtables lib) | Ligne dupliquée. | csv | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| extra-value(goodtables lib) | Une ligne a plus de colonne que l'entête | csv | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| missing-value(goodtables lib) | Une ligne a moins de colonne que l'entête. | csv | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| wrong id_dataset | L'utilisateur n'as pas les droits d'importer dans ce JDD (à vérifier/implémenter ?) | load raw data to db | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| psycopg2.errors.BadCopyFileFormat | Erreur lié à un problème de séparateur | load row data to db | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| missing_value | Valeur manquante pour un champ obligatoire | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| incorrect_date | Format de date incorrect - Format attendu est YYYY-MM-DD ou DD-MM-YYYY (les heures sont acceptées sous ce format: HH:MM:SS) | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| incorrect_uuid | Format d'UUID incorrect | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| incorrect_length | Chaine de charactère trop longue par rapport au champs de destination | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| incorrect_integer | Valeur incorect (ou négative) pour un champs de type entier | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| incorrect_cd_nom | Le cd_nom fourni n'est pas présent dans le taxref de l'instance | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| incorrect_cd_hab | Le cd_hab fourni n'est pas présent dans le habref de l'instance | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| date_min > date_max | date min > date_max | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| missing_uuid | UUID manquant (si calculer les UUID n'est pas coché) | data cleaning | warning | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| duplicated uuid | L'UUID fourni est déjà présent en base (dans la table synthese) - désactivable pour les instances avec beaucoup de données: paramètre `ENABLE_SYNTHESE_UUID_CHECK` | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| unique_id_sinp missing column | Si pas de colonne UUID fournie est que "calculer les UUID" est activé, on crée une colonne et on crée des UUID dedans | data cleaning | checks and corrects | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| unique_id_sinp missing values | Si UUID manquant dans une colonne UUID fournie et que "calculer les UUID" est activé, on calcul un UUID pour les valeurs manquantes | data cleaning | checks and corrects | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| missing count_min value | Si des valeurs sont manquantes pour denombrement_min, la valeur est remplacée par le paramètre DEFAULT_COUNT_VALUE | data cleaning | checks and corrects | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| missing count_max values | Si des valeurs sont manquantes pour denombrement_min, on met denombrement_min = denombrement_max | data cleaning | checks and correct | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| missing count_min column | Si pas de colonne dénombrement_min, on cree une colonne et on met la valeur du paramètre DEFAULT_COUNT_VALUE | data cleaning | checks and corrects | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| missing count_max column | Si pas de colonne denombrement_max on cree une colonne et on met denombrement_min = denombrement_max | data cleaning | checks and corrects | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| missing altitude_min and altitude_max columns | Creation de colonne et calcul si 'calcul des altitudes' est coché | data cleaning | checks and corrects | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| missing altitude_min or altitude_max values | Les altitdes sont calculées pour les valeurs manquantes si l'option est activée | data cleaning | checks and corrects | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| altitude_min > altitude_max | altitude_min > altitude_max | data cleaning | checks and corrects | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| profondeur_min > profondeur_max | profondeur_min > profondeur_max | data cleaning | checks and corrects | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| count_min > count_max | count_min > count_max | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| entity_source_pk column missing | Si pas de colonne fournie, création d'une colonne remplie avec un serial "gn_pk" | data cleaning | checks and corrects | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| entity_source_pk duplicated | entity_source_pk value dupliqué | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| incorrect_real | Valeur incorect pour un réel | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| geometry_out_of_box | Coordonnées géographiques en dehors de la bounding-box de l'instance (paramètre: `INSTANCE_BOUNDING_BOX` - desactivable via paramètre `ENABLE_BOUNDING_BOX_CHECK` | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| geometry_out_of_projection | Coordonnées géographiques en dehors du système de projection fourni | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| multiple_code_attachment | Plusieurs code de rattachement fourni pour une seule colonne (Ex code_commune = 05005, 05003) | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| multiple_attachment_type_code | Plusieurs code de rattachement fourni pour une seule ligne (code commune + code maille par ex) | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| code rattachement invalid | Le code de rattachement (code maille/département/commune) n'est pas dans le référentiel géographiques de GeoNature | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| Erreur de nomenclature | Code nomenclature erroné ; La valeur du champ n’est pas dans la liste des codes attendus pour ce champ. | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| Erreur de géometrie | Géométrie invalide ; la valeur de la géométrie ne correspond pas au format WKT. | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| Géoréférencement manquant | Géoréférencement manquant ; un géoréférencement doit être fourni, c’est à dire qu’il faut livrer : soit une géométrie, soit une ou plusieurs commune(s), ou département(s), ou maille(s) | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| Preuve numérique incorect | La preuve numérique fournie n'est pas une URL | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| Erreur champs conditionnel (désactivable) | Le champ dEEFloutage doit être remplit si le jeu de données est privé | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| Erreur champs conditionnel (désactivable) | Le champ reference_biblio doit être remplit si le statut source est 'Littérature' | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| Erreur champs preuve (désactivable) | si le champ “preuveExistante” vaut oui, alors l’un des deux champs “preuveNumérique” ou “preuveNonNumérique” doit être rempli. A l’inverse, si l’un de ces deux champs est rempli, alors “preuveExistante” ne doit pas prendre une autre valeur que “oui” (code 1) | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| Erreur de rattachement(1) - désactivable | Vérifie que si type_info_geo = 1 (Géoréférencement) alors aucun rattachement n'est fourni | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ +| Erreur de rattachement(2) - désactivable | Si une entitié de rattachement est fourni alors le type_info_geo ne doit pas être null | data cleaning | error | ++-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+---------------------+ + + + +Utilisation du module d'imports +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Note : le processus a un petit peu évoluer en v2 avec notamment une +étape supplémentaire. + +Le module permet de traiter un fichier CSV +(GeoJSON non disponible dans la v2 pour le moment) sous toute +structure de données, d'établir les correspondances nécessaires entre +le format source et le format de la synthèse, et de traduire le +vocabulaire source vers les nomenclatures SINP. Il stocke et archive les +données sources et intègre les données transformées dans la synthèse de +GeoNature. Il semble préférable de prévoir un serveur disposant à minima +de 4 Go de RAM. + +1. Une fois connecté à GeoNature, accédez au module Imports. L'accueil + du module affiche une liste des imports en cours ou terminés, selon + les permissions de l'utilisateur connecté. Vous pouvez alors finir un + import en cours, ou bien commencer un nouvel import. + + .. image:: https://geonature.fr/docs/img/import/gn_imports-01.jpg + +2. Choisissez à quel JDD les données importées vont être associées. Si + vous souhaitez les associer à un nouveau JDD, il faut l'avoir créé + au préalable dans le module Métadonnées. + + .. image:: https://geonature.fr/docs/img/import/gn_imports-02.jpg + +3. Chargez le fichier CSV (GeoJSON non disponible dans la v2 pour le moment) à importer. + + .. image:: https://geonature.fr/docs/img/import/gn_imports-03.jpg + +4. Mapping des champs. Il s'agit de faire correspondre les champs du + fichier importé aux champs de la Synthèse (basé sur le standard + "Occurrences de taxons" du SINP). Vous pouvez utiliser un mapping + déjà existant ou en créer un nouveau. Le module contient par défaut + un mapping correspondant à un fichier exporté au format par défaut + de la synthèse de GeoNature. Si vous créez un nouveau mapping, il + sera ensuite réutilisable pour les imports suivants. Il est aussi + possible de choisir si les UUID uniques doivent être générés et si + les altitudes doivent être calculées automatiquement si elles ne + sont pas renseignées dans le fichier importé. + + .. image:: https://geonature.fr/docs/img/import/gn_imports-04.jpg + +6. Une fois le mapping des champs réalisé, au moins sur les champs + obligatoires, il faut alors valider le mapping pour lancer le + contrôle des données. Vous pouvez ensuite consulter les éventuelles + erreurs. Il est alors possible de corriger les données en erreurs + directement dans la base de données, dans la table temporaire des + données en cours d'import, puis de revalider le mapping, ou de + passer à l'étape suivante. Les données en erreur ne seront pas + importées et seront téléchargeables dans un fichier dédié à l'issue + du processus. + + .. image:: https://geonature.fr/docs/img/import/gn_imports-05.jpg + +7. Mapping des contenus. Il s'agit de faire correspondre les valeurs + des champs du fichier importé avec les valeurs disponibles dans les + champs de la Synthèse de GeoNature (basés par défaut sur les + nomenclatures du SINP). Par défaut les correspondances avec les + nomenclatures du SINP sous forme de code ou de libellés sont + fournies. + + .. image:: https://geonature.fr/docs/img/import/gn_imports-06.jpg + +8. La dernière étape permet d'avoir un aperçu des données à importer + et leur nombre, avant de valider l'import final dans la Synthèse de + GeoNature. + + .. image:: https://geonature.fr/docs/img/import/gn_imports-07.jpg + +Pour chaque fichier importé, les données brutes sont importées +intialement et stockées en binaire dans le champs `t_imports.source_file`. +Elles sont aussi stockées +dans une table intermédiaire, enrichie au fur et à mesure des étapes de +l'import. + +Liste des contrôles réalisés sur le fichier importé et ses données : + + +Schéma (initial et théorique) des étapes de fonctionnement du module : + +.. image:: https://geonature.fr/docs/img/import/gn_imports_etapes.png + +Modèle de données du schéma `gn_imports` du module (à adapter à la version 2.0.0) : + +.. image:: https://geonature.fr/docs/img/import/gn_imports_MCD-2020-03.png + + +Fonctionnement du module (serveur et BDD) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- J'ai un R d'au moins 1 sur le module Import : J'accède au module et je vois les imports en fonction de mon R. +- J'ai un C d'au moins 1 sur le module Import, je peux créer un import, ou terminer un import auquel j'ai accès. +- J'ai au moins un JDD actif associé au module Import. +- Je créé un nouvel Import. Le C sur le module Import permet de lister mes JDD actifs et associés au module Import, ceux de mon organisme ou tous les JDD actifs associés au module Import. +- Je choisis le JDD auquel je veux associer les données à importer. +- **Etape 1** : J'uploade mon fichier CSV (GeoJSON n'est plus disponible dans la v2 pour le moment). Le contenu du CSV est stocké en binaire dans la table des imports (`gn_imports.t_imports.source_file`). Cela permet d'analyser le fichier (encodage, séparateur...) et de télécharger les données sources. +- **Etape 2** : L'encodage, le format et le séparateur du fichier sont auto-détectés. Je peux les modifier si je le souhaite. Je renseigne le SRID parmi les SRID disponibles dans la configuration du module. +- **Etape 3** : Je choisis un modèle d'Import existant et/ou je mets en correspondance les champs du fichier source avec ceux de la Synthèse de GeoNature. Les modèles d'import listés dépendent des permissions sur l'objet "MAPPING". +La première ligne du fichier binaire est lue pour lister les champs du fichier source. +- Si je choisis un modèle et que je mappe un nouveau champs, ou une valeur différente pour un champs, je peux modifier le modèle existant, en créer un nouveau ou ne sauvegarder ces modifications dans aucun modèle. +- Si j'ai mappé une valeur source différente sur un champs déjà présent dans le modèle, il est écrasé par la nouvelle valeur si je mets à jour le modèle. Actuellement un champs de destination ne peut avoir qu'un seul champs source. Par contre un champs source peut avoir plusieurs champs de destination (`date` → `date_min` et `date` → `date_max`, par exemple). +- Les correspondances des champs sont stockées dans tous les cas en json dans le champs `gn_imports.t_imports.fieldmapping`. Cela permet de pouvoir reprendre les correspondances d'un import, même si le modèle a été modifié entre temps. +- Quand on valide l'étape 3, les données sources des champs mappés sont chargées dans la table d'import temporaire (`gn_imports.t_imports_synthese`) avec une colonne pour la valeur de la source et une pour la valeur de destination. Cela permet à l'application de faire des traitements de transformation et de contrôle sur les données. Les données sources dans des champs non mappées sont importées dans un champs json de cette table (`extra_fields`) +- **Etape 4** : Les valeurs des champs à nomenclature sont déterminées à partir du contenu de la table `gn_imports.t_imports_synthese`. Une nomenclature de destination peut avoir plusieurs valeurs source. Pour chaque type de nomenclature on liste les valeurs trouvées dans le fichier source et on propose de les associer aux valeurs des nomenclatures présentes dans GeoNature. Si le fichier source comprend des lignes vides, on propose en plus de mapper le cas "Pas de valeur". +La gestion des mappings est similaire à l'étape 3 (ils sont stockées cette fois-ci dans le champs `gn_imports.t_imports.contentmapping`). +- **Etape 5** : Il est proposé à l'utilisateur de lancer les contrôles. Ceux-ci sont exécutés en asynchrone dans tous les cas, et une barre de progression est affichée à l'utilisateur. Quand les contrôles sont terminés, le nombre d'erreurs est affiché, ainsi qu'une carte de l'étendue géographique des données et un tableau d'aperçu des données telles qu'elles seront importées. +Si il y a des erreurs, l'utilisateur peut télécharger le fichier des données sources invalides. Elles sont récupérées dans la table `gn_imports.t_imports.source_file` en ne prenant que les lignes qui ont une erreur, en se basant sur les données qui ont le champs `valid=false` dans `gn_imports.t_imports_synthese` +L'utilisateur peut alors lancer l'import des données dans la Synthèse. +Il est lancée en asynchrone dans tous les cas, et un spinner de chargement est affiché tant que l'import est en cours. Si d'autres imports sont en cours, le mécanisme asynchrone gère un système de queue pour les faire les uns après les autres et ne pas saturer le serveur. +- Il est possible de reprendre et modifier un import que celui-ci soit terminé ou non. Il est notamment possible d'uploader un nouveau fichier pour un import existant. En cas de modification d’un import existant, les données sont immédiatement supprimées de la synthèse. Les nouvelles données seront insérées lors de la nouvelle finalisation de l’import. +- Une fois les données importées, les données sont supprimées de la table temporaire (`gn_imports.t_imports_synthese`) +- **Administration des modèles** : Depuis le module ADMIN de GeoNature, il est possible de lister, afficher et modifier les modèles d'import. + + +Financement de la version 1.0.0 : DREAL et Conseil Régional +Auvergne-Rhône-Alpes. + +Financement de la version 2.0.0 : Ministère de la Transition écologique +et UMS Patrinat. + diff --git a/docs/installation-all.rst b/docs/installation-all.rst index a4c849bbda..acccff9f1b 100644 --- a/docs/installation-all.rst +++ b/docs/installation-all.rst @@ -7,11 +7,10 @@ En lançant le script d'installation ci-dessous, l'application GeoNature ainsi q Les applications suivantes seront installées : -- `GeoNature `_ -- `TaxHub `_ qui pilote le schéma ``taxonomie`` +- `GeoNature `_ (incluant `TaxHub `_ qui pilote le schéma ``taxonomie``) - `UsersHub `_ qui pilote le schéma ``utilisateurs`` (le paramètre ``install_usershub_app`` du fichier de configuration ``install_all.ini`` permet de désactiver l'installation de l'application. Il est cependant recommandé d'installer l'application pour disposer d'une interface pour gérer les utilisateurs dans GeoNature) -Si vous disposez déjà de Taxhub ou de UsersHub sur un autre serveur ou une autre base de données et que vous souhaitez installer simplement GeoNature, veuillez suivre la documentation :ref:`installation-standalone`. +Si vous disposez déjà de UsersHub sur un autre serveur ou une autre base de données et que vous souhaitez installer simplement GeoNature, veuillez suivre la documentation :ref:`installation-standalone`. Installation des applications @@ -21,8 +20,7 @@ Commencer la procédure en se connectant au serveur en SSH avec l'utilisateur d Téléchargement ^^^^^^^^^^^^^^ - -Se placer à la racine du ``home`` de l'utilisateur puis récupérer les scripts d'installation (X.Y.Z à remplacer par le numéro de la `dernière version stable de GeoNature `_). Ces scripts installent les applications GeoNature, TaxHub et UsersHub (en option) ainsi que leurs bases de données (uniquement les schémas du coeur) : +* Se placer à la racine du ``home`` de l'utilisateur puis récupérer les scripts d'installation (X.Y.Z à remplacer par le numéro de la `dernière version stable de GeoNature `_). Ces scripts installent les applications GeoNature (incluant TaxHub) et UsersHub (en option) ainsi que leurs bases de données (uniquement les schémas du coeur) : .. code:: console @@ -75,7 +73,6 @@ Une fois l'installation terminée, lancez la commande suivante: Les applications sont disponibles aux adresses suivantes : - http://monip.com/geonature/ -- http://monip.com/taxhub/ - http://monip.com/usershub/ (en option) Vous pouvez vous connecter avec l'utilisateur intégré par défaut (admin/admin). @@ -102,14 +99,13 @@ Si vous rencontrez une erreur, se reporter aux fichiers de logs ``/home/`whoami` Par défaut et par mesure de sécurité, la base de données est accessible uniquement localement par la machine où elle est installée. Pour accéder à la BDD depuis une autre machine (pour s'y connecter avec QGIS, pgAdmin ou autre), vous pouvez consulter cette documentation https://github.com/PnX-SI/Ressources-techniques/blob/master/PostgreSQL/acces-bdd.rst. Attention, exposer la base de données sur internet n'est pas recommandé. Il est préférable de se connecter via un tunnel SSH. QGIS et la plupart des outils d'administration de base de données permettent d'établir une connexion à la base de cette manière. - Attention si vous redémarrez PostgreSQL (``sudo service postgresql restart``), il faut ensuite redémarrer les API de GeoNature, UsersHub et TaxHub : + Attention si vous redémarrez PostgreSQL (``sudo service postgresql restart``), il faut ensuite redémarrer les API de GeoNature et UsersHub : .. code:: console $ sudo systemctl restart geonature $ sudo systemctl restart geonature-worker $ sudo systemctl restart usershub - $ sudo systemctl restart taxhub :Note: diff --git a/docs/installation-docker.rst b/docs/installation-docker.rst index 9104d478f2..0671c8e9e5 100644 --- a/docs/installation-docker.rst +++ b/docs/installation-docker.rst @@ -1,7 +1,7 @@ Docker ****** -L'installation de GeoNature avec Docker est la manière la plus simple de déployer GeoNature, ses 4 modules externes principaux (Import, Export, Dashboard, Monitoring), TaxHub et UsersHub, mais aussi de les mettre à jour, avec seulement quelques lignes de commandes. +L'installation de GeoNature avec Docker est la manière la plus simple de déployer GeoNature, ses 4 modules externes principaux (Import, Export, Dashboard, Monitoring) et UsersHub, mais aussi de les mettre à jour, avec seulement quelques lignes de commandes. Elle permet aussi d'installer GeoNature sur différents systèmes, et pas uniquement sur Debian, comme c'est le cas avec l'installation classique. @@ -12,7 +12,7 @@ Docker Compose Pour déployer facilement GeoNature avec Docker, utilisez le Docker Compose proposé et documenté dans le dépôt `GeoNature-Docker-services `_. -Pour des déploiements Docker plus avancés et spécifiques, des images Docker des différents outils (GeoNature, TaxHub, UsersHub, GeoNature et ses 4 modules externes principaux) sont automatiquement construites et publiées à chaque nouvelle version publiée. +Pour des déploiements Docker plus avancés et spécifiques, des images Docker des différents outils (GeoNature, UsersHub, GeoNature et ses 4 modules externes principaux) sont automatiquement construites et publiées à chaque nouvelle version publiée. Image backend ------------- diff --git a/docs/installation-standalone.rst b/docs/installation-standalone.rst index a277e39901..1fc059f8cd 100644 --- a/docs/installation-standalone.rst +++ b/docs/installation-standalone.rst @@ -1,8 +1,8 @@ Installation de GeoNature uniquement ************************************ -Cette procédure détail l’installation de GeoNature seul, sans TaxHub et UsersHub. -Si vous souhaitez installer GeoNature avec TaxHub et UsersHub, reportez-vous à la section :ref:`installation-all`. +Cette procédure détaille l’installation de GeoNature (incluant TaxHub) sans UsersHub. +Si vous souhaitez installer GeoNature avec UsersHub, reportez-vous à la section :ref:`installation-all`. Installation des dépendances ---------------------------- @@ -111,37 +111,9 @@ Lors de l'installation de la BDD (``02_create_db.sh``), le schéma ``utilisateur UsersHub n'est pas nécessaire au fonctionnement de GeoNature mais il sera utile pour avoir une interface de gestion des utilisateurs, des groupes et de leurs droits. -Par contre il est nécessaire d'installer TaxHub (https://github.com/PnX-SI/TaxHub) pour que GeoNature fonctionne. En effet, GeoNature utilise l'API de TaxHub. Une fois GeoNature installé, il vous faut donc installer TaxHub en le connectant à la BDD de GeoNature, vu que son schéma ``taxonomie`` a déjà été installé par le script ``02_create_db.sh`` de GeoNature. Lors de l'installation de TaxHub, n'installez donc que l'application et pas la BDD. +TaxHub v2 est intégré à GeoNature depuis sa version 2.15.0 -Télécharger TaxHub depuis son dépôt Github depuis la racine de votre utilisateur : - -:: - - cd ~ - wget https://github.com/PnX-SI/TaxHub/archive/X.Y.Z.zip - unzip X.Y.Z.zip - rm X.Y.Z.zip - -en mode développeur: - -``https://github.com/PnX-SI/TaxHub.git`` - -Rendez vous dans le répertoire téléchargé et dézippé, puis "désamplez" le fichier ``settings.ini`` et remplissez la configuration avec les paramètres de connexion à la BDD GeoNature précedemment installée : - -:: - - cp settings.ini.sample settings.ini - nano settings.ini - -Lancer le script d'installation de l'application : - -:: - - ./install_app.sh 2>&1 | tee install_app.log - -Suite à l'execution de ce script, l'application Taxhub a été lancée automatiquement par le superviseur et est disponible à l'adresse ``http://127.0.0.1:5000`` (et l'API, à ``http://127.0.0.1:5000/api``) - -Voir la doc d'installation de TaxHub : https://taxhub.readthedocs.io/ +Voir la documentation de TaxHub : https://taxhub.readthedocs.io/ Voir la doc d'installation de UsersHub : https://usershub.readthedocs.io/ diff --git a/docs/installation.rst b/docs/installation.rst index b4c67152a3..2f8c954820 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -14,8 +14,8 @@ GeoNature repose sur les composants suivants : Deux méthodes d’installation existent : -- :ref:`installation-all` : Installation automatisée de GeoNature, TaxHub et UsersHub. -- :ref:`installation-standalone` : TaxHub et UsersHub ne sont pas installés (mais leurs schémas sont tous de même créés dans la base de données). +- :ref:`installation-all` : Installation automatisée de GeoNature (incluant TaxHub) et UsersHub. +- :ref:`installation-standalone` : UsersHub n'est pas installé (mais son schéma de BDD est tout de même créé dans la base de données de GeoNature). Prérequis @@ -265,7 +265,7 @@ Mise à jour de l'application .. warning:: Vérifiez préalablement la compatibilité des modules que vous utilisez avant de mettre GeoNature à jour. S’il est nécessaire de les mettre à jour, arrêtez vous après le remplacement du dossier par le nouveau code source - (et la récupération éventuelle de la configuration) ; le script de migration de GeoNature s’occupera automatiquement + (et la récupération éventuelle de la configuration); le script de migration de GeoNature s’occupera automatiquement d’installer la nouvelle version du module. La mise à jour de GeoNature consiste à télécharger sa nouvelle version dans un nouveau répertoire, récupérer les fichiers de configuration et de surcouche, ainsi que les médias depuis la version actuelle et de relancer l'installation dans le répertoire de la nouvelle version. @@ -292,7 +292,7 @@ La mise à jour doit être réalisée avec votre utilisateur linux courant (``ge Sauf mentions contraires dans les notes de version, vous pouvez sauter des versions mais en suivant bien les différentes notes de versions intermédiaires. -* Si vous devez aussi mettre à jour TaxHub et/ou UsersHub, suivez leurs notes de versions mais aussi leur documentation (https://usershub.readthedocs.io et https://taxhub.readthedocs.io). +* Si vous devez aussi mettre à jour UsersHub, suivez ses notes de version et sa documentation (https://usershub.readthedocs.io). * Lancez le script de ``migration.sh`` à la racine du dossier ``geonature``: @@ -310,5 +310,5 @@ Dans ce cas, les commandes à exécuter sont : git status # optionnel, pour connaitre l'état et la version ou branche actuellement utilisée git fetch git checkout X.Y.Z # où X.Y.Z est le numéro du tag de la version vers laquelle faire la mise à jour, ou le nom de la branche à utiliser - git submodule update # ou "git config submodule.recurse true" lancé une seule fois, pour que la mise à jour des sous-modules soit relancée automatiquement à chaque git pull + git submodule update # ou "git config submodule.recurse true" lancé une seule fois, pour que la mise à jour des sous-modules soit relancée automatiquement à chaque "git pull" ./install/migration/migration.sh . diff --git a/docs/tests_backend.rst b/docs/tests_backend.rst index c1c1d8fb68..32ae1652cb 100644 --- a/docs/tests_backend.rst +++ b/docs/tests_backend.rst @@ -249,13 +249,7 @@ Cette fonctionnalité s'appuie sur ``pytest`` et son extension ``pytest-benchmar Lancement des tests de performances ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Il existe différentes manières de lancer les tests de performances: - -- La commande ``pytest`` sans arguments. Cette dernière lancera les tests de performances en plus des tests unitaires. -- La commande ``pytest --benchmark-only`` qui lancera uniquement les tests de performances. - - - +Pour lancer les tests de performances, utiliser la commande suivante : ``pytest --benchmark-only`` Ajouter des tests de performances diff --git a/docs/tests_frontend.rst b/docs/tests_frontend.rst index d0aceefc2a..2aa3c1e7c1 100644 --- a/docs/tests_frontend.rst +++ b/docs/tests_frontend.rst @@ -24,6 +24,18 @@ Structure La nomenclature des fichiers de test est XXXXXXX-spec.js (XXXX correspondant au nom du module testé). +Afin d'améliorer la lisibilité des fichiers de test si un module contient beaucoup de tests il est nécessaire de séparer les tests end to end en plusieurs fichiers et les placer dans un dossier portant le nom du module. + +Exemple +""""""" + +.. code-block:: bash + + e2e + ├── import + │ ├── all-steps-spec.js + │ └── list-search-spec.js + Dans chaque fichier la structure des tests est de la forme - une description @@ -68,14 +80,17 @@ Angular et Cypress suggèrent l'ajout de tags de ce type: - test-qa - data-test -Voir https://docs.cypress.io/guides/references/best-practices#Selecting-Elements pour les bonnes pratique de sélection d'éléments: +Il est recommandé d'utiliser un nom explicite pour éviter toutes confusions. Exemple """"""" .. code-block:: HTML -
+ + +Voir https://docs.cypress.io/guides/references/best-practices#Selecting-Elements pour les bonnes pratique de sélection d'éléments. + Lancement ********* diff --git a/frontend/angular.json b/frontend/angular.json index d4fa1496b7..e4c4c27c9d 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -54,6 +54,7 @@ "node_modules/@swimlane/ngx-datatable/themes/material.css", "node_modules/@swimlane/ngx-datatable/assets/icons.css", "src/styles.scss", + "node_modules/material-design-icons/iconfont/material-icons.css", "node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", "node_modules/@circlon/angular-tree-component/src/lib/angular-tree-component.css", "node_modules/leaflet.markercluster/dist/MarkerCluster.css", @@ -64,7 +65,12 @@ "node_modules/leaflet/dist/leaflet.js", "node_modules/leaflet-draw/dist/leaflet.draw.js", "node_modules/leaflet.markercluster/dist/leaflet.markercluster.js", - "node_modules/leaflet.locatecontrol/dist/L.Control.Locate.min.js" + "node_modules/leaflet.locatecontrol/dist/L.Control.Locate.min.js", + "node_modules/@bokeh/bokehjs/build/js/bokeh.min.js", + "node_modules/@bokeh/bokehjs/build/js/bokeh-widgets.min.js", + "node_modules/@bokeh/bokehjs/build/js/bokeh-mathjax.min.js", + "node_modules/@bokeh/bokehjs/build/js/bokeh-gl.min.js", + "node_modules/@bokeh/bokehjs/build/js/bokeh-tables.min.js" ] }, "configurations": { diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts index c8222ec4d6..a9252c86af 100644 --- a/frontend/cypress.config.ts +++ b/frontend/cypress.config.ts @@ -15,6 +15,7 @@ export default defineConfig({ return require('./cypress/plugins/index.js')(on, config); }, baseUrl: 'http://127.0.0.1:4200', - specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}', + specPattern: 'cypress/e2e/**/*spec.{js,jsx,ts,tsx}', + downloadsFolder: 'cypress/downloads', }, }); diff --git a/frontend/cypress/e2e/control-layers-spec.js b/frontend/cypress/e2e/control-layers-spec.js index bf0932a8f0..556470c265 100644 --- a/frontend/cypress/e2e/control-layers-spec.js +++ b/frontend/cypress/e2e/control-layers-spec.js @@ -2,10 +2,10 @@ describe('Testing Leaflet control layers', () => { const controlSelector = '.leaflet-control-layers'; const controlExpandedSelector = `.leaflet-control-layers-expanded`; const overlayersTitleSelector = '[data-qa="title-overlay"]'; - beforeEach(()=> { + beforeEach(() => { cy.geonatureLogin(); cy.visit('/#/'); - }) + }); it('should display "overlayers button controler"', () => { cy.get(controlSelector).should('be.visible'); cy.get(controlSelector).trigger('mouseover'); diff --git a/frontend/cypress/e2e/import/Readme.md b/frontend/cypress/e2e/import/Readme.md new file mode 100644 index 0000000000..b70a9d4158 --- /dev/null +++ b/frontend/cypress/e2e/import/Readme.md @@ -0,0 +1,102 @@ +# Remarques sur les tests au niveau de l'import + +## Notifications + +- pas de tests autour des notifications +- suppression d'un import --> pas de notification +- si import supprimé, le lien de la notification mène à une erreur pas compréhensible + +## Liste Import + +- Le test sur le tri de la colonne "id" ne fonctionne pas (mis sur skip) --> à résoudre coté backend +- la lib de tableau utilisé est obsolète, non maintenue. +- le statut de l'import n'apparait pas sur la liste + +## Import + +### LocalStorage + +La persistence d'une saisie à l'autre de certain paramètres (JDD, CRS, mappings) de l'import pourrait être une idée. + +### Seulement "Annuler et supprimer" ou "Enregistrer et quitter" + +- Manque "Annuler et quitter" (reviens à la liste sans sauver les changements, et sans supprimer l'import) +- Manque "Annuler les modifications" (reste sur la page, en rechageant les paramètres par défault) +- Annuler et supprimer: il manque une modal d'avertissement / confirmation: (attention, ça va supprimer l'import et les données importées). En l'état, ça se supprime trop immédiatement. + +### Permissions + +Si on le droit de créer un import, mais pas de la supprimer, on a quand même le bouton de suppression. +Combinaison des permissions gérée un peu au cas par cas. + +### Stepper + +- MAE un peu alembiquée. complexe à manipuler, source d'erreur d'état. + +### Step 0 (destinations) + +- le menu déroulant pourrait être remplacé par des boutons sélectionables en exclusion mutuelle + --> Sélection en 1 click plutôt que deux + --> plus joli visuellement (avec logo de la destination, etc.) + +### Step 0 (destinations) and Step 1 (upload) + +- ça donne envie de fusionner ces deux étapes, avec une dépendance entre le choix de la destination et la liste des JDD. + +## Import - Step 1 - upload du fichier + +### Champs JDD + +Si cette étape n'est pas validée, l'import n'est pas créé --> pas de sauvegarde des champs. +Ca ne saute pas aux yeux comme fonctionnement. + +### **vide** vs **invalide** + +Les deux cas "fichier vide" et "fichier invalide" ne se comportent pas de la même façon: + +- fichier vide: toast d'erreur + "Suivant" activé +- fichier invalide: pas de toast d'erreur + "Suivant" désactivé + +> A confirmer: les chemins apparents de gestions d'erreur backend ne reflètent pas la gestion réelle de l'erreur. + +### Autre type de fichier + +Il n'y a pas de check sur l'extension du fichier + +Le seul control est réalisé par la valeur de l'attribut **accept** de la balise **input**. +Pas de check à l'envoi, pas de check à réception. On peut envoyer un PDF, ça passe. + +## Import - step 2 - configuration du fichier + +- Pas de test de cette étape. + +### Sauvegarde des champs + +L'import est créé. Quand on revient à la liste, on le retrouve bien. +Cependant, les champs saisis ne sont jamais saisis. + +## Import - step 3 - mapping des champs + +### Gestion des models utilisés + +Mapping associé à un import en y copiant le contenu. Permet de s'affranchir de la complexité de l'historisation d'un mapping. Mais, impossible de remonter le nom du mapping utilisé. Ca devrait être largement améliorable. Avec du versioning des mapping par exemple. +En tout cas, l'info disparait dès qu'on quitte la page. + +Au minimum du minimum: ajout de metadonnées au mapping "nom du mapping, date et heure" + +### Etat du code "work in progress" + +Tentative de fusion visible entre mapping des champs et mapping des nomenclatures (front et back). +Non terminée. Résulte en beaucoup de duplication entre ces pages, et un manque de lisibilité. + +## Import - step 4 - mapping des nomenclatures + +Voir step 3 mapping des nomenclatures + +- état initial: le modèle "Nomenclatures SINP (labels)" semble être chargé par défault, mais non indiqué dans le menu. + Pas clair. Et en plusça propose de sauver le mapping, alors qu'il est chargé par défault. + +## Import - rapport + +- Manque de nuances "En cours" --> en création, en cours, etc. +- Si en cours de paramétrisation--> envie d'avoir un chemin de navigation vers l'édition de l'import diff --git a/frontend/cypress/e2e/import/constants/common.js b/frontend/cypress/e2e/import/constants/common.js new file mode 100644 index 0000000000..7bacfa87c1 --- /dev/null +++ b/frontend/cypress/e2e/import/constants/common.js @@ -0,0 +1,8 @@ +export const VIEWPORTS = [ + { + width: 1920, + height: 1080, + }, +]; + +export const TIMEOUT_WAIT = 2000; diff --git a/frontend/cypress/e2e/import/constants/fieldsContent.js b/frontend/cypress/e2e/import/constants/fieldsContent.js new file mode 100644 index 0000000000..f37b212512 --- /dev/null +++ b/frontend/cypress/e2e/import/constants/fieldsContent.js @@ -0,0 +1,36 @@ +export const FIELDS_CONTENT_STEP_UPLOAD = { + datasetField: { + defaultValue: 'JDD-TEST-IMPORT-ADMIN', + newValue: 'JDD-TEST-IMPORT-2', + selector: 'ng-select', + }, + fileUploadField: { + defaultValue: 'import/synthese/valid_file_test_link_list_import_synthese.csv', + newValue: 'import/synthese/valid_file_import_synthese_test_changed.csv', + parentSelector: '[data-qa="import-new-upload-file"]', + selector: '[data-qa="import-new-upload-file-label"]', + }, +}; + +export const FIELDS_CONTENT_STEP_FILE_DECODE = { + encodeField: { + defaultValue: 'utf-8 (auto-détecté)', + newValue: 'iso-8859-15', + selector: '[data-qa="import-new-decode-encode"]', + }, + formatField: { + defaultValue: 'csv (auto-détecté)', + newValue: 'geojson', + selector: '[data-qa="import-new-decode-format"]', + }, + delimiterField: { + defaultValue: '; (auto-détecté)', + newValue: ',', + selector: '[data-qa="import-new-decode-delimiter"]', + }, + sridField: { + defaultValue: 'WGS84', + newValue: 'Lambert93', + selector: '[data-qa="import-new-decode-srid"]', + }, +}; diff --git a/frontend/cypress/e2e/import/constants/files.js b/frontend/cypress/e2e/import/constants/files.js new file mode 100644 index 0000000000..4f0b610418 --- /dev/null +++ b/frontend/cypress/e2e/import/constants/files.js @@ -0,0 +1,31 @@ +export const FILES = { + synthese: { + valid: { + fixture: 'import/synthese/valid_file_test_link_list_import_synthese.csv', + toast: '', + formErrorElement: '', + }, + bad: { + fixture: 'import/synthese/bad.csv', + toast: '', + formErrorElement: '[data-qa="import-new-upload-error-empty"]', + }, + empty: { + fixture: 'import/synthese/empty.csv', + toast: 'File must start with columns', + formErrorElement: '[data-qa="import-new-upload-error-firstColumn"]', + }, + bad_extension: { + fixture: 'import/synthese/bad_extension.pdf', + toast: '', + formErrorElement: '', + }, + }, + occhab: { + valid: { + fixture: 'import/occhab/valid_file.csv', + toast: '', + formErrorElement: '', + }, + }, +}; diff --git a/frontend/cypress/e2e/import/constants/filters.js b/frontend/cypress/e2e/import/constants/filters.js new file mode 100644 index 0000000000..a3a6f48c41 --- /dev/null +++ b/frontend/cypress/e2e/import/constants/filters.js @@ -0,0 +1,17 @@ +export const FILTERS_TABLE = [ + { + columnName: 'Jeu de données', + searchTerm: ['JDD-TEST', 'JDD-TEST-IMPORT-ADMIN', 'JDD-INVALID'], + expectedRowsCount: [4, 1, 0], + }, + { + columnName: 'Fichier', + searchTerm: ['valid_file_test_import', 'invalid_file.csv'], + expectedRowsCount: [2, 0], + }, + { + columnName: 'Auteur', + searchTerm: ['Administrateur-test-import', 'Agent-test-import'], + expectedRowsCount: [4, 1], + }, +]; diff --git a/frontend/cypress/e2e/import/constants/mappings.js b/frontend/cypress/e2e/import/constants/mappings.js new file mode 100644 index 0000000000..b3ebdbe9a4 --- /dev/null +++ b/frontend/cypress/e2e/import/constants/mappings.js @@ -0,0 +1,6 @@ +export const DEFAULT_FIELDMAPPINGS = ['Synthese GeoNature', 'Format DEE (champs 10 char)']; + +export const DEFAULT_CONTENTMAPPINGS = [ + 'Nomenclatures SINP (labels)', + 'Nomenclatures SINP (codes)', +]; diff --git a/frontend/cypress/e2e/import/constants/selectors.js b/frontend/cypress/e2e/import/constants/selectors.js new file mode 100644 index 0000000000..2993fb258a --- /dev/null +++ b/frontend/cypress/e2e/import/constants/selectors.js @@ -0,0 +1,159 @@ +export const STEP_NAMES = ['upload', 'decode', 'fieldmapping', 'contentmapping', 'import']; + +export const SELECTORS_NAVIGATION = { + step: { + upload: { + back_btn_selector: '', + next_btn_selector: '[data-qa="import-new-upload-validate"]', + }, + decode: { + back_btn_selector: '[data-qa="import-file-decode-back-btn"]', + step_btn_selector: '[data-qa="import-file-decode-step-btn"]', + next_btn_selector: '[data-qa="import-new-decode-validate"]', + }, + fieldmapping: { + back_btn_selector: '[data-qa="import-fieldmapping-model-back-btn"]', + step_btn_selector: '[data-qa="import-fieldmapping-step-btn"]', + next_btn_selector: '[data-qa="import-new-fieldmapping-model-validate"]', + }, + contentmapping: { + back_btn_selector: '[data-qa="import-contentmapping-back-btn"]', + step_btn_selector: '[data-qa="import-contentmapping-step-btn"]', + next_btn_selector: '[data-qa="import-new-contentmapping-model-validate"]', + }, + import: { + back_btn_selector: '[data-qa="import-data-back-btn"]', + step_btn_selector: '[data-qa="import-data-step-btn"]', + next_btn_selector: '[data-qa="import-new-verification-start"]', + }, + }, + general: { + save_and_quit_btn_selector: '[data-qa="import-new-footer-save"]', + cancel_and_delete_import_btn_selector: '[data-qa="import-new-footer-delete"]', + 'delete-import-modal-btn-selector': '[data-qa="modal-delete-validate"]', + }, +}; + +export const getSelectorsForStep = (stepName) => { + const selectors = SELECTORS_NAVIGATION.step[stepName]; + if (!selectors) { + throw new Error(`No selectors found for step: ${stepName}`); + } + return selectors; +}; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +export const SELECTOR_IMPORT_MODAL_DELETE = '[data-qa=import-modal-delete]'; +export const SELECTOR_IMPORT_MODAL_DELETE_VALIDATE = '[data-qa=modal-delete-validate]'; +export const SELECTOR_IMPORT_MODAL_DESTINATION_START = '[data-qa=import-modal-destination-start]'; +export const SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN = '[data-qa=import-fieldmapping-theme-date_min]'; +export const SELECTOR_IMPORT_FIELDMAPPING_OBSERVERS = + '[data-qa=import-fieldmapping-theme-observers]'; +export const SELECTOR_IMPORT_FIELDMAPPING_NOM_CITE = '[data-qa=import-fieldmapping-theme-nom_cite]'; +export const SELECTOR_IMPORT_FIELDMAPPING_WKT = '[data-qa=import-fieldmapping-theme-WKT]'; +export const SELECTOR_IMPORT_FIELDMAPPING_CD_NOM = '[data-qa=import-fieldmapping-theme-cd_nom]'; +export const SELECTOR_IMPORT_FIELDMAPPING_VALIDATE = + '[data-qa=import-new-fieldmapping-model-validate]'; +export const SELECTOR_IMPORT_FIELDMAPPING_BUTTON_DELETE = + '[data-qa=import-fieldmapping-selection-button-delete]'; +export const SELECTOR_IMPORT_FIELDMAPPING_BUTTON_DELETE_OK = + '[data-qa=import-fieldmapping-selection-modal-delete-ok]'; +export const SELECTOR_IMPORT_FIELDMAPPING_SELECTION = + '[data-qa=import-fieldmapping-selection-select]'; +export const SELECTOR_IMPORT_FIELDMAPPING_SELECTION_RENAME = + '[data-qa=import-fieldmapping-selection-button-rename]'; +export const SELECTOR_IMPORT_FIELDMAPPING_SELECTION_RENAME_OK = + '[data-qa=import-fieldmapping-selection-rename-ok]'; +export const SELECTOR_IMPORT_FIELDMAPPING_SELECTION_RENAME_TEXT = + '[data-qa=import-fieldmapping-selection-rename-text]'; +export const SELECTOR_IMPORT_FIELDMAPPING_MODAL = '[data-qa=import-fieldmapping-saving-modal]'; +export const SELECTOR_IMPORT_FIELDMAPPING_MODAL_CLOSE = + '[data-qa=import-fieldmapping-saving-modal-close]'; +export const SELECTOR_IMPORT_FIELDMAPPING_MODAL_OK = + '[data-qa=import-fieldmapping-saving-modal-ok]'; +export const SELECTOR_IMPORT_FIELDMAPPING_MODAL_NEW_OK = + '[data-qa=import-fieldmapping-saving-modal-new-ok]'; +export const SELECTOR_IMPORT_FIELDMAPPING_MODAL_NAME = + '[data-qa=import-fieldmapping-saving-modal-mapping-name]'; +export const SELECTOR_IMPORT_FOOTER_DELETE = '[data-qa=import-new-footer-delete]'; +export const SELECTOR_IMPORT_FOOTER_SAVE = '[data-qa=import-new-footer-save]'; +export const SELECTOR_IMPORT_LIST = '[data-qa="import-list"]'; +export const SELECTOR_IMPORT_LIST_TABLE = '[data-qa=import-list-table]'; +export const SELECTOR_IMPORT_LIST_TOOLBAR = '[data-qa=import-list-toolbar]'; +export const SELECTOR_IMPORT_LIST_TOOLBAR_DESTINATIONS = + '[data-qa=import-list-toolbar-destinations]'; +export const SELECTOR_IMPORT_LIST_TOOLBAR_SEARCH = '[data-qa=import-list-toolbar-search]'; +export const SELECTOR_DESTINATIONS = '[data-qa=destinations]'; +export const SELECTOR_IMPORT = '[data-qa=gn-sidenav-link-IMPORT]'; +export const SELECTOR_IMPORT_UPLOAD_DATASET = '[data-qa=import-new-upload-datasets]'; +export const SELECTOR_IMPORT_UPLOAD_FILE = '[data-qa=import-new-upload-file]'; +export const SELECTOR_IMPORT_UPLOAD_VALIDATE = '[data-qa=import-new-upload-validate]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_STEP_BUTTON = + '[data-qa=import-contentmapping-step-btn]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_BUTTON_DELETE = + '[data-qa=import-contentmapping-selection-button-delete]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_MODAL_DELETE_OK = + '[data-qa=import-contentmapping-selection-modal-delete-ok]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_VALIDATE = + '[data-qa=import-contentmapping-model-validate]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_SELECT = '[data-qa=import-contentmapping-model-select]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_MODAL = '[data-qa=import-contentmapping-saving-modal]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_MODAL_OK = + '[data-qa=import-contentmapping-saving-modal-ok]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_MODAL_NEW_OK = + '[data-qa=import-contentmapping-saving-modal-new-ok]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_MODAL_NAME = + '[data-qa=import-contentmapping-saving-modal-mapping-name]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_MODAL_CLOSE = + '[data-qa=import-contentmapping-saving-modal-close]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_FORM = '[data-qa=import-contentmapping-form]'; + +export const SELECTOR_IMPORT_CONTENTMAPPING_SELECTION_RENAME = + '[data-qa=import-contentmapping-selection-button-rename]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_SELECTION_RENAME_OK = + '[data-qa=import-contentmapping-selection-rename-ok]'; +export const SELECTOR_IMPORT_CONTENTMAPPING_SELECTION_TEXT = + '[data-qa=import-contentmapping-selection-rename-text]'; +export const SELECTOR_IMPORT_NEW_VERIFICATION_START = '[data-qa=import-new-verification-start]'; + +export const SELECTOR_IMPORT_RECAPITULATIF = '[data-qa=import-recapitulatif]'; +export const SELECTOR_IMPORT_RECAPITULATIF_MAP = '[data-qa=import-recapitulatif-map]'; + +export const SELECTOR_IMPORT_REPORT = '[data-qa=import-report]'; +export const SELECTOR_IMPORT_REPORT_DOWNLOAD_PDF = '[data-qa=import-report-download-pdf]'; +export const SELECTOR_IMPORT_REPORT_MAP = '[data-qa=import-report-map]'; +export const SELECTOR_IMPORT_REPORT_CHART = '[data-qa=import-report-chart]'; +export const SELECTOR_IMPORT_REPORT_ERRORS_TITLE = '[data-qa=import-report-errors-title]'; +export const SELECTOR_IMPORT_REPORT_ERRORS_CSV = '[data-qa=import-report-errors-csv]'; + +export const LIST_TABLE_ACTIONS = ['edit', 'report', 'csv', 'delete']; +export function getSelectorImportListTableAction(rowIndex, action) { + return `[data-qa="import-list-table-row-${rowIndex}-actions-${action}"]`; +} + +export function getSelectorImportListTableRowCSV(rowIndex) { + return getSelectorImportListTableAction(rowIndex, 'csv'); +} + +export function getSelectorImportListTableRowDelete(rowIndex) { + return getSelectorImportListTableAction(rowIndex, 'delete'); +} + +export function getSelectorImportListTableRowEdit(rowIndex) { + return getSelectorImportListTableAction(rowIndex, 'edit'); +} + +export function getSelectorImportListTableRowReport(rowIndex) { + return getSelectorImportListTableAction(rowIndex, 'report'); +} + +export function getSelectorImportListTableRowFile(rowIndex) { + return `[data-qa="import-list-table-row-${rowIndex}-fichier"]`; +} + +export function getSelectorImportListTableRowId(rowIndex) { + return `[data-qa="import-list-table-row-${rowIndex}-id-import"]`; +} diff --git a/frontend/cypress/e2e/import/constants/users.js b/frontend/cypress/e2e/import/constants/users.js new file mode 100644 index 0000000000..6f3b9b85e0 --- /dev/null +++ b/frontend/cypress/e2e/import/constants/users.js @@ -0,0 +1,47 @@ +export const USERS = [ + { + id: 0, + login: { + username: 'admin-test-import', + password: 'admin', + }, + destinations: { + Synthèse: { + count: 3, + code: 'synthese', + }, + Occhab: { + count: 1, + code: 'occhab', + }, + }, + dataset: 'JDD-TEST-IMPORT-ADMIN', + datasetId: 9999, + }, + { + id: 1, + login: { + username: 'agent-test-import', + password: 'agent', + }, + destinations: { + Synthèse: { + count: 1, + code: 'synthese', + }, + Occhab: { + count: 0, + code: 'occhab', + }, + }, + dataset: 'JDD-TEST-IMPORT-2', + datasetId: 9998, + }, +]; + +export function availableDestinations(destinations) { + return Object.keys(destinations); +} +export function availableImportsCount(destinations) { + return Object.values(destinations).reduce((partialSum, item) => partialSum + item.count, 0); +} diff --git a/frontend/cypress/e2e/import/list-table-download-file-spec.js b/frontend/cypress/e2e/import/list-table-download-file-spec.js new file mode 100644 index 0000000000..08f2688b12 --- /dev/null +++ b/frontend/cypress/e2e/import/list-table-download-file-spec.js @@ -0,0 +1,79 @@ +import { USERS } from './constants/users'; +import { TIMEOUT_WAIT, VIEWPORTS } from './constants/common'; +import { + getSelectorImportListTableRowFile, + SELECTOR_IMPORT_LIST_TABLE, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +const USER = USERS[0]; +const DESTINATION_CODE = USER.destinations.Synthèse.code; + +const DOWNLOADS_FOLDER = Cypress.config('downloadsFolder'); +const FILENAME = 'valid_file_test_link_list_import_synthese.csv'; +const FILEPATH = `import/${DESTINATION_CODE}/${FILENAME}`; // Path relative to cypress/fixtures +const JDD_ID = 1002; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe('File Upload and POST Request Test', () => { + VIEWPORTS.forEach((viewport) => { + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + context(`user: ${USER.login.username}`, () => { + beforeEach(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(USER.login.username, USER.login.password); + cy.visitImport(); + }); + + it('Uploads a file via POST request', () => { + // Load the file content + cy.fixture(FILEPATH, 'binary').then((fileContent) => { + // Convert the binary file content to a Blob + const blob = Cypress.Blob.binaryStringToBlob(fileContent, 'application/octet-stream'); + + // Create a FormData object and append the file + const formData = new FormData(); + formData.append('file', blob, FILENAME); + + // Make the POST request + cy.request({ + method: 'PUT', + url: `${Cypress.env( + 'apiEndpoint' + )}import/${DESTINATION_CODE}/imports/${JDD_ID}/upload`, + body: formData, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }).then((response) => { + // Assert the response status or body + expect(response.status).to.equal(200); + }); + }); + }); + + it('Should download file in the folder', () => { + cy.getRowIndexByCellValue(SELECTOR_IMPORT_LIST_TABLE, 'Id Import', `${JDD_ID}`).then( + (rowIndex) => { + //Delete file if exist before downdload it + cy.deleteFileIfExist(FILENAME, DOWNLOADS_FOLDER); + cy.get(getSelectorImportListTableRowFile(rowIndex)).click(); + cy.wait(TIMEOUT_WAIT); + // Verify the file has been downloaded + cy.verifyDownload(FILENAME, DOWNLOADS_FOLDER).then(() => { + // Delete the file after verification + cy.deleteFile(FILENAME, DOWNLOADS_FOLDER); + }); + } + ); + }); + }); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/list-table-jdd-modify-delete-spec.js b/frontend/cypress/e2e/import/list-table-jdd-modify-delete-spec.js new file mode 100644 index 0000000000..4c26312171 --- /dev/null +++ b/frontend/cypress/e2e/import/list-table-jdd-modify-delete-spec.js @@ -0,0 +1,133 @@ +import { USERS } from './constants/users'; +import { TIMEOUT_WAIT, VIEWPORTS } from './constants/common'; +import { FILES } from './constants/files'; +import { + getSelectorImportListTableRowDelete, + getSelectorImportListTableRowEdit, + SELECTOR_IMPORT_LIST_TABLE, + SELECTOR_IMPORT_MODAL_DELETE, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +const COLUMN_NAME = 'Jeu de données'; +const JDD_LIST = [ + { + jdd_name: 'JDD-TEST-IMPORT-ADMIN', + jdd_is_active: true, + url_on_click_edit: Cypress.env('urlApplication') + 'import/occhab/process/1001', + }, + { + jdd_name: 'JDD-TEST-IMPORT-INACTIF', + jdd_is_active: false, + url_on_click_edit: Cypress.env('urlApplication') + 'import', + }, +]; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe('Tests actions on active/inactive list JDD ', () => { + VIEWPORTS.forEach((viewport) => { + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + const user = USERS[0]; + + context(`user: ${user.login.username}`, () => { + beforeEach(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visitImport(); + cy.get(SELECTOR_IMPORT_LIST_TABLE, { timeout: TIMEOUT_WAIT }).should('be.visible'); + }); + + JDD_LIST.forEach((jdd_item) => { + it(`should verify actions for ${jdd_item.jdd_name} (${ + jdd_item.jdd_is_active ? 'active' : 'inactive' + })`, () => { + cy.getRowIndexByCellValue( + SELECTOR_IMPORT_LIST_TABLE, + COLUMN_NAME, + jdd_item.jdd_name + ).then((rowIndex) => { + verifyEditAction(rowIndex, jdd_item); + verifyDeleteAction(rowIndex, jdd_item); + }); + }); + }); + + it('Should be able to modify a finished import, but still active JDD', () => { + cy.startImport(); + cy.pickDestination(); + cy.pickDataset(user.dataset); + cy.loadImportFile(FILES.synthese.valid.fixture); + cy.configureImportFile(); + cy.configureImportFieldMapping(); + cy.configureImportContentMapping(); + cy.verifyImport(); + cy.executeImport(); + cy.backToImportList(); + + cy.get(getSelectorImportListTableRowEdit(0)) + .should('exist') + .should('be.visible') + .should('not.be.disabled'); + + cy.request('PATCH', `${Cypress.env('apiEndpoint')}meta/dataset/${user.datasetId}`, { + active: false, + }); + + cy.reload(); + cy.get(getSelectorImportListTableRowEdit(0)) + .should('exist') + .should('be.visible') + .should('be.disabled'); + + cy.request('PATCH', `${Cypress.env('apiEndpoint')}meta/dataset/${user.datasetId}`, { + active: true, + }); + + cy.reload(); + cy.get(getSelectorImportListTableRowEdit(0)) + .should('exist') + .should('be.visible') + .should('not.be.disabled'); + + cy.removeFirstImportInList(); + }); + }); + }); + }); +}); + +function verifyEditAction(rowIndex, jdd_item) { + const actionStatus = jdd_item.jdd_is_active ? 'not.be.disabled' : 'be.disabled'; + cy.get(getSelectorImportListTableRowEdit(rowIndex)) + .should('exist') + .should('be.visible') + .should(actionStatus) + .then(($btn) => { + if (!$btn.prop('disabled')) { + cy.wrap($btn).click(); + cy.url().should('contain', jdd_item.url_on_click_edit); + cy.visitImport(); // Reset state for the next action + cy.get(SELECTOR_IMPORT_LIST_TABLE, { timeout: TIMEOUT_WAIT }).should('be.visible'); + } + }); +} + +function verifyDeleteAction(rowIndex, jdd_item) { + const actionStatus = jdd_item.jdd_is_active ? 'not.be.disabled' : 'be.disabled'; + cy.get(getSelectorImportListTableRowDelete(rowIndex)) + .should('exist') + .should('be.visible') + .should(actionStatus) + .then(($btn) => { + if (!$btn.prop('disabled')) { + cy.wrap($btn).click(); + cy.get(SELECTOR_IMPORT_MODAL_DELETE).should('be.visible'); + } + }); +} diff --git a/frontend/cypress/e2e/import/list-table-refer-to-jdd-spec.js b/frontend/cypress/e2e/import/list-table-refer-to-jdd-spec.js new file mode 100644 index 0000000000..f087441105 --- /dev/null +++ b/frontend/cypress/e2e/import/list-table-refer-to-jdd-spec.js @@ -0,0 +1,40 @@ +import { USERS } from './constants/users'; +import { TIMEOUT_WAIT, VIEWPORTS } from './constants/common'; +import { SELECTOR_IMPORT_LIST_TABLE } from './constants/selectors'; + +const COLUMN_NAME = 'Jeu de données'; +const JDD_LIST = ['JDD-TEST-IMPORT-ADMIN', 'JDD-TEST-IMPORT-2', 'JDD-TEST-IMPORT-3']; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe('Test List import - Refer to JDD page', () => { + VIEWPORTS.forEach((viewport) => { + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + const user = USERS[0]; + context(`user: ${user.login.username}`, () => { + beforeEach(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visitImport(); + }); + + JDD_LIST.forEach((cellValue) => + it('Should be redirected to page Metada dataset with dataset name: ' + cellValue, () => { + cy.getCellValueByColumnName(SELECTOR_IMPORT_LIST_TABLE, COLUMN_NAME, cellValue).then( + () => { + cy.get('@targetLink').click(); + cy.wait(TIMEOUT_WAIT); + } + ); + cy.location().then((loc) => { + cy.log('Current URL: ' + loc.href); + expect(loc.href).to.include('metadata/dataset_detail/'); + }); + }) + ); + }); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/list-table-sort-column-spec.js b/frontend/cypress/e2e/import/list-table-sort-column-spec.js new file mode 100644 index 0000000000..28ef856ce8 --- /dev/null +++ b/frontend/cypress/e2e/import/list-table-sort-column-spec.js @@ -0,0 +1,115 @@ +import { USERS } from './constants/users'; +import { VIEWPORTS } from './constants/common'; +import { SELECTOR_IMPORT_LIST_TABLE } from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +const COLUMNS = [ + { columnName: 'Id Import', sortable: true }, + { columnName: 'Fichier', sortable: true }, + { columnName: 'Auteur', sortable: false }, + { columnName: 'Debut import', sortable: true }, + { columnName: 'Destination', sortable: true }, + { columnName: 'Fin import', sortable: true }, +]; + +function getColumnIndexByName(columnName) { + return cy.get('datatable-header-cell').then(($headerCells) => { + const index = $headerCells + .toArray() + .findIndex((cell) => Cypress.$(cell).text().trim() === columnName); + if (index >= 0) { + return index; + } else { + throw new Error(`Column with name ${columnName} not found`); + } + }); +} + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe('Tests list import columns and rows content', () => { + VIEWPORTS.forEach((viewport) => { + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + // TODO: rechercher le user par son id + const user = USERS[0]; + context(`user: ${user.login.username}`, () => { + beforeEach(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visitImport(); + cy.getGlobalConfig(); + }); + + it('should verify column names in header', () => { + cy.get('@globalColumnsConfig').then((columns) => { + cy.get(`${SELECTOR_IMPORT_LIST_TABLE} datatable-header-cell`).should(($cells) => { + columns.forEach((column) => { + expect($cells.toArray().some((cell) => cell.innerText.trim() === column.name)).to.be + .true; + }); + }); + }); + }); + + COLUMNS.forEach(({ columnName, sortable }) => { + if (sortable) { + if (columnName == 'Id Import') { + it.skip( + `should sort the table by the ${columnName} column in ascending/descending order` + ); + } else { + it(`should sort the table by the ${columnName} column in ascending/descending order`, () => { + getColumnIndexByName(columnName).then((columnIndex) => { + // Ascending order + cy.get( + `[title="${columnName}"] > .datatable-header-cell-template-wrap > .datatable-icon-sort-unset` + ).click(); + cy.get(SELECTOR_IMPORT_LIST_TABLE).within(() => { + cy.get('datatable-body-row').then(($rows) => { + const texts = [...$rows].map((row) => { + const cell = row.querySelectorAll('datatable-body-cell')[columnIndex]; + return cell ? cell.innerText.trim() : ''; + }); + const sortedTexts = [...texts].sort(); + expect(texts).to.deep.equal(sortedTexts); + }); + }); + + // Descending order + cy.get( + `[title="${columnName}"] > .datatable-header-cell-template-wrap > .sort-btn` + ).click(); + cy.get(SELECTOR_IMPORT_LIST_TABLE).within(() => { + cy.get('datatable-body-row').then(($rows) => { + const texts = [...$rows].map((row) => { + const cell = row.querySelectorAll('datatable-body-cell')[columnIndex]; + return cell ? cell.innerText.trim() : ''; + }); + + const sortedTexts = [...texts].sort().reverse(); + expect(texts).to.deep.equal(sortedTexts); + }); + }); + }); + }); + } + } else if (columnName === columnName && !sortable) { + // Test for non-sortable columns + it(`should not sort the table by the ${columnName} column as it is not sortable`, () => { + getColumnIndexByName(columnName).then((columnIndex) => { + cy.get( + `[title="${columnName}"] > .datatable-header-cell-template-wrap > .datatable-icon-sort-unset` + ).should('not.exist'); + }); + }); + } + }); + }); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/list-toolbar-destinations-spec.js b/frontend/cypress/e2e/import/list-toolbar-destinations-spec.js new file mode 100644 index 0000000000..78e31b134c --- /dev/null +++ b/frontend/cypress/e2e/import/list-toolbar-destinations-spec.js @@ -0,0 +1,109 @@ +import { USERS, availableDestinations, availableImportsCount } from './constants/users'; +import { VIEWPORTS } from './constants/common'; +import { + SELECTOR_DESTINATIONS, + SELECTOR_IMPORT_LIST_TOOLBAR, + SELECTOR_IMPORT_LIST_TOOLBAR_DESTINATIONS, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +function generateRequestForDestination(destination) { + if (!destination) { + return `${Cypress.env('apiEndpoint')}/import/imports/*`; + } + return `${Cypress.env('apiEndpoint')}/import/${destination}/imports/*`; +} + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe('Import List - Toolbar - Destinations', () => { + VIEWPORTS.forEach((viewport) => { + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + USERS.forEach((user) => { + context(`user: ${user.login.username}`, () => { + beforeEach(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visitImport(); + }); + it('Should have pnx-destinations', () => { + cy.get(SELECTOR_IMPORT_LIST_TOOLBAR_DESTINATIONS).should('exist'); + }); + + it('Should contains exactly all the available modules', () => { + const destinations = Object.keys(user.destinations); + cy.get(SELECTOR_IMPORT_LIST_TOOLBAR_DESTINATIONS) + .should('exist') + .click() + .get('ng-dropdown-panel') + .get('.ng-option') + .then((options) => { + expect(options.length === destinations.length); + for (let i = 0; i < options.length; i++) { + const option = options[i].innerText; + expect(destinations.includes(option)); + } + }); + }); + // TODO: this test is not valid on the CI. But actually, it is already covered by the next test: filtering the list + it.skip('Should trigger an api call with the destination', () => { + const destinations = availableDestinations(user.destinations); + // Select every destination, one after the other- check that request is sent + for (const destinationKey of destinations) { + const code = user.destinations[destinationKey].code; + const request = generateRequestForDestination(code); + cy.intercept('GET', request).as(`getImports${code}`); + cy.get(SELECTOR_IMPORT_LIST_TOOLBAR).within(() => { + cy.get(SELECTOR_DESTINATIONS, { force: true }) + .should('exist') + .click() + .get('ng-dropdown-panel') + .get('.ng-option') + .contains(destinationKey) + .then((destination) => { + cy.wrap(destination).should('exist').click(); + cy.wait(`@getImports${code}`); + }); + }); + } + const request = generateRequestForDestination(); + cy.intercept('GET', request).as('getImports'); + // Clear selection + cy.get(SELECTOR_IMPORT_LIST_TOOLBAR_DESTINATIONS) + .find('.ng-clear-wrapper') + .should('exist') + .click(); + cy.wait(`@getImports`); + }); + + it('Should filter the list with every available destinations', () => { + const destinations = availableDestinations(user.destinations); + // Select every destination, one after the other + for (const destinationKey of destinations) { + cy.get(SELECTOR_IMPORT_LIST_TOOLBAR).within(() => { + cy.get(SELECTOR_DESTINATIONS, { force: true }) + .should('exist') + .click() + .get('ng-dropdown-panel') + .get('.ng-option') + .contains(destinationKey) + .then((destination) => { + cy.wrap(destination).should('exist').click(); + }); + }); + cy.checkImportListSize(user.destinations[destinationKey].count); + } + // Clear selection + cy.get(SELECTOR_IMPORT_LIST_TOOLBAR_DESTINATIONS).find('.ng-clear-wrapper').click(); + cy.checkImportListSize(availableImportsCount(user.destinations)); + }); + }); + }); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/list-toolbar-search-spec.js b/frontend/cypress/e2e/import/list-toolbar-search-spec.js new file mode 100644 index 0000000000..9631a84bd1 --- /dev/null +++ b/frontend/cypress/e2e/import/list-toolbar-search-spec.js @@ -0,0 +1,138 @@ +import { USERS } from './constants/users'; +import { VIEWPORTS } from './constants/common'; +import { FILTERS_TABLE } from './constants/filters'; +import { + SELECTOR_IMPORT_LIST_TABLE, + SELECTOR_IMPORT_LIST_TOOLBAR_SEARCH, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +const FIXTURE_PATH = 'import/synthese/liste_import.json'; +const TIMEOUT_WAIT = 3000; + +function filterMapList(importSeachTerm) { + cy.get(SELECTOR_IMPORT_LIST_TOOLBAR_SEARCH).clear().type(importSeachTerm); +} + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe('Tests list import columns and rows content', () => { + VIEWPORTS.forEach((viewport) => { + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + // TODO: rechercher le user par son id + const user = USERS[0]; + context(`user: ${user.login.username}`, () => { + beforeEach(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visitImport(); + cy.intercept(Cypress.env('apiEndpoint') + 'import/imports/?page=1&search=', { + fixture: 'import/synthese/liste_import.json', + }); + }); + + // NOTES: [TEST][IMPORT] Redondant avec le check du nombre de lignes dans list import via ce que doit voir un utilisateur ? + it('Should display the correct number of rows ', () => { + // TODO: mettre en accord le json de la fixture + cy.fixture(FIXTURE_PATH).then((config) => { + cy.get(SELECTOR_IMPORT_LIST_TABLE) + .find('datatable-body-row') + .its('length') + .should('be.gte', config.count); + }); + }); + it('Should display the correct columns present in default config ', () => { + // Make an HTTP request to get the list of columns displayed for import + cy.request('GET', Cypress.env('apiEndpoint') + 'gn_commons/config').then((response) => { + // Extract the list of columns from the response + const columnsImport = response.body.IMPORT.LIST_COLUMNS_FRONTEND; + const columnNames = columnsImport.map((column) => column.name); + // Assert that each column name exists in at least one header cell + cy.get(`${SELECTOR_IMPORT_LIST_TABLE} datatable-header-cell`).should(($cells) => { + columnNames.forEach((columnName) => { + expect($cells.toArray().some((cell) => cell.innerText.trim() === columnName)).to.be + .true; + }); + }); + }); + }); + }); + }); + }); +}); + +describe('Tests Filter Search List Import', () => { + VIEWPORTS.forEach((viewport) => { + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + const user = USERS[0]; + + context(`user: ${user.login.username}`, () => { + beforeEach(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visitImport(); + cy.get(SELECTOR_IMPORT_LIST_TABLE, { timeout: TIMEOUT_WAIT }) + .should('be.visible') + .then(($table) => { + FILTERS_TABLE.forEach((filter) => { + filter.columnIndex = getColumnIndex($table, filter.columnName); + }); + }); + }); + + FILTERS_TABLE.forEach((filter) => { + filter.searchTerm.forEach((searchTerm, index) => { + const expectedRowsCount = filter.expectedRowsCount[index]; + + it(`Should get ${expectedRowsCount} rows for search term "${searchTerm}" in column "${filter.columnName}"`, () => { + performSearchAndAssertRowCount(filter, searchTerm, expectedRowsCount); + }); + + if (expectedRowsCount > 0) { + it(`Should get a row containing "${searchTerm}" in column "${filter.columnName}"`, () => { + performSearchAndAssertRowContent(filter, searchTerm); + }); + } + }); + }); + }); + }); + }); + + function getColumnIndex($table, columnName) { + const index = $table + .find('datatable-header-cell') + .toArray() + .findIndex((headerCell) => Cypress.$(headerCell).text().trim() === columnName); + expect(index).to.be.gte(0); + return index; + } + + function performSearchAndAssertRowCount(filter, searchTerm, expectedRowsCount) { + filterMapList(searchTerm); + cy.wait(TIMEOUT_WAIT); + cy.get(`${SELECTOR_IMPORT_LIST_TABLE} datatable-body`, { timeout: TIMEOUT_WAIT }).within(() => { + cy.get('datatable-body-row').should('have.length', expectedRowsCount); + }); + } + + function performSearchAndAssertRowContent(filter, searchTerm) { + filterMapList(searchTerm); + cy.wait(TIMEOUT_WAIT); + cy.get(`${SELECTOR_IMPORT_LIST_TABLE} datatable-body-row`, { timeout: TIMEOUT_WAIT }) + .eq(0) + .as('firstRow'); + cy.get('@firstRow') + .find('datatable-body-cell') + .eq(filter.columnIndex) + .invoke('text') + .then((rowValue) => { + cy.wrap(rowValue.trim()).should('contain', searchTerm); + }); + } +}); diff --git a/frontend/cypress/e2e/import/navigation-check-back-each-steps-spec.js b/frontend/cypress/e2e/import/navigation-check-back-each-steps-spec.js new file mode 100644 index 0000000000..880fcfd40f --- /dev/null +++ b/frontend/cypress/e2e/import/navigation-check-back-each-steps-spec.js @@ -0,0 +1,280 @@ +import { + STEP_NAMES, + getSelectorsForStep, + SELECTORS_NAVIGATION, + SELECTOR_IMPORT_LIST_TABLE, + getSelectorImportListTableRowEdit, + getSelectorImportListTableRowDelete, + SELECTOR_IMPORT_MODAL_DELETE_VALIDATE, +} from './constants/selectors'; +import { USERS } from './constants/users'; +import { TIMEOUT_WAIT, VIEWPORTS } from './constants/common'; + +import { + FIELDS_CONTENT_STEP_UPLOAD, + FIELDS_CONTENT_STEP_FILE_DECODE, +} from './constants/fieldsContent'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +function getURLStepImport(destination_label, id_import) { + const baseUrl = Cypress.env('urlApplication'); + const urlStepsImport = { + step_1_upload: { + url: `${baseUrl}import/${destination_label}/process/${id_import}/upload`, + }, + step_2_decode_file: { + url: `${baseUrl}import/${destination_label}/process/${id_import}/decode`, + }, + step_3_fieldmapping: { + url: `${baseUrl}import/${destination_label}/process/${id_import}/fieldmapping`, + }, + step_4_contentmapping: { + url: `${baseUrl}import/${destination_label}/process/${id_import}/contentmapping`, + }, + step_5_import_data: { + url: `${baseUrl}import/${destination_label}/process/${id_import}/import`, + }, + }; + + return urlStepsImport; +} + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe('Import Process Navigation', () => { + VIEWPORTS.forEach((viewport) => { + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + const user = USERS[0]; + const DESTINATION = 'Synthèse'; + const DESTINATION_URL = 'synthese'; + const COLUMN_NAME = 'Id Import'; + const SELECTOR_NAVIGATION_GENERAL = SELECTORS_NAVIGATION.general; + const SELECTOR_NAVIGATION_STEP_UPLOAD = getSelectorsForStep(STEP_NAMES[0]); + const SELECTOR_NAVIGATION_STEP_DECODE_FILE = getSelectorsForStep(STEP_NAMES[1]); + const SELECTOR_NAVIGATION_STEP_FIELDMAPPING = getSelectorsForStep(STEP_NAMES[2]); + let importID; // Declare importID variable + // const selectorNavigationStepContentMapping = getSelectorsForStep(STEP_NAMES[3]); + // const selectorNavigationStepImportData = getSelectorsForStep(STEP_NAMES[4]); + before(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visitImport(); + cy.wait(TIMEOUT_WAIT); + cy.startImport(); + cy.pickDestination(DESTINATION); + + // STEP 1 - UPLOAD + cy.pickDataset(FIELDS_CONTENT_STEP_UPLOAD.datasetField.defaultValue); + cy.loadImportFile(FIELDS_CONTENT_STEP_UPLOAD.fileUploadField.defaultValue); + cy.wait(TIMEOUT_WAIT); + cy.url().then((url) => { + // Extract the ID using string manipulation + const parts = url.split('/'); + const id = parts[parts.length - 2]; // Get the penultimate element + // Log the extracted ID + importID = id; + }); + cy.visitImport(); + }); + + it(`should navigate correctly from step ${STEP_NAMES[0]} to step ${STEP_NAMES[1]} and save fields content`, function () { + // Wait for the alias to be available + cy.wrap(importID) + .as('newImportID') + .then((importID) => { + // Define selectors and URLs using the newImportID + const urlStepsImport = getURLStepImport(DESTINATION_URL, importID); + cy.getRowIndexByCellValue(SELECTOR_IMPORT_LIST_TABLE, COLUMN_NAME, importID); + // Navigation from list to upload by using Edit action + cy.get('@rowIndex').then((rowIndex) => { + cy.get(getSelectorImportListTableRowEdit(rowIndex)).should('be.enabled').click(); + }); + cy.wait(TIMEOUT_WAIT); + // Should go on last step edited --> decode file + cy.url().should('eq', urlStepsImport.step_2_decode_file.url); + + ////////////////////////////////////////////////////////////////////////// + // Should contains the decode file form with default value + cy.get(SELECTOR_NAVIGATION_STEP_DECODE_FILE.back_btn_selector) + .should('be.visible') + .click(); + // Verify the selected value in the ng-select input + cy.get(FIELDS_CONTENT_STEP_UPLOAD.datasetField.selector).within(() => { + cy.get('.ng-value').should( + 'contain.text', + FIELDS_CONTENT_STEP_UPLOAD.datasetField.defaultValue + ); + }); + cy.get(FIELDS_CONTENT_STEP_UPLOAD.fileUploadField.selector).then(($el) => { + const expectedValue = $el + .text() + .trim() + .replace(/\u00A0/g, ' '); + const defaultValue = FIELDS_CONTENT_STEP_UPLOAD.fileUploadField.defaultValue.trim(); + expect(defaultValue).to.include(expectedValue); + }); + // Change values in upload step + cy.pickDataset(FIELDS_CONTENT_STEP_UPLOAD.datasetField.newValue); + cy.loadImportFile(FIELDS_CONTENT_STEP_UPLOAD.fileUploadField.newValue); + cy.get(SELECTOR_NAVIGATION_STEP_DECODE_FILE.back_btn_selector) + .should('be.visible') + .click(); + + /////////// NOTES: not working (jdd is not saved if changed) + // cy.get(FIELDS_CONTENT_STEP_UPLOAD.datasetField.selector).within(() => { + // cy.get('.ng-value').should('contain.text', FIELDS_CONTENT_STEP_UPLOAD.datasetField.newValue); + // }); + + cy.get(FIELDS_CONTENT_STEP_UPLOAD.fileUploadField.selector).then(($el) => { + const expectedValue = $el + .text() + .trim() + .replace(/\u00A0/g, ' '); + const newValue = FIELDS_CONTENT_STEP_UPLOAD.fileUploadField.newValue.trim(); + expect(newValue).to.include(expectedValue); + }); + + // Finalize step 2 (decode file and configuration) + cy.get(SELECTOR_NAVIGATION_STEP_UPLOAD.next_btn_selector).should('be.visible').click(); + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.sridField.selector).select( + FIELDS_CONTENT_STEP_FILE_DECODE.sridField.defaultValue + ); + cy.wait(TIMEOUT_WAIT); + cy.get(SELECTOR_NAVIGATION_GENERAL.save_and_quit_btn_selector) + .should('be.visible') + .click(); + cy.visitImport(); + + // cy.get(selectorNavigationGeneral.save_and_quit_btn_selector).should('be.visible').click(); + // cy.url().should('eq', Cypress.env('urlApplication') + "import"); + // cy.get('@rowIndex').then((rowIndex) => { + // cy.log('Row index:', rowIndex); + // expect(rowIndex).to.not.be.null; + // }); + }); + }); + + it(`should navigate correctly from step ${STEP_NAMES[1]} to step ${STEP_NAMES[2]} and save fields content`, function () { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visitImport(); + cy.wrap(importID) + .as('newImportID') + .then((importID) => { + const urlStepsImport = getURLStepImport(DESTINATION_URL, importID); + cy.getRowIndexByCellValue(SELECTOR_IMPORT_LIST_TABLE, COLUMN_NAME, importID); + // Navigation from list to upload by using Edit action + cy.get('@rowIndex').then((rowIndex) => { + cy.get(getSelectorImportListTableRowEdit(rowIndex)).should('be.enabled').click(); + }); + cy.wait(TIMEOUT_WAIT); + // Should go on last step edited --> decode file + cy.url().should('eq', urlStepsImport.step_2_decode_file.url); + + ////////////////////////////////////////////////////////////////////////// + // Should contains the decode file form with default value + // Verify the selected value in the ng-select input + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.delimiterField.selector).then(($select) => { + const selectedValue = $select.find(':selected').text().trim(); + expect(selectedValue).to.equal( + FIELDS_CONTENT_STEP_FILE_DECODE.delimiterField.defaultValue + ); + }); + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.encodeField.selector).then(($select) => { + const selectedValue = $select.find(':selected').text().trim(); + expect(selectedValue).to.equal( + FIELDS_CONTENT_STEP_FILE_DECODE.encodeField.defaultValue + ); + }); + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.formatField.selector).then(($select) => { + const selectedValue = $select.find(':selected').text().trim(); + expect(selectedValue).to.equal( + FIELDS_CONTENT_STEP_FILE_DECODE.formatField.defaultValue + ); + }); + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.sridField.selector).then(($select) => { + const selectedValue = $select.find(':selected').text().trim(); + expect(selectedValue).to.equal( + FIELDS_CONTENT_STEP_FILE_DECODE.sridField.defaultValue + ); + }); + // Change values in decode step + cy.get(SELECTOR_NAVIGATION_STEP_DECODE_FILE.next_btn_selector) + .should('be.visible') + .click(); + cy.wait(TIMEOUT_WAIT); + cy.get(SELECTOR_NAVIGATION_STEP_FIELDMAPPING.back_btn_selector) + .scrollIntoView() + .should('be.visible') + .click(); + cy.wait(TIMEOUT_WAIT); + + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.sridField.selector).select( + FIELDS_CONTENT_STEP_FILE_DECODE.sridField.newValue + ); + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.formatField.selector).select( + FIELDS_CONTENT_STEP_FILE_DECODE.formatField.newValue + ); + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.encodeField.selector).select( + FIELDS_CONTENT_STEP_FILE_DECODE.encodeField.newValue + ); + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.delimiterField.selector).select( + FIELDS_CONTENT_STEP_FILE_DECODE.delimiterField.newValue + ); + + cy.get(SELECTOR_NAVIGATION_STEP_DECODE_FILE.next_btn_selector) + .should('be.visible') + .click(); + cy.wait(TIMEOUT_WAIT); + cy.get(SELECTOR_NAVIGATION_STEP_FIELDMAPPING.back_btn_selector) + .scrollIntoView() + .should('be.visible') + .click(); + ////////////////////////////////////////////////////////////////////////// + // Should contains the decode file form with new value + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.delimiterField.selector).then(($select) => { + const selectedValue = $select.find(':selected').text().trim(); + expect(selectedValue).to.equal( + FIELDS_CONTENT_STEP_FILE_DECODE.delimiterField.newValue + ); + }); + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.encodeField.selector).then(($select) => { + const selectedValue = $select.find(':selected').text().trim(); + expect(selectedValue).to.equal(FIELDS_CONTENT_STEP_FILE_DECODE.encodeField.newValue); + }); + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.formatField.selector).then(($select) => { + const selectedValue = $select.find(':selected').text().trim(); + expect(selectedValue).to.equal(FIELDS_CONTENT_STEP_FILE_DECODE.formatField.newValue); + }); + cy.get(FIELDS_CONTENT_STEP_FILE_DECODE.sridField.selector).then(($select) => { + const selectedValue = $select.find(':selected').text().trim(); + expect(selectedValue).to.equal(FIELDS_CONTENT_STEP_FILE_DECODE.sridField.newValue); + }); + cy.visitImport(); + }); + }); + + after(() => { + cy.wrap(importID) + .as('newImportID') + .then((importID) => { + cy.visitImport(); + cy.getRowIndexByCellValue(SELECTOR_IMPORT_LIST_TABLE, COLUMN_NAME, importID); + cy.get('@rowIndex').then((rowIndex) => { + cy.get(getSelectorImportListTableRowDelete(rowIndex)).should('be.visible').click(); + cy.get(SELECTOR_IMPORT_MODAL_DELETE_VALIDATE).should('exist').click(); + }); + }); + // Delete file by using + // cy.visit(urlStepsImport.step_2_decode_file.url); + // cy.get(selectorNavigationGeneral.cancel_and_delete_import_btn_selector).should('be.visible').click(); + // cy.url().should('eq', Cypress.env('urlApplication') + "import"); + }); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/navigation-list-spec.js b/frontend/cypress/e2e/import/navigation-list-spec.js new file mode 100644 index 0000000000..a6509cd0de --- /dev/null +++ b/frontend/cypress/e2e/import/navigation-list-spec.js @@ -0,0 +1,62 @@ +import { USERS, availableImportsCount } from './constants/users'; +import { VIEWPORTS } from './constants/common'; +import { + getSelectorImportListTableAction, + LIST_TABLE_ACTIONS, + SELECTOR_IMPORT_LIST, + SELECTOR_IMPORT_LIST_TABLE, + SELECTOR_IMPORT_LIST_TOOLBAR_DESTINATIONS, + SELECTOR_IMPORT_LIST_TOOLBAR_SEARCH, + SELECTOR_IMPORT_MODAL_DESTINATION_START, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe('Import List - Toolbar - Destinations', () => { + VIEWPORTS.forEach((viewport) => { + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + USERS.forEach((user) => { + context(`user: ${user.login.username}`, () => { + beforeEach(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visitImport(); + }); + + it('Should have a correct import list setup', () => { + cy.get(SELECTOR_IMPORT_LIST).should('exist').should('be.visible'); + + cy.get(SELECTOR_IMPORT_LIST_TOOLBAR_DESTINATIONS) + .should('exist') + .should('be.visible') + .should('not.be.disabled'); + + cy.get(SELECTOR_IMPORT_LIST_TOOLBAR_SEARCH) + .should('exist') + .should('be.visible') + .should('not.be.disabled'); + + cy.get(SELECTOR_IMPORT_LIST_TABLE).should('exist').should('be.visible'); + + cy.get(SELECTOR_IMPORT_MODAL_DESTINATION_START) + .should('exist') + .should('be.visible') + .should('not.be.disabled'); + + const importCount = availableImportsCount(user.destinations); + + for (let i = 0; i < importCount; i++) { + for (const action of LIST_TABLE_ACTIONS) { + cy.get(getSelectorImportListTableAction(i, action)) + .should('exist') + .should('be.visible'); + } + } + }); + }); + }); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/navigation-mappings-cancel-save-spec.js b/frontend/cypress/e2e/import/navigation-mappings-cancel-save-spec.js new file mode 100644 index 0000000000..003cffe2b4 --- /dev/null +++ b/frontend/cypress/e2e/import/navigation-mappings-cancel-save-spec.js @@ -0,0 +1,222 @@ +import { USERS } from './constants/users'; +import { TIMEOUT_WAIT, VIEWPORTS } from './constants/common'; +import { FILES } from './constants/files'; +import { + getSelectorImportListTableRowEdit, + getSelectorImportListTableRowId, + SELECTOR_IMPORT_CONTENTMAPPING_STEP_BUTTON, + SELECTOR_IMPORT_FIELDMAPPING_CD_NOM, + SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN, + SELECTOR_IMPORT_FIELDMAPPING_NOM_CITE, + SELECTOR_IMPORT_FIELDMAPPING_OBSERVERS, + SELECTOR_IMPORT_FIELDMAPPING_WKT, + SELECTOR_IMPORT_FOOTER_DELETE, + SELECTOR_IMPORT_FOOTER_SAVE, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +function runTheProcessUntilFieldMapping(user) { + cy.visitImport(); + cy.startImport(); + cy.pickDestination(); + cy.pickDataset(user.dataset); + cy.loadImportFile(FILES.synthese.valid.fixture); + cy.configureImportFile(); +} + +function runTheProcessUntilContentMapping(user) { + runTheProcessUntilFieldMapping(user); + cy.configureImportFieldMapping(); + cy.wait(500); +} + +function goToContentMappingPage() { + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_STEP_BUTTON).click(); + cy.wait(500); +} + +function checkImportIsFirstInList(importId) { + cy.get(getSelectorImportListTableRowId(0)).should('have.text', ` ${importId} `); +} + +function checkImportIsNotFirstInList(importId) { + cy.get(getSelectorImportListTableRowId(0)).should('not.have.text', ` ${importId} `); +} + +function clickOnFirstLineEdit() { + cy.get(getSelectorImportListTableRowEdit(0)).click(); + cy.wait(TIMEOUT_WAIT); +} + +function selectFieldMappingField(dataQa, value) { + cy.get(dataQa) + .should('exist') + .click() + .get('ng-dropdown-panel >') + .get('.ng-option') + .contains(value) + .then((v) => { + cy.wrap(v).should('exist').click(); + }); +} + +function selectContentMappingField(dataQa, value) { + cy.get(`[data-qa=import-contentmapping-theme-${dataQa}]`).should('exist').select(value); +} + +function fillTheFieldMappingFormRaw() { + selectFieldMappingField(SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN, 'date_debut'); + selectFieldMappingField(SELECTOR_IMPORT_FIELDMAPPING_OBSERVERS, 'date_debut'); + selectFieldMappingField(SELECTOR_IMPORT_FIELDMAPPING_NOM_CITE, 'date_debut'); + selectFieldMappingField(SELECTOR_IMPORT_FIELDMAPPING_WKT, 'date_debut'); + selectFieldMappingField(SELECTOR_IMPORT_FIELDMAPPING_CD_NOM, 'date_debut'); +} + +// //////////////////////////////////////////////////////////////////////////// +// Create a mapping with dummy values +// //////////////////////////////////////////////////////////////////////////// + +describe('Navigation - cancel and save', () => { + const viewport = VIEWPORTS[0]; + const user = USERS[0]; + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + context(`user: ${user.login.username}`, () => { + beforeEach(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visitImport(); + }); + + it('fieldmapping - cancel and suppress', () => { + runTheProcessUntilFieldMapping(user); + cy.url().then((url) => { + // Extract the ID using string manipulation + const parts = url.split('/'); + const importID = parts[parts.length - 2]; // Get the penultimate element + + fillTheFieldMappingFormRaw(); + cy.get(SELECTOR_IMPORT_FOOTER_DELETE).should('be.enabled').click(); + cy.wait(TIMEOUT_WAIT); + cy.checkCurrentPageIsImport(); + checkImportIsNotFirstInList(importID); + }); + }); + + it('fieldmapping - cancel', () => { + runTheProcessUntilFieldMapping(user); + cy.url().then((url) => { + // Extract the ID using string manipulation + const parts = url.split('/'); + const importID = parts[parts.length - 2]; // Get the penultimate element + + fillTheFieldMappingFormRaw(); + cy.visitImport(); + checkImportIsFirstInList(importID); + clickOnFirstLineEdit(); + cy.url().then((url) => { + const parts = url.split('/'); + const importID_reopened = parts[parts.length - 2]; // Get the penultimate element + expect(importID).to.be.equal(importID_reopened); + // Checks that a ng-select is not restored + cy.get(SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN).find('.ng-placeholder'); + cy.deleteCurrentImport(); + }); + }); + }); + + it('fieldmapping - save', () => { + runTheProcessUntilFieldMapping(user); + cy.url().then((url) => { + // Extract the ID using string manipulation + const parts = url.split('/'); + const importID = parts[parts.length - 2]; // Get the penultimate element + fillTheFieldMappingFormRaw(); + cy.get(SELECTOR_IMPORT_FOOTER_SAVE).should('be.enabled').click(); + checkImportIsFirstInList(importID); + clickOnFirstLineEdit(); + + // Checks that a ng-select is well restored + cy.get(SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN) + .find('.ng-value-label') + .should('exist') + .should('contains.text', 'date_debut'); + + cy.deleteCurrentImport(); + }); + }); + + it('contentmapping - cancel and suppress', () => { + runTheProcessUntilContentMapping(user); + cy.url().then((url) => { + // Extract the ID using string manipulation + const parts = url.split('/'); + const importID = parts[parts.length - 2]; // Get the penultimate element + cy.get(SELECTOR_IMPORT_FOOTER_DELETE).should('be.enabled').click(); + cy.wait(TIMEOUT_WAIT); + cy.checkCurrentPageIsImport(); + checkImportIsNotFirstInList(importID); + }); + }); + + it('contentmapping - cancel', () => { + const FIELD = 'id_nomenclature_behaviour'; + const VALUE = '0 - Inconnu'; + runTheProcessUntilContentMapping(user); + cy.url().then((url) => { + // Extract the ID using string manipulation + const parts = url.split('/'); + const importID = parts[parts.length - 2]; // Get the penultimate element + selectContentMappingField(FIELD, VALUE); + cy.visitImport(); + cy.wait(500); + checkImportIsFirstInList(importID); + clickOnFirstLineEdit(); + goToContentMappingPage(); + + cy.url().then((url) => { + const parts = url.split('/'); + const importID_reopened = parts[parts.length - 2]; // Get the penultimate element + expect(importID).to.be.equal(importID_reopened); + cy.get(`[data-qa=import-contentmapping-theme-${FIELD}] option:selected`).should( + 'not.have.text', + ` ${VALUE} ` + ); + cy.deleteCurrentImport(); + }); + }); + cy.wait(500); + }); + + it('contentmapping - save', () => { + const FIELD = 'id_nomenclature_behaviour'; + const VALUE = '0 - Inconnu'; + + runTheProcessUntilContentMapping(user); + cy.url().then((url) => { + // Extract the ID using string manipulation + const parts = url.split('/'); + const importID = parts[parts.length - 2]; // Get the penultimate element + + selectContentMappingField(FIELD, VALUE); + + cy.get(SELECTOR_IMPORT_FOOTER_SAVE).should('be.enabled').click(); + cy.wait(TIMEOUT_WAIT); + checkImportIsFirstInList(importID); + clickOnFirstLineEdit(); + goToContentMappingPage(); + + // Checks that a ng-select is well restored + cy.get(`[data-qa=import-contentmapping-theme-${FIELD}] option:selected`).should( + 'have.text', + ` ${VALUE} ` + ); + + cy.deleteCurrentImport(); + }); + }); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/navigation-module-access-spec.js b/frontend/cypress/e2e/import/navigation-module-access-spec.js new file mode 100644 index 0000000000..5a6482768c --- /dev/null +++ b/frontend/cypress/e2e/import/navigation-module-access-spec.js @@ -0,0 +1,33 @@ +import { USERS } from './constants/users'; +import { VIEWPORTS } from './constants/common'; +import { SELECTOR_IMPORT } from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe(`Should be able to acces the import module`, () => { + VIEWPORTS.forEach((viewport) => { + context(`viewport: ${viewport.width}x${viewport.height}`, () => { + USERS.forEach((user) => { + context(`user: ${user.login.username}`, () => { + beforeEach(() => { + cy.viewport(viewport.width, viewport.height); + cy.geonatureLogin(user.login.username, user.login.password); + cy.visit('/#'); + }); + + it('Should switch to import page after a click on the button', () => { + cy.get(SELECTOR_IMPORT).should('exist').click(); + cy.checkCurrentPageIsImport(); + }); + + it('Should land directly to the import page', () => { + cy.visitImport(); + cy.checkCurrentPageIsImport(); + }); + }); + }); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/propcess-import-in-synthese-spec.js b/frontend/cypress/e2e/import/propcess-import-in-synthese-spec.js new file mode 100644 index 0000000000..c1e2f5d20b --- /dev/null +++ b/frontend/cypress/e2e/import/propcess-import-in-synthese-spec.js @@ -0,0 +1,36 @@ +import { USERS } from './constants/users'; +import { VIEWPORTS } from './constants/common'; +import { FILES } from './constants/files'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +const USER = USERS[0]; +const VIEWPORT = VIEWPORTS[0]; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe('Import - create a new import', () => { + beforeEach(() => { + cy.viewport(VIEWPORT.width, VIEWPORT.height); + cy.geonatureLogin(USER.login.username, USER.login.password); + cy.visitImport(); + }); + + it('Should be able to import a valid-file in synthese', () => { + cy.startImport(); + cy.pickDestination(); + cy.pickDataset(USER.dataset); + cy.loadImportFile(FILES.synthese.valid.fixture); + cy.configureImportFile(); + cy.configureImportFieldMapping(); + cy.configureImportContentMapping(); + cy.verifyImport(); + cy.executeImport(); + cy.backToImportList(); + cy.removeFirstImportInList(); + }); +}); diff --git a/frontend/cypress/e2e/import/step1-upload-spec.js b/frontend/cypress/e2e/import/step1-upload-spec.js new file mode 100644 index 0000000000..b2ec1e5aac --- /dev/null +++ b/frontend/cypress/e2e/import/step1-upload-spec.js @@ -0,0 +1,92 @@ +import { USERS } from './constants/users'; +import { VIEWPORTS } from './constants/common'; +import { FILES } from './constants/files'; +import { + SELECTOR_IMPORT_UPLOAD_DATASET, + SELECTOR_IMPORT_UPLOAD_FILE, + SELECTOR_IMPORT_UPLOAD_VALIDATE, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +const USER = USERS[1]; +const VIEWPORT = VIEWPORTS[0]; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +describe('Import - Upload step', () => { + context(`viewport: ${VIEWPORT.width}x${VIEWPORT.height}`, () => { + beforeEach(() => { + cy.viewport(VIEWPORT.width, VIEWPORT.height); + cy.geonatureLogin(USER.login.username, USER.login.password); + cy.visitImport(); + cy.startImport(); + cy.pickDestination(); + cy.get(SELECTOR_IMPORT_UPLOAD_VALIDATE).should('exist').should('be.disabled'); + }); + + it('Should be able to select a jdd', () => { + cy.pickDataset(USER.dataset); + cy.get(`${SELECTOR_IMPORT_UPLOAD_DATASET} > ng-select`) + .should('have.class', 'ng-valid') + .find('.ng-value-label') + .should('exist') + .should('contains.text', USER.dataset); + + cy.get(SELECTOR_IMPORT_UPLOAD_DATASET).find('.ng-clear-wrapper').should('exist').click(); + + cy.get(`${SELECTOR_IMPORT_UPLOAD_DATASET} > ng-select`).should('have.class', 'ng-invalid'); + + cy.pickDataset(USER.dataset); + + cy.get(`${SELECTOR_IMPORT_UPLOAD_DATASET} > ng-select`) + .should('have.class', 'ng-valid') + .find('.ng-value-label') + .should('exist') + .should('contains.text', USER.dataset); + }); + + it('Should access jdd only filtered based on permissions ', () => { + cy.get(`${SELECTOR_IMPORT_UPLOAD_DATASET} > ng-select`) + .click() + .get('.ng-option') + .should('have.length', 1) + .should('contain', USER.dataset); + }); + + it('Should throw error if file is empty', () => { + // required to trigger file validation + cy.pickDataset(USER.dataset); + const file = FILES.synthese.empty; + cy.get(file.formErrorElement).should('not.exist'); + cy.loadImportFile(file.fixture); + cy.get(file.formErrorElement).should('be.visible'); + cy.hasToastError(file.toast); + }); + + it('Should throw error if csv is not valid', () => { + // required to trigger file validation + cy.pickDataset(USER.dataset); + const file = FILES.synthese.bad; + cy.get(file.formErrorElement).should('not.exist'); + cy.fixture(file.fixture, null).as('import_file'); + cy.get(SELECTOR_IMPORT_UPLOAD_FILE).selectFile('@import_file'); + cy.contains(file.fixture.split(/(\\|\/)/g).pop()); + cy.get(file.formErrorElement).should('be.visible'); + }); + + // Skipped //////////////////////////////////////////////////////////////// + it.skip('Should throw error if input is not a valid extension', () => { + const file = FILES.synthese.bad_extension; + cy.get(file.formErrorElement).should('not.exist'); + cy.fixture(file.fixture, null).as('import_file'); + cy.get(SELECTOR_IMPORT_UPLOAD_FILE).selectFile('@import_file'); + cy.contains(file.fixture.split(/(\\|\/)/g).pop()); + cy.get(file.formErrorElement).should('be.visible'); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/step3-field-mapping-spec.js b/frontend/cypress/e2e/import/step3-field-mapping-spec.js new file mode 100644 index 0000000000..2598ecb528 --- /dev/null +++ b/frontend/cypress/e2e/import/step3-field-mapping-spec.js @@ -0,0 +1,322 @@ +import { USERS } from './constants/users'; +import { TIMEOUT_WAIT, VIEWPORTS } from './constants/common'; +import { FILES } from './constants/files'; +import { DEFAULT_FIELDMAPPINGS } from './constants/mappings'; +import { v4 as uuidv4 } from 'uuid'; +import { + SELECTOR_IMPORT_FIELDMAPPING_BUTTON_DELETE, + SELECTOR_IMPORT_FIELDMAPPING_BUTTON_DELETE_OK, + SELECTOR_IMPORT_FIELDMAPPING_CD_NOM, + SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN, + SELECTOR_IMPORT_FIELDMAPPING_MODAL, + SELECTOR_IMPORT_FIELDMAPPING_MODAL_CLOSE, + SELECTOR_IMPORT_FIELDMAPPING_MODAL_NAME, + SELECTOR_IMPORT_FIELDMAPPING_MODAL_NEW_OK, + SELECTOR_IMPORT_FIELDMAPPING_MODAL_OK, + SELECTOR_IMPORT_FIELDMAPPING_NOM_CITE, + SELECTOR_IMPORT_FIELDMAPPING_OBSERVERS, + SELECTOR_IMPORT_FIELDMAPPING_SELECTION, + SELECTOR_IMPORT_FIELDMAPPING_SELECTION_RENAME, + SELECTOR_IMPORT_FIELDMAPPING_SELECTION_RENAME_OK, + SELECTOR_IMPORT_FIELDMAPPING_SELECTION_RENAME_TEXT, + SELECTOR_IMPORT_FIELDMAPPING_VALIDATE, + SELECTOR_IMPORT_FIELDMAPPING_WKT, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +const FIELDMAPPING_TEST_NAME = uuidv4(); +const FIELDMAPPING_TEST_RENAME = uuidv4(); +const USER_ADMIN = USERS[0]; +const USER_AGENT = USERS[1]; +const VIEWPORT = VIEWPORTS[0]; + +function selectField(dataQa, value) { + cy.get(dataQa) + .should('exist') + .click() + .get('ng-dropdown-panel >') + .get('.ng-option') + .contains(value) + .then((v) => { + cy.wrap(v).should('exist').click(); + }); +} + +function selectMapping(mappingName) { + cy.get(SELECTOR_IMPORT_FIELDMAPPING_SELECTION) + .should('exist') + .click() + .get('ng-dropdown-panel') + .get('.ng-option') + .contains(mappingName) + .then((v) => { + cy.wrap(v).should('exist').click(); + }); +} + +function deleteCurrentMapping() { + // Delete the mapping + cy.get(SELECTOR_IMPORT_FIELDMAPPING_BUTTON_DELETE).should('exist').click(); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_BUTTON_DELETE_OK, { force: true }) + .should('be.enabled') + .click(); + cy.wait(TIMEOUT_WAIT); +} + +// //////////////////////////////////////////////////////////////////////////// +// Create a mapping with dummy values +// //////////////////////////////////////////////////////////////////////////// + +function fillTheFormRaw() { + selectField(SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN, 'date_debut'); + selectField(SELECTOR_IMPORT_FIELDMAPPING_OBSERVERS, 'date_debut'); + selectField(SELECTOR_IMPORT_FIELDMAPPING_NOM_CITE, 'date_debut'); + selectField(SELECTOR_IMPORT_FIELDMAPPING_WKT, 'date_debut'); + selectField(SELECTOR_IMPORT_FIELDMAPPING_CD_NOM, 'date_debut'); +} + +function fillTheForm() { + // Fill in the form with mandatory field + cy.get(SELECTOR_IMPORT_FIELDMAPPING_VALIDATE).should('exist'); + + selectField(SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN, 'date_debut'); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_VALIDATE).should('exist').should('not.be.enabled'); + + selectField(SELECTOR_IMPORT_FIELDMAPPING_OBSERVERS, 'date_debut'); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_VALIDATE).should('exist').should('not.be.enabled'); + selectField; + selectField(SELECTOR_IMPORT_FIELDMAPPING_NOM_CITE, 'date_debut'); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_VALIDATE).should('exist').should('not.be.enabled'); + + selectField(SELECTOR_IMPORT_FIELDMAPPING_WKT, 'date_debut'); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_VALIDATE).should('exist').should('not.be.enabled'); + + selectField(SELECTOR_IMPORT_FIELDMAPPING_CD_NOM, 'date_debut'); + + // Every mandatory field is filled: should be able to validate + cy.get(SELECTOR_IMPORT_FIELDMAPPING_VALIDATE).should('exist').should('be.enabled').click(); + + // Validation modal appear + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL, { force: true }).should('be.visible'); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_NEW_OK, { force: true }).should('be.disabled'); + + // Save the model + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_NAME, { force: true }) + .should('exist') + .clear() + .type(FIELDMAPPING_TEST_NAME); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_NEW_OK, { force: true }).should('be.enabled').click(); + + cy.wait(TIMEOUT_WAIT); +} + +function runTheProcess(user) { + cy.visitImport(); + cy.startImport(); + cy.pickDestination(); + cy.pickDataset(user.dataset); + cy.loadImportFile(FILES.synthese.valid.fixture); + cy.configureImportFile(); +} + +function restartTheProcess(user) { + cy.wait(TIMEOUT_WAIT); + cy.deleteCurrentImport(); + cy.wait(TIMEOUT_WAIT); + runTheProcess(user); +} + +function checkThatMappingCanNotBeSaved() { + // Trigger the modal + cy.get(SELECTOR_IMPORT_FIELDMAPPING_VALIDATE).should('exist').should('be.enabled').click(); + + // Validation modal appear + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_OK, { force: true }).should('not.exist'); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL, { force: true }).should('be.visible'); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_NEW_OK, { force: true }).should('be.disabled'); + + // Save the model + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_NAME, { force: true }) + .should('exist') + .clear() + .type(FIELDMAPPING_TEST_NAME); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_NEW_OK, { force: true }).should('be.enabled'); + + // Close the modal + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_CLOSE, { force: true }).click(); +} + +// //////////////////////////////////////////////////////////////////////////// +// Create a mapping with dummy values +// //////////////////////////////////////////////////////////////////////////// + +describe('Import - Field mapping step', () => { + context(`viewport: ${VIEWPORT.width}x${VIEWPORT.height}`, () => { + beforeEach(() => { + cy.viewport(VIEWPORT.width, VIEWPORT.height); + cy.geonatureLogin(USER_ADMIN.login.username, USER_ADMIN.login.password); + runTheProcess(USER_ADMIN); + cy.get('[data-qa="import-new-fieldmapping-form"]').should('exist'); + }); + + it('Should be able to create a new field mapping, rename it, and delete it', () => { + fillTheForm(); + restartTheProcess(USER_ADMIN); + + // Check the import list, and select expected mapping + selectMapping(FIELDMAPPING_TEST_NAME); + + // Rename the mapping + cy.get(SELECTOR_IMPORT_FIELDMAPPING_SELECTION_RENAME).should('exist').click(); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_SELECTION_RENAME_OK).should('be.disabled'); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_SELECTION_RENAME_TEXT) + .should('exist') + .clear() + .type(FIELDMAPPING_TEST_RENAME); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_SELECTION_RENAME_OK).should('be.enabled').click(); + + cy.wait(TIMEOUT_WAIT); + + // Reload the page + cy.reload(); + + // Check that the name has changed + selectMapping(FIELDMAPPING_TEST_RENAME); + + // Delete the mapping + deleteCurrentMapping(); + + // Reload the page + cy.reload(); + + // Check that the name has disappaeared + cy.get(SELECTOR_IMPORT_FIELDMAPPING_SELECTION) + .should('exist') + .click() + .get('ng-dropdown-panel') + .get('.ng-option') + .contains(FIELDMAPPING_TEST_RENAME) + .should('not.exist'); + }); + + it('Should be able to modifiy an item of the field mapping', () => { + fillTheForm(); + restartTheProcess(USER_ADMIN); + + // Check the import list, and select expected mapping + selectMapping(FIELDMAPPING_TEST_NAME); + + // Change a mapping value and save + selectField(SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN, 'date_fin'); + + cy.get(SELECTOR_IMPORT_FIELDMAPPING_VALIDATE).should('exist').should('be.enabled').click(); + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_OK, { force: true }).should('be.enabled').click(); + + cy.wait(TIMEOUT_WAIT); + + // restart the process + restartTheProcess(USER_ADMIN); + + // Check the import list, and select expected mapping + selectMapping(FIELDMAPPING_TEST_NAME); + + cy.get(SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN) + .find('.ng-value-label') + .should('exist') + .should('contains.text', 'date_fin'); + + // delete current mapping + deleteCurrentMapping(); + cy.wait(TIMEOUT_WAIT); + }); + + it('Should not be able to access fieldmapping owned by a different user', () => { + // Create the fieldmapping + fillTheForm(); + + // Switch user + cy.wait(TIMEOUT_WAIT); + cy.deleteCurrentImport(); + cy.geonatureLogout(); + cy.geonatureLogin(USER_AGENT.login.username, USER_AGENT.login.password); + runTheProcess(USER_AGENT); + + // Check that field mapping does not exist + cy.get(SELECTOR_IMPORT_FIELDMAPPING_SELECTION) + .should('exist') + .click() + .get('ng-dropdown-panel') + .get('.ng-option') + .contains(FIELDMAPPING_TEST_NAME) + .should('not.exist'); + + // Switch back to previous user + cy.deleteCurrentImport(); + cy.geonatureLogout(); + cy.geonatureLogin(USER_ADMIN.login.username, USER_ADMIN.login.password); + runTheProcess(USER_ADMIN); + + // Check that field mapping does exist + cy.get(SELECTOR_IMPORT_FIELDMAPPING_SELECTION) + .should('exist') + .click() + .get('ng-dropdown-panel') + .get('.ng-option') + .contains(FIELDMAPPING_TEST_NAME) + .should('exist'); + + // Check the import list, and select expected mapping + selectMapping(FIELDMAPPING_TEST_NAME); + deleteCurrentMapping(); + }); + + it('An admin user should be able to access and delete a mapping owned by an agent user', () => { + // Switch user + cy.deleteCurrentImport(); + cy.geonatureLogout(); + cy.geonatureLogin(USER_AGENT.login.username, USER_AGENT.login.password); + runTheProcess(USER_AGENT); + + // Create a mapping + fillTheForm(); + + // Switch back to previous user + cy.deleteCurrentImport(); + cy.geonatureLogout(); + cy.geonatureLogin(USER_ADMIN.login.username, USER_ADMIN.login.password); + runTheProcess(USER_ADMIN); + + // Check that field mapping does exist + cy.get(SELECTOR_IMPORT_FIELDMAPPING_SELECTION) + .should('exist') + .click() + .get('ng-dropdown-panel') + .get('.ng-option') + .contains(FIELDMAPPING_TEST_NAME) + .should('exist'); + + // Check the import list, and select expected mapping + selectMapping(FIELDMAPPING_TEST_NAME); + deleteCurrentMapping(); + }); + + it('Should not be able to modifiy the default mapping. A save to alternative should be offered to the user.', () => { + // Mapping Synthese + selectMapping(DEFAULT_FIELDMAPPINGS[0]); + selectField(SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN, 'date_fin'); + checkThatMappingCanNotBeSaved(); + + restartTheProcess(USER_ADMIN); + selectMapping(DEFAULT_FIELDMAPPINGS[1]); + fillTheFormRaw(); + checkThatMappingCanNotBeSaved(); + }); + + afterEach(() => { + cy.deleteCurrentImport(); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/step4-content-mapping-spec.js b/frontend/cypress/e2e/import/step4-content-mapping-spec.js new file mode 100644 index 0000000000..352dc0ae82 --- /dev/null +++ b/frontend/cypress/e2e/import/step4-content-mapping-spec.js @@ -0,0 +1,315 @@ +import { USERS } from './constants/users'; +import { TIMEOUT_WAIT, VIEWPORTS } from './constants/common'; +import { FILES } from './constants/files'; +import { v4 as uuidv4 } from 'uuid'; + +import { DEFAULT_CONTENTMAPPINGS } from './constants/mappings'; +import { + SELECTOR_IMPORT_CONTENTMAPPING_BUTTON_DELETE, + SELECTOR_IMPORT_CONTENTMAPPING_FORM, + SELECTOR_IMPORT_CONTENTMAPPING_MODAL, + SELECTOR_IMPORT_CONTENTMAPPING_MODAL_CLOSE, + SELECTOR_IMPORT_CONTENTMAPPING_MODAL_DELETE_OK, + SELECTOR_IMPORT_CONTENTMAPPING_MODAL_NAME, + SELECTOR_IMPORT_CONTENTMAPPING_MODAL_NEW_OK, + SELECTOR_IMPORT_CONTENTMAPPING_MODAL_OK, + SELECTOR_IMPORT_CONTENTMAPPING_SELECT, + SELECTOR_IMPORT_CONTENTMAPPING_SELECTION_RENAME, + SELECTOR_IMPORT_CONTENTMAPPING_SELECTION_RENAME_OK, + SELECTOR_IMPORT_CONTENTMAPPING_SELECTION_TEXT, + SELECTOR_IMPORT_CONTENTMAPPING_VALIDATE, + SELECTOR_IMPORT_NEW_VERIFICATION_START, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// +// //////////////////////////////////////////////////////////////////////////// + +const FIELD = 'id_nomenclature_behaviour'; +const MAPPING_TEST_NAME = uuidv4(); +const MAPPING_TEST_RENAME = uuidv4(); +const USER_ADMIN = USERS[0]; +const USER_AGENT = USERS[1]; +const VALUE = '0 - Inconnu'; +const VIEWPORT = VIEWPORTS[0]; + +function selectField(dataQa, value) { + cy.get(`[data-qa=import-contentmapping-theme-${dataQa}]`).should('exist').select(value); +} + +function selectMapping(mappingName) { + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_SELECT).should('exist').select(mappingName); +} + +function deleteCurrentMapping() { + // Delete the mapping + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_BUTTON_DELETE).should('exist').click(); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL_DELETE_OK, { force: true }) + .should('be.enabled') + .click({ force: true }); +} + +// //////////////////////////////////////////////////////////////////////////// +// Save mappping +// //////////////////////////////////////////////////////////////////////////// + +function saveTheForm() { + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_VALIDATE).should('exist').should('be.enabled').click(); + + // Validation modal appear + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL, { force: true }).should('exist'); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL_OK, { force: true }) + .should('be.enabled') + .click({ force: true }); +} + +function saveTheNewForm() { + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_VALIDATE) + .should('exist') + .should('be.enabled') + .click({ force: true }); + + // Validation modal appear + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL, { force: true }).should('exist'); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL_NEW_OK, { force: true }).should('be.disabled'); + + // Save the model + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL_NAME, { force: true }) + .should('exist') + .clear() + .type(MAPPING_TEST_NAME); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL_NEW_OK, { force: true }) + .should('be.enabled') + .click({ force: true }); +} + +function checkThatMappingCanNotBeSaved() { + // Trigger the modal + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_VALIDATE) + .should('exist') + .should('be.enabled') + .click({ force: true }); + + // Validation modal appear + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL, { force: true }).should('be.visible'); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL_OK, { force: true }).should('not.exist'); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL_NEW_OK, { force: true }).should('be.disabled'); + + // Save the model + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL_NAME, { force: true }) + .should('exist') + .clear() + .type(MAPPING_TEST_NAME); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL_NEW_OK, { force: true }).should('be.enabled'); + + // Close the modal + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_MODAL_CLOSE, { force: true }).click({ force: true }); +} + +// //////////////////////////////////////////////////////////////////////////// +// Create a mapping with dummy values +// //////////////////////////////////////////////////////////////////////////// + +function runTheProcess(user) { + cy.visitImport(); + cy.startImport(); + cy.pickDestination(); + cy.pickDataset(user.dataset); + cy.loadImportFile(FILES.synthese.valid.fixture); + cy.configureImportFile(); + cy.configureImportFieldMapping(); +} + +function runTheProcessForOcchab(user) { + cy.visitImport(); + cy.startImport(); + cy.pickDestination('Occhab'); + cy.pickDataset(user.dataset); + cy.loadImportFile(FILES.synthese.valid.fixture); + cy.configureImportFile(); + // cy.configureImportFieldMapping(); + selectFieldMappingField('import-fieldmapping-theme-date_min', 'error'); + selectFieldMappingField('import-fieldmapping-theme-WKT', 'error'); + cy.get('#mat-tab-label-0-1').click(); + selectFieldMappingField('import-fieldmapping-theme-nom_cite', 'error'); + selectFieldMappingField('import-fieldmapping-theme-cd_hab', 'error'); + + cy.get('[data-qa="import-new-fieldmapping-model-validate"]').click(); + cy.get('[data-qa="import-fieldmapping-saving-modal-cancel"]', { force: true }).click(); +} + +function restartTheProcess(user) { + cy.deleteCurrentImport(); + cy.wait(TIMEOUT_WAIT); + runTheProcess(user); +} + +// Occhab dedicated +function selectFieldMappingField(dataQa, value) { + cy.get(`[data-qa="${dataQa}"]`) + .should('exist') + .click() + .get('ng-dropdown-panel >') + .get('.ng-option') + .contains(value) + .then((v) => { + cy.wrap(v).should('exist').click(); + }); +} + +// //////////////////////////////////////////////////////////////////////////// +// Create a mapping with dummy values +// //////////////////////////////////////////////////////////////////////////// + +describe('Import - Content mapping step', () => { + context(`viewport: ${VIEWPORT.width}x${VIEWPORT.height}`, () => { + beforeEach(() => { + cy.viewport(VIEWPORT.width, VIEWPORT.height); + cy.geonatureLogin(USER_ADMIN.login.username, USER_ADMIN.login.password); + runTheProcess(USER_ADMIN); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_FORM).should('exist'); + }); + + it('Should be able to create a new mapping, rename it, and delete it', () => { + cy.log('Mapping: ' + MAPPING_TEST_NAME); + saveTheNewForm(); + restartTheProcess(USER_ADMIN); + + // Check the import list, and select expected mapping + selectMapping(MAPPING_TEST_NAME); + + // Rename the mapping + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_SELECTION_RENAME).should('exist').click(); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_SELECTION_RENAME_OK).should('be.disabled'); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_SELECTION_TEXT) + .should('exist') + .clear() + .type(MAPPING_TEST_RENAME); + cy.get(SELECTOR_IMPORT_CONTENTMAPPING_SELECTION_RENAME_OK).should('be.enabled').click(); + + // Reload the page + cy.reload(); + + // Check that the name has changed + selectMapping(MAPPING_TEST_RENAME); + + // Delete the mapping + deleteCurrentMapping(); + + // Reload the page + cy.reload(); + + // Check that the name has disappeared + cy.get( + `${SELECTOR_IMPORT_CONTENTMAPPING_SELECT} option:contains(${MAPPING_TEST_NAME})` + ).should('not.exist'); + }); + + it('Should be able to modifiy an item of the contentmapping', () => { + saveTheNewForm(); + restartTheProcess(USER_ADMIN); + + // Check the import list, and select expected mapping + selectMapping(MAPPING_TEST_NAME); + selectField(FIELD, VALUE); + saveTheForm(); + + // restart the process + restartTheProcess(USER_ADMIN); + + // Check the import list, and select expected mapping + selectMapping(MAPPING_TEST_NAME); + + cy.get(`[data-qa=import-contentmapping-theme-${FIELD}] option:selected`).should( + 'have.text', + ` ${VALUE} ` + ); + + // delete current mapping + deleteCurrentMapping(); + }); + + it('Should not be able to access mapping owned by a different user', () => { + // Create the mapping + saveTheNewForm(); + + // Switch user + cy.deleteCurrentImport(); + cy.geonatureLogout(); + cy.geonatureLogin(USER_AGENT.login.username, USER_AGENT.login.password); + runTheProcess(USER_AGENT); + + // Check that content mapping does not exist + cy.get( + `${SELECTOR_IMPORT_CONTENTMAPPING_SELECT} option:contains(${MAPPING_TEST_NAME})` + ).should('not.exist'); + + // Switch back to previous user + cy.deleteCurrentImport(); + cy.geonatureLogout(); + cy.geonatureLogin(USER_ADMIN.login.username, USER_ADMIN.login.password); + runTheProcess(USER_ADMIN); + + // Check that content mapping does exist + cy.get( + `${SELECTOR_IMPORT_CONTENTMAPPING_SELECT} option:contains(${MAPPING_TEST_NAME})` + ).should('exist'); + + // Check the import list, and select expected mapping + selectMapping(MAPPING_TEST_NAME); + deleteCurrentMapping(); + }); + + it('An admin user should be able to access and delete a mapping owned by an agent user', () => { + // Switch user + cy.deleteCurrentImport(); + cy.geonatureLogout(); + cy.geonatureLogin(USER_AGENT.login.username, USER_AGENT.login.password); + runTheProcess(USER_AGENT); + + // Create the mapping + saveTheNewForm(); + + // Switch back to previous user + cy.deleteCurrentImport(); + cy.geonatureLogout(); + cy.geonatureLogin(USER_ADMIN.login.username, USER_ADMIN.login.password); + runTheProcess(USER_ADMIN); + + // Check that content mapping does exist + cy.get( + `${SELECTOR_IMPORT_CONTENTMAPPING_SELECT} option:contains(${MAPPING_TEST_NAME})` + ).should('exist'); + + // Check the import list, and select expected mapping + selectMapping(MAPPING_TEST_NAME); + deleteCurrentMapping(); + }); + + it('Should not be able to modifiy the default mapping. A save to alternative should be offered to the user.', () => { + // Mapping Synthese + selectMapping(DEFAULT_CONTENTMAPPINGS[0]); + selectField(FIELD, VALUE); + checkThatMappingCanNotBeSaved(); + }); + + afterEach(() => { + cy.deleteCurrentImport(); + }); + }); + + context(`viewport: ${VIEWPORT.width}x${VIEWPORT.height}`, () => { + it('Should skip the contentmapping if it is not needed', () => { + cy.viewport(VIEWPORT.width, VIEWPORT.height); + cy.geonatureLogin(USER_ADMIN.login.username, USER_ADMIN.login.password); + // occhab + runTheProcessForOcchab(USER_ADMIN); + // Should be on step "verification" + cy.get(SELECTOR_IMPORT_NEW_VERIFICATION_START).should('exist'); + }); + + afterEach(() => { + cy.deleteCurrentImport(); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/step5-recaptiulatif-spec.js b/frontend/cypress/e2e/import/step5-recaptiulatif-spec.js new file mode 100644 index 0000000000..1ff9303e8e --- /dev/null +++ b/frontend/cypress/e2e/import/step5-recaptiulatif-spec.js @@ -0,0 +1,49 @@ +import { USERS } from './constants/users'; +import { VIEWPORTS } from './constants/common'; +import { FILES } from './constants/files'; +import { + SELECTOR_IMPORT_RECAPITULATIF, + SELECTOR_IMPORT_RECAPITULATIF_MAP, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// Create a mapping with dummy values +// //////////////////////////////////////////////////////////////////////////// + +const USER_ADMIN = USERS[0]; +const VIEWPORT = VIEWPORTS[0]; + +function runTheProcess(user) { + cy.visitImport(); + cy.startImport(); + cy.pickDestination(); + cy.pickDataset(user.dataset); + cy.loadImportFile(FILES.synthese.valid.fixture); + cy.configureImportFile(); + cy.configureImportFieldMapping(); + cy.configureImportContentMapping(); + cy.triggerImportVerification(); +} + +// //////////////////////////////////////////////////////////////////////////// +// Create a mapping with dummy values +// //////////////////////////////////////////////////////////////////////////// + +describe('Import - Recapitulatif step', () => { + context(`viewport: ${VIEWPORT.width}x${VIEWPORT.height}`, () => { + beforeEach(() => { + cy.viewport(VIEWPORT.width, VIEWPORT.height); + cy.geonatureLogin(USER_ADMIN.login.username, USER_ADMIN.login.password); + runTheProcess(USER_ADMIN); + }); + + it('should contains all the mains elements', () => { + cy.get(SELECTOR_IMPORT_RECAPITULATIF).should('exist'); + cy.get(SELECTOR_IMPORT_RECAPITULATIF_MAP).should('exist'); + }); + + afterEach(() => { + cy.deleteCurrentImport(); + }); + }); +}); diff --git a/frontend/cypress/e2e/import/step6-report-spec.js b/frontend/cypress/e2e/import/step6-report-spec.js new file mode 100644 index 0000000000..47c056db9a --- /dev/null +++ b/frontend/cypress/e2e/import/step6-report-spec.js @@ -0,0 +1,102 @@ +import { USERS } from './constants/users'; +import { TIMEOUT_WAIT, VIEWPORTS } from './constants/common'; +import { FILES } from './constants/files'; +import { + SELECTOR_IMPORT_REPORT, + SELECTOR_IMPORT_REPORT_CHART, + SELECTOR_IMPORT_REPORT_DOWNLOAD_PDF, + SELECTOR_IMPORT_REPORT_ERRORS_CSV, + SELECTOR_IMPORT_REPORT_ERRORS_TITLE, + SELECTOR_IMPORT_REPORT_MAP, +} from './constants/selectors'; + +// //////////////////////////////////////////////////////////////////////////// +// Create a mapping with dummy values +// //////////////////////////////////////////////////////////////////////////// + +const DOWNLOADS_FOLDER = Cypress.config('downloadsFolder'); +const FILENAME_INVALID_DATA = 'invalid_data.csv'; +const USER_ADMIN = USERS[0]; +const VIEWPORT = VIEWPORTS[0]; + +function runTheProcess(user) { + cy.visitImport(); + cy.startImport(); + cy.pickDestination(); + cy.pickDataset(user.dataset); + cy.loadImportFile(FILES.synthese.valid.fixture); + cy.configureImportFile(); + cy.configureImportFieldMapping(); + cy.configureImportContentMapping(); + cy.triggerImportVerification(); + cy.executeImport(); +} + +// //////////////////////////////////////////////////////////////////////////// +// Create a mapping with dummy values +// //////////////////////////////////////////////////////////////////////////// + +describe('Import - Report step', () => { + context(`viewport: ${VIEWPORT.width}x${VIEWPORT.height}`, () => { + beforeEach(() => { + cy.viewport(VIEWPORT.width, VIEWPORT.height); + cy.geonatureLogin(USER_ADMIN.login.username, USER_ADMIN.login.password); + runTheProcess(USER_ADMIN); + cy.get(SELECTOR_IMPORT_REPORT).should('exist'); + }); + + it('should contains the elements', () => { + cy.get(SELECTOR_IMPORT_REPORT_MAP).should('exist'); + cy.get(SELECTOR_IMPORT_REPORT_CHART).should('exist').should('not.be.empty'); + cy.get(SELECTOR_IMPORT_REPORT_ERRORS_TITLE) + .should('exist') + .should('have.text', '2 erreur(s)'); + + // Download a verify the error file + cy.get(SELECTOR_IMPORT_REPORT_ERRORS_CSV).click({ + force: true, + }); + cy.verifyDownload(FILENAME_INVALID_DATA, DOWNLOADS_FOLDER).then(() => { + cy.fixture('import/synthese/invalid_data.csv').then((fixtureFileContent) => { + cy.readFile(`${DOWNLOADS_FOLDER}/${FILENAME_INVALID_DATA}`).then( + (downloadedFileContent) => { + expect(downloadedFileContent).equals(fixtureFileContent); + } + ); + }); + // Delete the file after verification + cy.deleteFile(FILENAME_INVALID_DATA, DOWNLOADS_FOLDER); + }); + + cy.url().then((url) => { + // Extract the ID using string manipulation + const parts = url.split('/'); + const importID = parts[parts.length - 2]; // Get the penultimate element + const destination = parts[parts.length - 3]; + + // PDF report + cy.get(SELECTOR_IMPORT_REPORT_DOWNLOAD_PDF).click({ + force: true, + }); + + cy.wait(TIMEOUT_WAIT); + // https://github.com/cypress-io/cypress/issues/25443 + cy.task('getLastDownloadFileName', DOWNLOADS_FOLDER).then((filename) => { + cy.verifyDownload(filename, DOWNLOADS_FOLDER); + cy.deleteFile(filename, DOWNLOADS_FOLDER); + }); + }); + }); + + afterEach(() => { + cy.url().then((url) => { + // Extract the ID using string manipulation + const parts = url.split('/'); + const importID = parts[parts.length - 2]; // Get the penultimate element + const destination = parts[parts.length - 3]; + cy.deleteImport(importID, destination); + cy.visitImport(); + }); + }); + }); +}); diff --git a/frontend/cypress/e2e/metadata-spec.js b/frontend/cypress/e2e/metadata-spec.js index 9bf98e287f..611cfbe058 100644 --- a/frontend/cypress/e2e/metadata-spec.js +++ b/frontend/cypress/e2e/metadata-spec.js @@ -24,9 +24,7 @@ describe('Testing metadata', () => { cy.visit('/#/metadata'); }); - it('should display "cadre d\'acquisition"', async () => { - // const listCadreAcq = await promisify(cy.get("[data-qa='pnx-metadata-acq-framework']")); const wantedCA = await promisify(cy.get(caSelector)); // listCadreAcq[0].firstChild.firstChild.firstChild.children[1].innerText; @@ -50,19 +48,15 @@ describe('Testing metadata', () => { cy.get('[data-qa="pnx-metadata-jdd-' + jddUUID + '"]').click(); cy.get('[data-qa="pnx-metadata-dataset-name"]').contains(jdd); cy.get('[data-qa="pnx-metadata-exit-jdd"]').click(); - }); - - it('should search a JDD and find it"', {defaultCommandTimeout: 60000},() => { - + + it('should search a JDD and find it"', { defaultCommandTimeout: 60000 }, () => { cy.get('[data-qa="pnx-metadata-search"]').type(jdd); //http://127.0.0.1:8000/meta/acquisition_frameworks?datasets=0&creator=1&actors=1 - cy.intercept(Cypress.env('apiEndpoint') + 'meta/acquisition_frameworks?**').as('getAF'); - cy.wait('@getAF').then((interception) => { + cy.intercept(Cypress.env('apiEndpoint') + 'meta/acquisition_frameworks?**', (req) => { cy.get('[data-qa="pnx-metadata-acq-framework-header-' + caUUID + '"]').click(); cy.get('[data-qa="pnx-metadata-jdd-' + jddUUID + '"]').contains(jdd); }); - }); it('should create a new "cardre d\'acquisition"', () => { @@ -111,7 +105,6 @@ describe('Testing metadata', () => { cy.get('[data-qa="pnx-metadata-acq-framework-name"]').contains(newCadreAcq.name); }); - it('should create a new "jeux de données"', () => { cy.visit('/#/metadata'); cy.get('[data-qa="pnx-metadata-add-jdd"]').click(); diff --git a/frontend/cypress/e2e/occhab-spec.js b/frontend/cypress/e2e/occhab-spec.js index e8c55bd2a5..7153463f3b 100644 --- a/frontend/cypress/e2e/occhab-spec.js +++ b/frontend/cypress/e2e/occhab-spec.js @@ -1,50 +1,56 @@ import promisify from 'cypress-promise'; - -describe("Testing occhab", () => { - +describe('Testing occhab', () => { beforeEach(() => { cy.geonatureLogin(); }); it('should create an habitation', async () => { - cy.visit("/#/occhab") - - const canvas = "[data-qa='pnx-occhab-form'] > div:nth-child(1) > pnx-map > div > div.leaflet-container.leaflet-touch.leaflet-fade-anim.leaflet-grab.leaflet-touch-drag.leaflet-touch-zoom" - cy.get('#add-btn').click() - - cy.get('#validateButton').should('be.disabled') - - cy.get('[data-qa="pnx-occhab-form"] > div:nth-child(1) > pnx-map > div > div.leaflet-container.leaflet-touch.leaflet-fade-anim.leaflet-grab.leaflet-touch-drag.leaflet-touch-zoom > div.leaflet-control-container > div.leaflet-top.leaflet-left > div.leaflet-draw.leaflet-control > div:nth-child(1) > div > a').click() - cy.get(canvas).click(250,250) - cy.get(canvas).click(300,250) - cy.get(canvas).click(300,300) - cy.get(canvas).click(250,300) - cy.get(canvas).click(250,250) - cy.get('#validateButton').should('be.disabled') - - cy.get('[data-qa="gn-common-form-observers-select"]').click() - cy.get('[data-qa="gn-common-form-observers-select-AGENT test"]').click() - cy.get('#validateButton').should('be.disabled') - - cy.get('[data-qa="pnx-occhab-form-dataset"] > ng-select').click() - cy.get('[data-qa="Carto d\'habitat X"]').click() - cy.get('#validateButton').should('be.disabled') - - cy.get('[data-qa="pnx-occhab-form-geographic"] > div > select').select("1: Object") - cy.get('#validateButton').should('be.disabled') - - cy.get('#add-hab-btn').click() - cy.get('#taxonInput').type('dune') - cy.get('#ngb-typeahead-3-0').click() - cy.get('[data-qa="pnx-occhab-form-technique-collect"] > div > select').select('1: Object') - cy.get('[data-qa="pnx-occhab-form-valid-button"]').click() - - cy.get('#validateButton').click() - - const listHabit = await promisify(cy.get('[data-qa="pnx-occhab-map-list-datatable"] > div > datatable-body > datatable-selection > datatable-scroller')) - expect(listHabit[0].children[0].children[0].children[1].children[4].innerText).contains('Prés salés du contact haut schorre/dune') - listHabit[0].children[0].children[0].children[1].children[2].children[0].children[0].click() - }) - -}) \ No newline at end of file + cy.visit('/#/occhab'); + + const canvas = + "[data-qa='pnx-occhab-form'] > div:nth-child(1) > pnx-map > div > div.leaflet-container.leaflet-touch.leaflet-fade-anim.leaflet-grab.leaflet-touch-drag.leaflet-touch-zoom"; + cy.get('#add-btn').click(); + + cy.get('#validateButton').should('be.disabled'); + + cy.get( + '[data-qa="pnx-occhab-form"] > div:nth-child(1) > pnx-map > div > div.leaflet-container.leaflet-touch.leaflet-fade-anim.leaflet-grab.leaflet-touch-drag.leaflet-touch-zoom > div.leaflet-control-container > div.leaflet-top.leaflet-left > div.leaflet-draw.leaflet-control > div:nth-child(1) > div > a' + ).click(); + cy.get(canvas).click(250, 250); + cy.get(canvas).click(300, 250); + cy.get(canvas).click(300, 300); + cy.get(canvas).click(250, 300); + cy.get(canvas).click(250, 250); + cy.get('#validateButton').should('be.disabled'); + + cy.get('[data-qa="gn-common-form-observers-select"]').click(); + cy.get('[data-qa="gn-common-form-observers-select-AGENT test"]').click(); + cy.get('#validateButton').should('be.disabled'); + + cy.get('[data-qa="pnx-occhab-form-dataset"] > ng-select').click(); + cy.get('[data-qa="Carto d\'habitat X"]').click(); + cy.get('#validateButton').should('be.disabled'); + + cy.get('[data-qa="pnx-occhab-form-geographic"] > div > select').select('1: Object'); + cy.get('#validateButton').should('be.disabled'); + + cy.get('#add-hab-btn').click(); + cy.get('#taxonInput').type('dune'); + cy.get('#ngb-typeahead-3-0').click(); + cy.get('[data-qa="pnx-occhab-form-technique-collect"] > div > select').select('1: Object'); + cy.get('[data-qa="pnx-occhab-form-valid-button"]').click(); + + cy.get('#validateButton').click(); + + const listHabit = await promisify( + cy.get( + '[data-qa="pnx-occhab-map-list-datatable"] > div > datatable-body > datatable-selection > datatable-scroller' + ) + ); + expect(listHabit[0].children[0].children[0].children[1].children[4].innerText).contains( + 'Prés salés du contact haut schorre/dune' + ); + listHabit[0].children[0].children[0].children[1].children[2].children[0].children[0].click(); + }); +}); diff --git a/frontend/cypress/e2e/occtax-dataset-submodule-spec.js b/frontend/cypress/e2e/occtax-dataset-submodule-spec.js index 45cdf0c5d5..2476995914 100644 --- a/frontend/cypress/e2e/occtax-dataset-submodule-spec.js +++ b/frontend/cypress/e2e/occtax-dataset-submodule-spec.js @@ -1,16 +1,11 @@ - - beforeEach(() => { cy.geonatureLogin(); cy.visit('/#/occtax_ds'); }); - it('Should click on OCCTAX_DS module and load data with module_code in url', () => { - - cy.intercept(Cypress.env('apiEndpoint') + 'occtax/OCCTAX_DS/releves?**').as('getReleves'); - cy.wait('@getReleves').then((interception) => { - expect(interception.response.statusCode, 200); + cy.intercept(Cypress.env('apiEndpoint') + 'occtax/OCCTAX_DS/releves?**', (req) => { + expect(req.response.statusCode, 200); }); }); it('Should change module nav home name', () => { diff --git a/frontend/cypress/e2e/occtax-form-spec.js b/frontend/cypress/e2e/occtax-form-spec.js index d517859640..2de710390e 100644 --- a/frontend/cypress/e2e/occtax-form-spec.js +++ b/frontend/cypress/e2e/occtax-form-spec.js @@ -4,8 +4,8 @@ const taxaNameRef = 'Canis lupus lupus = Canis lupus lupus Linnaeus, 1758 - [SSE const dateSaisieTaxon = '25/01/2022'; const taxaSearch = 'canis lupus'; -function filterMapList() { - cy.intercept(Cypress.env('apiEndpoint') + 'occtax/OCCTAX/releves?**').as('getReleves'); +function filterMapList(testFunction) { + cy.intercept(Cypress.env('apiEndpoint') + 'occtax/OCCTAX/releves?**', testFunction); cy.get('[data-qa="pnx-occtax-filter"]').click(); cy.get('[data-qa="taxonomy-form-input"]').clear().type(taxaSearch); const results = cy.get('ngb-typeahead-window'); @@ -19,7 +19,6 @@ function filterMapList() { .clear() .type(dateSaisieTaxon); cy.get('[data-qa="pnx-occtax-filter-search"]').click(); - cy.wait('@getReleves'); } describe('Testing adding an observation in OccTax', { testIsolation: false }, () => { @@ -322,7 +321,7 @@ describe('Testing adding an observation in OccTax', { testIsolation: false }, () }); it('Should filter the last observation', () => { - filterMapList(); + filterMapList((req) => {}); }); // // FIXME we should wait for the end of the research! @@ -332,7 +331,6 @@ describe('Testing adding an observation in OccTax', { testIsolation: false }, () "[data-qa='pnx-occtax-map-list'] > div > div.row > div:nth-child(2) > ngx-datatable > div > datatable-body > datatable-selection > datatable-scroller > datatable-row-wrapper > datatable-body-row > div.datatable-row-center.datatable-row-group.ng-star-inserted > datatable-body-cell:nth-child(7) > div > div > span" ) ); - expect(date[0].innerText).to.equal('25-01-2022'); }); it('should edit a releve', () => { @@ -351,9 +349,10 @@ describe('Testing adding an observation in OccTax', { testIsolation: false }, () // FIXME we should wait for the end of the research! it('Should delete the taxa', () => { - filterMapList(); - cy.get('[data-qa="pnx-occtax-delete-taxa"]').first().click(); - // cy.wait(2000) - cy.get('[data-qa="pnx-occtax-delete"]').click(); + filterMapList((req) => { + cy.get('[data-qa="pnx-occtax-delete-taxa"]').first().click(); + // cy.wait(2000) + cy.get('[data-qa="pnx-occtax-delete"]').click(); + }); }); }); diff --git a/frontend/cypress/e2e/synthese-spec.js b/frontend/cypress/e2e/synthese-spec.js index a868a9a937..c598f4d83c 100644 --- a/frontend/cypress/e2e/synthese-spec.js +++ b/frontend/cypress/e2e/synthese-spec.js @@ -1,7 +1,7 @@ describe('Tests gn_synthese', () => { beforeEach(() => { cy.geonatureLogin(); - cy.visit("/#/synthese") + cy.visit('/#/synthese'); }); afterEach(() => { @@ -10,7 +10,6 @@ describe('Tests gn_synthese', () => { }); it('Should search by taxa name', function () { - // objectifs : pouvoir rentrer un nom d'espèce dans le filtre, que cela affiche le ou les observations sur la liste correspondant à ce nom cy.get('#taxonInput').clear(); cy.get('#taxonInput').type('lynx'); @@ -31,12 +30,20 @@ describe('Tests gn_synthese', () => { it('Should search by date', function () { // objectifs : pouvoir changer les dates des filtres, que cela affiche le ou les obs dans la liste d'observations dans la plage de dates donnée - - cy.intercept(Cypress.env('apiEndpoint') + 'synthese/for_web?**', (req => { + cy.intercept(Cypress.env('apiEndpoint') + 'synthese/for_web?**', (req) => { if (req.body.hasOwnProperty('date_min')) { - req.alias = 'filteredByDate' + const cells = cy.get('.synthese-list-col-date_min'); + cells.then((d) => { + expect(d.length).to.greaterThan(0); + }); + cells.each(($el, index, $list) => { + var [day, month, year] = $el.text().split('-'); + const date = new Date(year, month - 1, day); + expect(date).to.be.greaterThan(new Date('2016-12-24')); + expect(date).to.be.lessThan(new Date('2017-01-02')); + }); } - })) + }); // select datemin cy.get('[data-qa="synthese-form-date-min"]').click(); @@ -51,20 +58,6 @@ describe('Tests gn_synthese', () => { // search cy.get('[data-qa="synthese-search-btn"]').click(); - - cy.wait('@filteredByDate').then(() => { - // get datatable cells containing the observater info - const cells = cy.get('.synthese-list-col-date_min'); - cells.then((d) => { - expect(d.length).to.greaterThan(0); - }); - cells.each(($el, index, $list) => { - var [day, month, year] = $el.text().split("-"); - const date = new Date(year, month -1, day); - expect(date).to.be.greaterThan(new Date('2016-12-24')); - expect(date).to.be.lessThan(new Date('2017-01-02')); - }); - }) }); it('Should search by observer', function () { @@ -104,39 +97,35 @@ describe('Tests gn_synthese', () => { }); it('Should search by acquisition framework and dataset', () => { - // ce test permet de faire une suite d'actions basées sur la sélection des CA et des JDD // vérifie que la sélection d'un cadre d'acquisition filtre bien les jeux de données // objectifs : pouvoir sélectionnner un jeu de données dans la liste déroulante, cy.get('[data-qa="synthese-form-ca"] ng-select').click(); // Intercept request to datasets which must have a parameter to "id_acquisition_framework" - cy.intercept(Cypress.env('apiEndpoint') + 'meta/datasets?**', (req => { + cy.intercept(Cypress.env('apiEndpoint') + 'meta/datasets?**', (req) => { if (req.body.hasOwnProperty('id_acquisition_frameworks')) { - req.alias = 'filteredDatasets' + // select CA 1 and check JDD-1 is in list + cy.get('[data-qa="synthese-form-ca"] ng-select').click(); + cy.get('[data-qa="CA-1"]').click(); + cy.get('[data-qa="synthese-form-dataset"] ng-select').click(); + cy.get('[data-qa="JDD-1"]').click(); + cy.get('[data-qa="synthese-search-btn"]') + .click() + .wait(1000) + .then(() => { + const resultsCells = cy.get('.synthese-list-col-dataset_name'); + resultsCells.then((d) => { + expect(d.length).to.greaterThan(0); + }); + resultsCells.each(($el, index, $list) => { + expect($el.text().trim()).to.be.equal('JDD-1'); + }); + }); } - })) + }); // select a CA without dataset and check JDD 1 is not in list - cy.get('[data-qa="CA-2-empty"]').click() + cy.get('[data-qa="CA-2-empty"]').click(); // wait for the filtered request - cy.wait('@filteredDatasets') - - // select CA 1 and check JDD-1 is in list - cy.get('[data-qa="synthese-form-ca"] ng-select').click(); - cy.get('[data-qa="CA-1"]').click(); - cy.get('[data-qa="synthese-form-dataset"] ng-select').click(); - cy.get('[data-qa="JDD-1"]').click(); - cy.get('[data-qa="synthese-search-btn"]') - .click() - .wait(1000) - .then(() => { - const resultsCells = cy.get('.synthese-list-col-dataset_name'); - resultsCells.then((d) => { - expect(d.length).to.greaterThan(0); - }); - resultsCells.each(($el, index, $list) => { - expect($el.text().trim()).to.be.equal('JDD-1'); - }); - }); }); it('Should open the observation details pop-up and check its content', () => { diff --git a/frontend/cypress/fixtures/config.json b/frontend/cypress/fixtures/config.json index 14787a642a..51f461a3de 100644 --- a/frontend/cypress/fixtures/config.json +++ b/frontend/cypress/fixtures/config.json @@ -203,7 +203,6 @@ "DISPLAY_MAP_LAST_OBS": true }, "DEFAULT_LANGUAGE": "fr", - "API_TAXHUB": "http://127.0.0.1:5000", "ACCOUNT_MANAGEMENT": { "AUTO_DATASET_CREATION": true, "ENABLE_USER_MANAGEMENT": false, diff --git a/frontend/cypress/fixtures/import/occhab/valid_file.csv b/frontend/cypress/fixtures/import/occhab/valid_file.csv new file mode 100644 index 0000000000..d9285de36e --- /dev/null +++ b/frontend/cypress/fixtures/import/occhab/valid_file.csv @@ -0,0 +1,21 @@ +Erreur station;Erreur habitat;id_station_source;unique_id_sinp_station;unique_dataset_id;date_min;date_max;observers_txt;id_nomenclature_area_surface_calculation;WKT;id_nomenclature_geographic_object;unique_id_sinp_habitat;nom_cite;cd_hab;technical_precision +OK !;OK !;;afa81c29-c75d-408d-bf48-53cce02d5561;VALID_DATASET_UUID;17/11/2023;17/11/2023;;;POINT(3.634 44.399);St;4ee53579-b09b-408f-aa1f-d62495a66667;prairie;24; +OK !;OK !;;74be5e79-72e7-42a8-ba2e-d5e27c9caddb;;17/11/2023;;;;POINT(3.634 44.399);St;d91496e9-d904-45a8-9e18-cb8acbbb6ea6;prairie;24; +DUPLICATE_UUID(unique_id_sinp_station);ERRONEOUS_PARENT_ENTITY;;462d385f-489a-436b-babb-8cca5fc62e1d;;17/11/2023;17/11/2023;;;POINT(3.634 44.399);St;e5e7a184-3e92-4adb-a721-5bd004b3397f;forêt;24; +DUPLICATE_UUID(unique_id_sinp_station);ERRONEOUS_PARENT_ENTITY;;462d385f-489a-436b-babb-8cca5fc62e1d;;17/11/2023;17/11/2023;;;POINT(3.634 44.399);St;8f52f122-b9ae-45b3-b947-2c9f7934b823;prairie;24; +DATASET_NOT_FOUND(unique_dataset_id);ERRONEOUS_PARENT_ENTITY;;bdc3346d-0fc3-40fa-b787-be927e4dd82e;050d613c-543f-47fd-800a-13931b2721c7;17/11/2023;17/11/2023;;;POINT(3.634 44.399);St;2ff4867d-6943-45d8-873d-187fbc6d67a7;prairie;24; +DATASET_NOT_AUTHORIZED(unique_dataset_id);ERRONEOUS_PARENT_ENTITY;;f5f031a3-cf1b-419c-9817-69c39f51aef4;FORBIDDEN_DATASET_UUID;17/11/2023;17/11/2023;;;POINT(3.634 44.399);St;5dfb9930-4795-4e6f-baae-3dd86abb3b70;prairie;24; +INVALID_UUID(unique_dataset_id);Pas d’habitat;;;erroneous;17/11/2023;17/11/2023;;;POINT(3.634 44.399);;;;; +NO-GEOM(Champs géométriques);Pas d’habitat;;;;17/11/2023;17/11/2023;Toto;;;;;;; +MISSING_VALUE(date_min);ERRONEOUS_PARENT_ENTITY;;4ee7728d-387d-49c5-b9a3-4162b0987fa5;;;;;;POINT(3.634 44.399);St;aeb10ac4-6d69-4fa6-8df6-14d9304911df;prairie;24; +Pas de station;NO_PARENT_ENTITY(id_station);;;;;;;;;;;prairie;24; +ORPHAN_ROW(unique_id_sinp_station);ORPHAN_ROW(unique_id_sinp_station);;258a2478-8a0e-4321-83df-c2313ad3040e;;;;;;;;;;; +ORPHAN_ROW(id_station_source);ORPHAN_ROW(id_station_source);Station 0;;;;;;;;;;;; +DUPLICATE_SOURCE_ENTITY_PK(id_station_source);ERRONEOUS_PARENT_ENTITY;Station 1;;;17/11/2023;17/11/2023;;;POINT(3.634 44.399);St;;prairie;24; +DUPLICATE_SOURCE_ENTITY_PK(id_station_source);ERRONEOUS_PARENT_ENTITY;Station 1;;;17/11/2023;17/11/2023;;;POINT(3.634 44.399);St;;prairie;24; +OK !;OK !;Station 2;;;17/11/2023;17/11/2023;;;POINT(3.634 44.399);St;;prairie;24; +Pas de station;OK !;Station 2;;;;;;;;;;prairie;24; +Pas de station;OK !;;74be5e79-72e7-42a8-ba2e-d5e27c9caddb;;;;;;;;;prairie;24; +Pas de station;ERRONEOUS_PARENT_ENTITY;;bdc3346d-0fc3-40fa-b787-be927e4dd82e;;;;;;;;;prairie;24; +INVALID_UUID(unique_id_sinp_station);INVALID_UUID(unique_id_sinp_station),ERRONEOUS_PARENT_ENTITY;;erroneous;;17/11/2023;17/11/2023;;;POINT(3.634 44.399);St;6c02ef80-2e78-4c2c-b8b5-1c75e2349fc2;prairie;24; +Pas de station;INVALID_UUID(unique_id_sinp_station);;erroneous;;;;;;;;;prairie;24; diff --git a/frontend/cypress/fixtures/import/synthese/bad.csv b/frontend/cypress/fixtures/import/synthese/bad.csv new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/cypress/fixtures/import/synthese/bad_extension.pdf b/frontend/cypress/fixtures/import/synthese/bad_extension.pdf new file mode 100644 index 0000000000..20e21bbd46 Binary files /dev/null and b/frontend/cypress/fixtures/import/synthese/bad_extension.pdf differ diff --git a/frontend/cypress/fixtures/import/synthese/empty.csv b/frontend/cypress/fixtures/import/synthese/empty.csv new file mode 100644 index 0000000000..55b500db4c --- /dev/null +++ b/frontend/cypress/fixtures/import/synthese/empty.csv @@ -0,0 +1,2 @@ + +error;id_synthese;id_origine;comment_releve;comment_occurrence;date_debut;date_fin;heure_debut;heure_fin;cd_nom;cd_ref;nom_valide;nom_vernaculaire;nom_cite;regne;group1_inpn;group2_inpn;classe;ordre;famille;rang_taxo;nombre_min;nombre_max;alti_min;alti_max;prof_min;prof_max;observateurs;determinateur;communes;geometrie_wkt_4326;x_centroid_4326;y_centroid_4326;nom_lieu;validateur;niveau_validation;date_validation;comment_validation;preuve_numerique_url;preuve_non_numerique;jdd_nom;jdd_uuid;jdd_id;ca_nom;ca_uuid;ca_id;cd_habref;cd_habitat;nom_habitat;precision_geographique;nature_objet_geo;type_regroupement;methode_regroupement;technique_observation;biologique_statut;etat_biologique;biogeographique_statut;naturalite;preuve_existante;niveau_precision_diffusion;stade_vie;sexe;objet_denombrement;type_denombrement;niveau_sensibilite;statut_observation;floutage_dee;statut_source;type_info_geo;methode_determination;comportement;reference_biblio;uuid_perm_sinp;uuid_perm_grp_sinp;date_creation;date_modification diff --git a/frontend/cypress/fixtures/import/synthese/invalid_data.csv b/frontend/cypress/fixtures/import/synthese/invalid_data.csv new file mode 100644 index 0000000000..82e9687ad6 --- /dev/null +++ b/frontend/cypress/fixtures/import/synthese/invalid_data.csv @@ -0,0 +1,4 @@ +error;id_synthese;id_origine;comment_releve;comment_occurrence;date_debut;date_fin;heure_debut;heure_fin;cd_nom;cd_ref;nom_valide;nom_vernaculaire;nom_cite;regne;group1_inpn;group2_inpn;classe;ordre;famille;rang_taxo;nombre_min;nombre_max;alti_min;alti_max;prof_min;prof_max;observateurs;determinateur;communes;geometrie_wkt_4326;x_centroid_4326;y_centroid_4326;nom_lieu;validateur;niveau_validation;date_validation;comment_validation;preuve_numerique_url;preuve_non_numerique;jdd_nom;jdd_uuid;jdd_id;ca_nom;ca_uuid;ca_id;cd_habref;cd_habitat;nom_habitat;precision_geographique;nature_objet_geo;type_regroupement;methode_regroupement;technique_observation;biologique_statut;etat_biologique;biogeographique_statut;naturalite;preuve_existante;niveau_precision_diffusion;stade_vie;sexe;objet_denombrement;type_denombrement;niveau_sensibilite;statut_observation;floutage_dee;statut_source;type_info_geo;methode_determination;comportement;reference_biblio;uuid_perm_sinp;uuid_perm_grp_sinp;date_creation;date_modification +duplicate id;3;3;Relevé n°3;Occurrence n°3;2017-01-08;;;;67111;67111;Alburnus alburnus (Linnaeus, 1758);Ablette;Ablette;Animalia;Chordés;Poissons;Actinopterygii;Cypriniformes;Leuciscidae;ES;1;1;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;2f92f91a-64a2-4684-90e4-140466bb34e3;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +duplicate id;3;4;Relevé n°4;Occurrence n°4;2017-01-08;2017-01-08;20:00:00;23:00:00;67111;67111;Alburnus alburnus (Linnaeus, 1758);Ablette;Ablette;Animalia;Chordés;Poissons;Actinopterygii;Cypriniformes;Leuciscidae;ES;1;1;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;2f92f91a-64a2-4684-90e4-140466bb34e4;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +count min > count max;5;5;Relevé n°5;Occurrence n°5;2017-01-08;2017/01/08;20:00;23:00:00;67111;67111;Alburnus alburnus (Linnaeus, 1758);Ablette;Ablette;Animalia;Chordés;Poissons;Actinopterygii;Cypriniformes;Leuciscidae;ES;20;5;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;2f92f91a-64a2-4684-90e4-140466bb34e5;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 diff --git a/frontend/cypress/fixtures/import/synthese/liste_import.json b/frontend/cypress/fixtures/import/synthese/liste_import.json new file mode 100644 index 0000000000..5c2da9d0cd --- /dev/null +++ b/frontend/cypress/fixtures/import/synthese/liste_import.json @@ -0,0 +1,519 @@ +{ + "count": 2, + "imports": [ + { + "altitude_autogenerated": null, + "authors": [ + { + "nom_complet": "User Test - 1" + } + ], + "authors_name": "User Test - 1", + "available_encodings": ["iso-8859-1", "iso-8859-15", "utf-8"], + "available_formats": ["csv", "geojson"], + "available_separators": [",", ";"], + "columns": [ + "error", + "id_synthese", + "id_origine", + "comment_releve", + "comment_occurrence", + "date_debut", + "date_fin", + "heure_debut", + "heure_fin", + "cd_nom", + "cd_ref", + "nom_valide", + "nom_vernaculaire", + "nom_cite", + "regne", + "group1_inpn", + "group2_inpn", + "classe", + "ordre", + "famille", + "rang_taxo", + "nombre_min", + "nombre_max", + "alti_min", + "alti_max", + "prof_min", + "prof_max", + "observateurs", + "determinateur", + "communes", + "geometrie_wkt_4326", + "x_centroid_4326", + "y_centroid_4326", + "nom_lieu", + "validateur", + "niveau_validation", + "date_validation", + "comment_validation", + "preuve_numerique_url", + "preuve_non_numerique", + "jdd_nom", + "jdd_uuid", + "jdd_id", + "ca_nom", + "ca_uuid", + "ca_id", + "cd_habref", + "cd_habitat", + "nom_habitat", + "precision_geographique", + "nature_objet_geo", + "type_regroupement", + "methode_regroupement", + "technique_observation", + "biologique_statut", + "etat_biologique", + "biogeographique_statut", + "naturalite", + "preuve_existante", + "niveau_precision_diffusion", + "stade_vie", + "sexe", + "objet_denombrement", + "type_denombrement", + "niveau_sensibilite", + "statut_observation", + "floutage_dee", + "statut_source", + "type_info_geo", + "methode_determination", + "comportement", + "reference_biblio", + "uuid_perm_sinp", + "uuid_perm_grp_sinp", + "date_creation", + "date_modification" + ], + "contentmapping": { + "DEE_FLOU": { + "Non": "NON" + }, + "ETA_BIO": { + "Non renseign\u00e9": "1" + }, + "METH_DETERMIN": { + "Autre m\u00e9thode de d\u00e9termination": "2" + }, + "METH_OBS": { + "Galerie/terrier": "23" + }, + "NATURALITE": { + "Sauvage": "1" + }, + "NAT_OBJ_GEO": { + "Inventoriel": "In" + }, + "NIV_PRECIS": { + "Pr\u00e9cise": "5" + }, + "OBJ_DENBR": { + "Individu": "IND" + }, + "OCC_COMPORTEMENT": { + "Non renseign\u00e9": "1" + }, + "PREUVE_EXIST": { + "Oui": "1" + }, + "SENSIBILITE": { + "Non sensible - Diffusion pr\u00e9cise": "0" + }, + "SEXE": { + "Femelle": "2" + }, + "STADE_VIE": { + "Adulte": "2", + "Immature": "4", + "Juv\u00e9nile": "3" + }, + "STATUT_BIO": { + "Non renseign\u00e9": "1" + }, + "STATUT_OBS": { + "Pr\u00e9sent": "Pr" + }, + "STATUT_SOURCE": { + "Terrain": "Te" + }, + "STATUT_VALID": { + "En attente de validation": "0" + }, + "STAT_BIOGEO": { + "Non renseign\u00e9": "1" + }, + "TYP_DENBR": { + "Compt\u00e9": "Co" + }, + "TYP_GRP": { + "OBS": "OBS" + } + }, + "cruved": { + "C": true, + "D": true, + "E": false, + "R": true, + "U": true, + "V": false + }, + "dataset": { + "active": true, + "dataset_name": "Contact al\u00e9atoire tous r\u00e8gnes confondus" + }, + "date_create_import": "2024-05-13 23:55:51.167003", + "date_end_import": "2024-05-13 23:56:46.793605", + "date_max_data": null, + "date_min_data": null, + "date_update_import": "2024-05-13 23:56:46.796963", + "destination": { + "code": "synthese", + "label": "Synth\u00e8se", + "statistics_labels": [ + { + "key": "taxa_count", + "value": "Nombre de taxons import\u00e9s" + } + ] + }, + "detected_encoding": "utf-8", + "detected_format": "csv", + "detected_separator": ";", + "encoding": "utf-8", + "errors_count": 3, + "fieldmapping": { + "WKT": "geometrie_wkt_4326", + "altitude_max": "alti_max", + "altitude_min": "alti_min", + "cd_hab": "cd_habref", + "cd_nom": "cd_nom", + "comment_context": "comment_releve", + "comment_description": "comment_occurrence", + "count_max": "nombre_max", + "count_min": "nombre_min", + "date_max": "date_fin", + "date_min": "date_debut", + "depth_max": "prof_max", + "depth_min": "prof_min", + "determiner": "determinateur", + "digital_proof": "preuve_numerique_url", + "entity_source_pk_value": "id_synthese", + "grp_method": "methode_regroupement", + "hour_max": "heure_fin", + "hour_min": "heure_debut", + "id_nomenclature_behaviour": "comportement", + "id_nomenclature_bio_condition": "etat_biologique", + "id_nomenclature_bio_status": "biologique_statut", + "id_nomenclature_biogeo_status": "biogeographique_statut", + "id_nomenclature_blurring": "floutage_dee", + "id_nomenclature_determination_method": "methode_determination", + "id_nomenclature_diffusion_level": "niveau_precision_diffusion", + "id_nomenclature_exist_proof": "preuve_existante", + "id_nomenclature_geo_object_nature": "nature_objet_geo", + "id_nomenclature_grp_typ": "type_regroupement", + "id_nomenclature_life_stage": "stade_vie", + "id_nomenclature_naturalness": "naturalite", + "id_nomenclature_obj_count": "objet_denombrement", + "id_nomenclature_obs_technique": "technique_observation", + "id_nomenclature_observation_status": "statut_observation", + "id_nomenclature_sensitivity": "niveau_sensibilite", + "id_nomenclature_sex": "sexe", + "id_nomenclature_source_status": "statut_source", + "id_nomenclature_type_count": "type_denombrement", + "id_nomenclature_valid_status": "niveau_validation", + "meta_create_date": "date_creation", + "meta_update_date": "date_modification", + "meta_validation_date": "date_validation", + "nom_cite": "nom_cite", + "non_digital_proof": "preuve_non_numerique", + "observers": "observateurs", + "place_name": "nom_lieu", + "precision": "precision_geographique", + "reference_biblio": "reference_biblio", + "unique_id_sinp": "uuid_perm_sinp", + "unique_id_sinp_grp": "uuid_perm_grp_sinp", + "validation_comment": "comment_validation", + "validator": "validateur" + }, + "format_source_file": "csv", + "full_file_name": "fichier_import_1.csv", + "id_dataset": 1, + "id_destination": 1, + "id_import": 1, + "loaded": false, + "processed": true, + "separator": ";", + "source_count": 6, + "srid": 4326, + "statistics": { + "taxa_count": 2 + }, + "task_id": null, + "task_progress": null, + "uuid_autogenerated": null + }, + { + "altitude_autogenerated": null, + "authors": [ + { + "nom_complet": "User Test - 2" + } + ], + "authors_name": "User Test - 2", + "available_encodings": ["iso-8859-1", "iso-8859-15", "utf-8"], + "available_formats": ["csv", "geojson"], + "available_separators": [",", ";"], + "columns": [ + "error", + "id_synthese", + "id_origine", + "comment_releve", + "comment_occurrence", + "date_debut", + "date_fin", + "heure_debut", + "heure_fin", + "cd_nom", + "cd_ref", + "nom_valide", + "nom_vernaculaire", + "nom_cite", + "regne", + "group1_inpn", + "group2_inpn", + "classe", + "ordre", + "famille", + "rang_taxo", + "nombre_min", + "nombre_max", + "alti_min", + "alti_max", + "prof_min", + "prof_max", + "observateurs", + "determinateur", + "communes", + "geometrie_wkt_4326", + "x_centroid_4326", + "y_centroid_4326", + "nom_lieu", + "validateur", + "niveau_validation", + "date_validation", + "comment_validation", + "preuve_numerique_url", + "preuve_non_numerique", + "jdd_nom", + "jdd_uuid", + "jdd_id", + "ca_nom", + "ca_uuid", + "ca_id", + "cd_habref", + "cd_habitat", + "nom_habitat", + "precision_geographique", + "nature_objet_geo", + "type_regroupement", + "methode_regroupement", + "technique_observation", + "biologique_statut", + "etat_biologique", + "biogeographique_statut", + "naturalite", + "preuve_existante", + "niveau_precision_diffusion", + "stade_vie", + "sexe", + "objet_denombrement", + "type_denombrement", + "niveau_sensibilite", + "statut_observation", + "floutage_dee", + "statut_source", + "type_info_geo", + "methode_determination", + "comportement", + "reference_biblio", + "uuid_perm_sinp", + "uuid_perm_grp_sinp", + "date_creation", + "date_modification" + ], + "contentmapping": { + "DEE_FLOU": { + "Non": "NON" + }, + "ETA_BIO": { + "Non renseign\u00e9": "1" + }, + "METH_DETERMIN": { + "Autre m\u00e9thode de d\u00e9termination": "2" + }, + "METH_OBS": { + "Galerie/terrier": "23" + }, + "NATURALITE": { + "Sauvage": "1" + }, + "NAT_OBJ_GEO": { + "Inventoriel": "In" + }, + "NIV_PRECIS": { + "Pr\u00e9cise": "5" + }, + "OBJ_DENBR": { + "Individu": "IND" + }, + "OCC_COMPORTEMENT": { + "Non renseign\u00e9": "1" + }, + "PREUVE_EXIST": { + "Oui": "1" + }, + "SENSIBILITE": { + "Non sensible - Diffusion pr\u00e9cise": "0" + }, + "SEXE": { + "Femelle": "2" + }, + "STADE_VIE": { + "Adulte": "2", + "Immature": "4", + "Juv\u00e9nile": "3" + }, + "STATUT_BIO": { + "Non renseign\u00e9": "1" + }, + "STATUT_OBS": { + "Pr\u00e9sent": "Pr" + }, + "STATUT_SOURCE": { + "Terrain": "Te" + }, + "STATUT_VALID": { + "En attente de validation": "0" + }, + "STAT_BIOGEO": { + "Non renseign\u00e9": "1" + }, + "TYP_DENBR": { + "Compt\u00e9": "Co" + }, + "TYP_GRP": { + "OBS": "OBS" + } + }, + "cruved": { + "C": true, + "D": true, + "E": false, + "R": true, + "U": true, + "V": false + }, + "dataset": { + "active": true, + "dataset_name": "JDD - TEST FRONTEND" + }, + "date_create_import": "2024-05-13 23:55:51.167003", + "date_end_import": "2024-05-13 23:56:46.793605", + "date_max_data": null, + "date_min_data": null, + "date_update_import": "2024-05-13 23:56:46.796963", + "destination": { + "code": "destination", + "label": "Destination", + "statistics_labels": [ + { + "key": "taxa_count", + "value": "Nombre de taxons import\u00e9s" + } + ] + }, + "detected_encoding": "utf-8", + "detected_format": "csv", + "detected_separator": ";", + "encoding": "utf-8", + "errors_count": 3, + "fieldmapping": { + "WKT": "geometrie_wkt_4326", + "altitude_max": "alti_max", + "altitude_min": "alti_min", + "cd_hab": "cd_habref", + "cd_nom": "cd_nom", + "comment_context": "comment_releve", + "comment_description": "comment_occurrence", + "count_max": "nombre_max", + "count_min": "nombre_min", + "date_max": "date_fin", + "date_min": "date_debut", + "depth_max": "prof_max", + "depth_min": "prof_min", + "determiner": "determinateur", + "digital_proof": "preuve_numerique_url", + "entity_source_pk_value": "id_synthese", + "grp_method": "methode_regroupement", + "hour_max": "heure_fin", + "hour_min": "heure_debut", + "id_nomenclature_behaviour": "comportement", + "id_nomenclature_bio_condition": "etat_biologique", + "id_nomenclature_bio_status": "biologique_statut", + "id_nomenclature_biogeo_status": "biogeographique_statut", + "id_nomenclature_blurring": "floutage_dee", + "id_nomenclature_determination_method": "methode_determination", + "id_nomenclature_diffusion_level": "niveau_precision_diffusion", + "id_nomenclature_exist_proof": "preuve_existante", + "id_nomenclature_geo_object_nature": "nature_objet_geo", + "id_nomenclature_grp_typ": "type_regroupement", + "id_nomenclature_life_stage": "stade_vie", + "id_nomenclature_naturalness": "naturalite", + "id_nomenclature_obj_count": "objet_denombrement", + "id_nomenclature_obs_technique": "technique_observation", + "id_nomenclature_observation_status": "statut_observation", + "id_nomenclature_sensitivity": "niveau_sensibilite", + "id_nomenclature_sex": "sexe", + "id_nomenclature_source_status": "statut_source", + "id_nomenclature_type_count": "type_denombrement", + "id_nomenclature_valid_status": "niveau_validation", + "meta_create_date": "date_creation", + "meta_update_date": "date_modification", + "meta_validation_date": "date_validation", + "nom_cite": "nom_cite", + "non_digital_proof": "preuve_non_numerique", + "observers": "observateurs", + "place_name": "nom_lieu", + "precision": "precision_geographique", + "reference_biblio": "reference_biblio", + "unique_id_sinp": "uuid_perm_sinp", + "unique_id_sinp_grp": "uuid_perm_grp_sinp", + "validation_comment": "comment_validation", + "validator": "validateur" + }, + "format_source_file": "csv", + "full_file_name": "fichier_import_2.csv", + "id_dataset": 2, + "id_destination": 2, + "id_import": 2, + "loaded": false, + "processed": true, + "separator": ";", + "source_count": 6, + "srid": 4326, + "statistics": { + "taxa_count": 2 + }, + "task_id": null, + "task_progress": null, + "uuid_autogenerated": null + } + ], + "limit": 15, + "offset": 0 +} diff --git a/frontend/cypress/fixtures/import/synthese/report.pdf b/frontend/cypress/fixtures/import/synthese/report.pdf new file mode 100644 index 0000000000..a85a34b6ef Binary files /dev/null and b/frontend/cypress/fixtures/import/synthese/report.pdf differ diff --git a/frontend/cypress/fixtures/import/synthese/valid_file_import_synthese_test_changed.csv b/frontend/cypress/fixtures/import/synthese/valid_file_import_synthese_test_changed.csv new file mode 100644 index 0000000000..1c12d057f1 --- /dev/null +++ b/frontend/cypress/fixtures/import/synthese/valid_file_import_synthese_test_changed.csv @@ -0,0 +1,7 @@ +error;id_synthese;id_origine;comment_releve;comment_occurrence;date_debut;date_fin;heure_debut;heure_fin;cd_nom;cd_ref;nom_valide;nom_vernaculaire;nom_cite;regne;group1_inpn;group2_inpn;classe;ordre;famille;rang_taxo;nombre_min;nombre_max;alti_min;alti_max;prof_min;prof_max;observateurs;determinateur;communes;geometrie_wkt_4326;x_centroid_4326;y_centroid_4326;nom_lieu;validateur;niveau_validation;date_validation;comment_validation;preuve_numerique_url;preuve_non_numerique;jdd_nom;jdd_uuid;jdd_id;ca_nom;ca_uuid;ca_id;cd_habref;cd_habitat;nom_habitat;precision_geographique;nature_objet_geo;type_regroupement;methode_regroupement;technique_observation;biologique_statut;etat_biologique;biogeographique_statut;naturalite;preuve_existante;niveau_precision_diffusion;stade_vie;sexe;objet_denombrement;type_denombrement;niveau_sensibilite;statut_observation;floutage_dee;statut_source;type_info_geo;methode_determination;comportement;reference_biblio;uuid_perm_sinp;uuid_perm_grp_sinp;date_creation;date_modification +valid;1;1;Relevé n°1;Occurrence n°1;2017-01-01;2017-01-01;12:05:02;12:05:02;60612;60612;Lynx lynx (Linnaeus, 1758);;Lynx Boréal;Animalia;Chordés;Mammifères;Mammalia;Carnivora;Felidae;ES;5;5;1500;1565;;;Administrateur test;Gil;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poil;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;10;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Adulte;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;b4f85a2e-dd88-4cdd-aa86-f1c7370faf3f;5b427c76-bd8c-4103-a33c-884c7037aa2b;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +valid;2;2;Relevé n°2;Occurrence n°2;2017-01-01;2017-01-02;12:05:02;12:05:02;351;351;Rana temporaria Linnaeus, 1758;Grenouille rousse (La);Grenouille rousse;Animalia;Chordés;Amphibiens;Amphibia;Anura;Ranidae;ES;1;1;1500;1565;;;Administrateur test;Théo;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;10;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Immature;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;830c93c7-288e-40f0-a17f-15fbb50e643a;5b427c76-bd8c-4103-a33c-884c7037aa2b;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +duplicate id;3;3;Relevé n°3;Occurrence n°3;2017-01-08;;;;67111;67111;Alburnus alburnus (Linnaeus, 1758);Ablette;Ablette;Animalia;Chordés;Poissons;Actinopterygii;Cypriniformes;Leuciscidae;ES;1;1;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;2f92f91a-64a2-4684-90e4-140466bb34e3;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +duplicate id;3;4;Relevé n°4;Occurrence n°4;2017-01-08;2017-01-08;20:00:00;23:00:00;67111;67111;Alburnus alburnus (Linnaeus, 1758);Ablette;Ablette;Animalia;Chordés;Poissons;Actinopterygii;Cypriniformes;Leuciscidae;ES;1;1;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;2f92f91a-64a2-4684-90e4-140466bb34e4;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +count min > count max;5;5;Relevé n°5;Occurrence n°5;2017-01-08;2017/01/08;20:00;23:00:00;67111;67111;Alburnus alburnus (Linnaeus, 1758);Ablette;Ablette;Animalia;Chordés;Poissons;Actinopterygii;Cypriniformes;Leuciscidae;ES;20;5;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;2f92f91a-64a2-4684-90e4-140466bb34e5;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +valid;6;6;Relevé n°6;Occurrence n°6;2017-01-01;2017-01-01;12:05:02;12:05:02;351;351;Rana temporaria Linnaeus, 1758;Grenouille rousse (La);Grenouille rousse;Animalia;Chordés;Amphibiens;Amphibia;Anura;Ranidae;ES;1;1;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;f5515e2a-b30d-11eb-8cc8-af8c2d0867b4;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 diff --git a/frontend/cypress/fixtures/import/synthese/valid_file_test_link_list_import_synthese.csv b/frontend/cypress/fixtures/import/synthese/valid_file_test_link_list_import_synthese.csv new file mode 100644 index 0000000000..1c12d057f1 --- /dev/null +++ b/frontend/cypress/fixtures/import/synthese/valid_file_test_link_list_import_synthese.csv @@ -0,0 +1,7 @@ +error;id_synthese;id_origine;comment_releve;comment_occurrence;date_debut;date_fin;heure_debut;heure_fin;cd_nom;cd_ref;nom_valide;nom_vernaculaire;nom_cite;regne;group1_inpn;group2_inpn;classe;ordre;famille;rang_taxo;nombre_min;nombre_max;alti_min;alti_max;prof_min;prof_max;observateurs;determinateur;communes;geometrie_wkt_4326;x_centroid_4326;y_centroid_4326;nom_lieu;validateur;niveau_validation;date_validation;comment_validation;preuve_numerique_url;preuve_non_numerique;jdd_nom;jdd_uuid;jdd_id;ca_nom;ca_uuid;ca_id;cd_habref;cd_habitat;nom_habitat;precision_geographique;nature_objet_geo;type_regroupement;methode_regroupement;technique_observation;biologique_statut;etat_biologique;biogeographique_statut;naturalite;preuve_existante;niveau_precision_diffusion;stade_vie;sexe;objet_denombrement;type_denombrement;niveau_sensibilite;statut_observation;floutage_dee;statut_source;type_info_geo;methode_determination;comportement;reference_biblio;uuid_perm_sinp;uuid_perm_grp_sinp;date_creation;date_modification +valid;1;1;Relevé n°1;Occurrence n°1;2017-01-01;2017-01-01;12:05:02;12:05:02;60612;60612;Lynx lynx (Linnaeus, 1758);;Lynx Boréal;Animalia;Chordés;Mammifères;Mammalia;Carnivora;Felidae;ES;5;5;1500;1565;;;Administrateur test;Gil;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poil;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;10;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Adulte;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;b4f85a2e-dd88-4cdd-aa86-f1c7370faf3f;5b427c76-bd8c-4103-a33c-884c7037aa2b;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +valid;2;2;Relevé n°2;Occurrence n°2;2017-01-01;2017-01-02;12:05:02;12:05:02;351;351;Rana temporaria Linnaeus, 1758;Grenouille rousse (La);Grenouille rousse;Animalia;Chordés;Amphibiens;Amphibia;Anura;Ranidae;ES;1;1;1500;1565;;;Administrateur test;Théo;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;10;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Immature;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;830c93c7-288e-40f0-a17f-15fbb50e643a;5b427c76-bd8c-4103-a33c-884c7037aa2b;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +duplicate id;3;3;Relevé n°3;Occurrence n°3;2017-01-08;;;;67111;67111;Alburnus alburnus (Linnaeus, 1758);Ablette;Ablette;Animalia;Chordés;Poissons;Actinopterygii;Cypriniformes;Leuciscidae;ES;1;1;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;2f92f91a-64a2-4684-90e4-140466bb34e3;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +duplicate id;3;4;Relevé n°4;Occurrence n°4;2017-01-08;2017-01-08;20:00:00;23:00:00;67111;67111;Alburnus alburnus (Linnaeus, 1758);Ablette;Ablette;Animalia;Chordés;Poissons;Actinopterygii;Cypriniformes;Leuciscidae;ES;1;1;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;2f92f91a-64a2-4684-90e4-140466bb34e4;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +count min > count max;5;5;Relevé n°5;Occurrence n°5;2017-01-08;2017/01/08;20:00;23:00:00;67111;67111;Alburnus alburnus (Linnaeus, 1758);Ablette;Ablette;Animalia;Chordés;Poissons;Actinopterygii;Cypriniformes;Leuciscidae;ES;20;5;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;2f92f91a-64a2-4684-90e4-140466bb34e5;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 +valid;6;6;Relevé n°6;Occurrence n°6;2017-01-01;2017-01-01;12:05:02;12:05:02;351;351;Rana temporaria Linnaeus, 1758;Grenouille rousse (La);Grenouille rousse;Animalia;Chordés;Amphibiens;Amphibia;Anura;Ranidae;ES;1;1;1600;1600;;;Administrateur test;Donovan;Vallouise-Pelvoux;POINT(6.5 44.85);6.5;44.85;;;En attente de validation;;;;Poils de plumes;Contact aléatoire tous règnes confondus;4d331cae-65e4-4948-b0b2-a11bc5bb46c2;1;Données d'observation de la faune, de la Flore et de la fonge du Parc national des Ecrins;57b7d0f2-4183-4b7b-8f08-6e105d476dc5;1;;;;100;Inventoriel;OBS;;Galerie/terrier;Non renseigné;Non renseigné;Non renseigné;Sauvage;Oui;Précise;Juvénile;Femelle;Individu;Compté;Non sensible - Diffusion précise;Présent;Non;Terrain;Géoréférencement;Autre méthode de détermination;Non renseigné;;f5515e2a-b30d-11eb-8cc8-af8c2d0867b4;5937d0f2-c96d-424b-bea4-9e3fdac894ed;2021-01-11 14:20:46.492497;2021-01-11 14:20:46.492497 diff --git a/frontend/cypress/plugins/index.js b/frontend/cypress/plugins/index.js index c85dae7f67..4db097f777 100644 --- a/frontend/cypress/plugins/index.js +++ b/frontend/cypress/plugins/index.js @@ -1,4 +1,28 @@ +const fs = require('fs'); +const path = require('path'); module.exports = (on, config) => { - config.env.apiEndpoint = process.env.API_ENDPOINT || 'http://127.0.0.1:8000/'; + config.env.apiEndpoint = process.env.API_ENDPOINT || 'http://localhost:8000/'; + config.env.urlApplication = process.env.URL_APPLICATION || 'http://127.0.0.1:4200/#/'; + + // Define the deleteFile task + on('task', { + deleteFile(filePath) { + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + return { success: true }; + } + return { success: false }; + }, + fileExists(filePath) { + return fs.existsSync(filePath); + }, + getLastDownloadFileName(downloadsDirPath) { + if (!fs.existsSync(downloadsDirPath)) { + return null; + } + const filenames = fs.readdirSync(downloadsDirPath); + return filenames[0]; + }, + }); return config; }; diff --git a/frontend/cypress/support/commands.js b/frontend/cypress/support/commands.js index fede1135e8..fcc563704e 100644 --- a/frontend/cypress/support/commands.js +++ b/frontend/cypress/support/commands.js @@ -1,19 +1,3 @@ +import './users'; -Cypress.Commands.add("geonatureLogin", () => { - cy.session("admin", () => { - cy.request({ - method: 'POST', - url: 'http://localhost:8000/auth/login', - body: { - login: "admin", - password: "admin" - } - }) - .its('body') - .then(body => { - window.localStorage.setItem("gn_expires_at", body.expires); - window.localStorage.setItem("gn_id_token", body.token); - window.localStorage.setItem('gn_current_user', JSON.stringify(body.user)); - }) - }) -}); +import './import'; diff --git a/frontend/cypress/support/import/backToImportList.js b/frontend/cypress/support/import/backToImportList.js new file mode 100644 index 0000000000..d223fafa97 --- /dev/null +++ b/frontend/cypress/support/import/backToImportList.js @@ -0,0 +1,7 @@ +Cypress.Commands.add('backToImportList', () => { + // Ignore an exception generated by bokeh not being able to find 'chatreport' + cy.on('uncaught:exception', (err, runnable) => { + return false; + }); + cy.get('[data-qa="import-report-back"]').click(); +}); diff --git a/frontend/cypress/support/import/checkCellValueExistsInColumn.js b/frontend/cypress/support/import/checkCellValueExistsInColumn.js new file mode 100644 index 0000000000..7aa307b600 --- /dev/null +++ b/frontend/cypress/support/import/checkCellValueExistsInColumn.js @@ -0,0 +1,25 @@ +Cypress.Commands.add('checkCellValueExistsInColumn', (tableSelector, columnName, cellValue) => { + let cellExists = false; + cy.get(`${tableSelector} datatable-body-row`) + .each(($row, rowIndex) => { + const cellSelector = `[data-qa="import-list-table-row-${rowIndex}-${columnName + .replace(/\s+/g, '-') + .toLowerCase()}"]`; + cy.log('cellSelector: ' + cellSelector); + cy.log('cellValue: ' + cellValue); + cy.wrap($row) + .find(cellSelector) + .then(($cell) => { + cy.log('cellValue: ' + cellValue); + cy.log('cellText: ' + $cell.text().trim()); + if ($cell.text().trim() === cellValue) { + cellExists = true; + return false; + } + }); + }) + .then(() => { + // Return the result + return cy.wrap(cellExists).as('cellValueExists'); + }); +}); diff --git a/frontend/cypress/support/import/checkCurrentPageIsImport.js b/frontend/cypress/support/import/checkCurrentPageIsImport.js new file mode 100644 index 0000000000..c2b53a597d --- /dev/null +++ b/frontend/cypress/support/import/checkCurrentPageIsImport.js @@ -0,0 +1,6 @@ +Cypress.Commands.add('checkCurrentPageIsImport', () => { + // Check the url + cy.url().should('be.equal', `${Cypress.config('baseUrl')}/#/import`); + // Check that the main component is there + cy.get('[data-qa=import-list]').should('exist'); +}); diff --git a/frontend/cypress/support/import/checkImportListSize.js b/frontend/cypress/support/import/checkImportListSize.js new file mode 100644 index 0000000000..7e4ec7963e --- /dev/null +++ b/frontend/cypress/support/import/checkImportListSize.js @@ -0,0 +1,5 @@ +Cypress.Commands.add('checkImportListSize', (expectedLength) => { + cy.get('[data-qa=import-list-table] datatable-body').within(() => { + cy.get('datatable-body-row').should('have.length', expectedLength); + }); +}); diff --git a/frontend/cypress/support/import/configureImportContentMapping.js b/frontend/cypress/support/import/configureImportContentMapping.js new file mode 100644 index 0000000000..f7caf3ea1c --- /dev/null +++ b/frontend/cypress/support/import/configureImportContentMapping.js @@ -0,0 +1,6 @@ +Cypress.Commands.add('configureImportContentMapping', () => { + cy.get('[data-qa="import-contentmapping-model-select"]') + .should('exist') + .select('Nomenclatures SINP (labels)'); + cy.get('[data-qa="import-contentmapping-model-validate"]').should('exist').click(); +}); diff --git a/frontend/cypress/support/import/configureImportFieldMapping.js b/frontend/cypress/support/import/configureImportFieldMapping.js new file mode 100644 index 0000000000..53335b251a --- /dev/null +++ b/frontend/cypress/support/import/configureImportFieldMapping.js @@ -0,0 +1,19 @@ +const DEFAULT_MAPPING = 'Synthese GeoNature'; + +Cypress.Commands.add('configureImportFieldMapping', () => { + cy.get('[data-qa="import-fieldmapping-selection-select"]') + .should('exist') + .click() + .get('ng-dropdown-panel') + .get('.ng-option') + .contains(DEFAULT_MAPPING) + .then((v) => { + cy.wrap(v).should('exist').click(); + }); + + // Every mandatory field is filled: should be able to validate + cy.get('[data-qa="import-new-fieldmapping-model-validate"]') + .should('exist') + .should('be.enabled') + .click(); +}); diff --git a/frontend/cypress/support/import/configureImportFile.js b/frontend/cypress/support/import/configureImportFile.js new file mode 100644 index 0000000000..fbfdf5805d --- /dev/null +++ b/frontend/cypress/support/import/configureImportFile.js @@ -0,0 +1,7 @@ +const DEFAULT_SRID = 'WGS84'; + +Cypress.Commands.add('configureImportFile', (srid) => { + srid = srid ?? DEFAULT_SRID; + cy.get('[data-qa="import-new-decode-srid"]').select(DEFAULT_SRID); + cy.get('[data-qa="import-new-decode-validate"]').should('exist').click(); +}); diff --git a/frontend/cypress/support/import/deleteCurrentImport.js b/frontend/cypress/support/import/deleteCurrentImport.js new file mode 100644 index 0000000000..a8efde5a34 --- /dev/null +++ b/frontend/cypress/support/import/deleteCurrentImport.js @@ -0,0 +1,10 @@ +Cypress.Commands.add('deleteCurrentImport', () => { + cy.url().then((url) => { + // Extract the ID using string manipulation + const parts = url.split('/'); + const importID = parts[parts.length - 2]; // Get the penultimate element + const destination = parts[parts.length - 4]; + cy.deleteImport(importID, destination); + cy.visitImport(); + }); +}); diff --git a/frontend/cypress/support/import/deleteFile.js b/frontend/cypress/support/import/deleteFile.js new file mode 100644 index 0000000000..c5c949ea7c --- /dev/null +++ b/frontend/cypress/support/import/deleteFile.js @@ -0,0 +1,4 @@ +Cypress.Commands.add('deleteFile', (fileName, downloadFolder) => { + const filePath = `${downloadFolder}/${fileName}`; + cy.task('deleteFile', filePath); +}); diff --git a/frontend/cypress/support/import/deleteFileIfExist.js b/frontend/cypress/support/import/deleteFileIfExist.js new file mode 100644 index 0000000000..2f4765daab --- /dev/null +++ b/frontend/cypress/support/import/deleteFileIfExist.js @@ -0,0 +1,13 @@ +Cypress.Commands.add('deleteFileIfExist', (fileName, downloadFolder) => { + const filePath = `${downloadFolder}/${fileName}`; + cy.task('fileExists', filePath).then((exists) => { + if (exists) { + cy.task('deleteFile', filePath).then((result) => { + expect(result.success).to.be.true; + cy.log(`File ${fileName} deleted: ${result.success}`); + }); + } else { + cy.log(`File ${fileName} does not exist.`); + } + }); +}); diff --git a/frontend/cypress/support/import/deleteImport.js b/frontend/cypress/support/import/deleteImport.js new file mode 100644 index 0000000000..f936b8c604 --- /dev/null +++ b/frontend/cypress/support/import/deleteImport.js @@ -0,0 +1,4 @@ +Cypress.Commands.add('deleteImport', (importID, destination) => { + // Log the extracted ID + cy.request('DELETE', `${Cypress.env('apiEndpoint')}import/${destination}/imports/${importID}/`); +}); diff --git a/frontend/cypress/support/import/executeImport.js b/frontend/cypress/support/import/executeImport.js new file mode 100644 index 0000000000..5c52266d9c --- /dev/null +++ b/frontend/cypress/support/import/executeImport.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('executeImport', () => { + cy.get('[data-qa="import-new-execute-start"]').should('exist').click(); +}); diff --git a/frontend/cypress/support/import/geonatureInitImportList.js b/frontend/cypress/support/import/geonatureInitImportList.js new file mode 100644 index 0000000000..ba08388516 --- /dev/null +++ b/frontend/cypress/support/import/geonatureInitImportList.js @@ -0,0 +1,4 @@ +Cypress.Commands.add('geonatureInitImportList', () => { + cy.geonatureLogin(); + cy.visit('/#/import'); +}); diff --git a/frontend/cypress/support/import/getCellValueByColumnName.js b/frontend/cypress/support/import/getCellValueByColumnName.js new file mode 100644 index 0000000000..49f76163aa --- /dev/null +++ b/frontend/cypress/support/import/getCellValueByColumnName.js @@ -0,0 +1,19 @@ +Cypress.Commands.add('getCellValueByColumnName', (tableSelector, columnName, cellValue) => { + cy.get(`${tableSelector} datatable-body-row`).each(($row, rowIndex) => { + const cellSelector = `[data-qa="import-list-table-row-${rowIndex}-${columnName + .replace(/\s+/g, '-') + .toLowerCase()}"]`; + cy.wrap($row) + .find(cellSelector) + .then(($cell) => { + if ($cell.text().trim() === cellValue) { + cy.wrap($cell) + .find('a') + .should('exist') + .then(($link) => { + cy.wrap($link).as('targetLink'); + }); + } + }); + }); +}); diff --git a/frontend/cypress/support/import/getGlobalConfig.js b/frontend/cypress/support/import/getGlobalConfig.js new file mode 100644 index 0000000000..394ff17129 --- /dev/null +++ b/frontend/cypress/support/import/getGlobalConfig.js @@ -0,0 +1,23 @@ +Cypress.Commands.add('getGlobalConfig', () => { + return cy + .request('GET', Cypress.env('apiEndpoint') + 'gn_commons/config') + .its('body.IMPORT.LIST_COLUMNS_FRONTEND') + .then((columnsImport) => { + const columnNames = [ + 'Id Import', + 'Fichier', + 'Auteur', + 'Debut import', + 'Destination', + 'Fin import', + ]; + const columns = columnsImport + .filter((column) => columnNames.includes(column.name)) + .map((column) => ({ + name: column.name, + sortable: column.filter, + })); + return columns; + }) + .as('globalColumnsConfig'); +}); diff --git a/frontend/cypress/support/import/getRowIndexByCellValue.js b/frontend/cypress/support/import/getRowIndexByCellValue.js new file mode 100644 index 0000000000..2b549ddc18 --- /dev/null +++ b/frontend/cypress/support/import/getRowIndexByCellValue.js @@ -0,0 +1,21 @@ +Cypress.Commands.add('getRowIndexByCellValue', (tableSelector, columnName, cellValue) => { + // Define a new Cypress command to get the row index by cell value + return cy + .get(`${tableSelector} datatable-body-row`) + .each(($row, rowIndex) => { + const cellSelector = `[data-qa="import-list-table-row-${rowIndex}-${columnName + .replace(/\s+/g, '-') + .toLowerCase()}"]`; + cy.wrap($row) + .find(cellSelector) + .then(($cell) => { + if ($cell.text().trim() === cellValue) { + cy.wrap(rowIndex).as('rowIndex'); + } + }); + }) + .then(() => { + // Return the aliased row index value + return cy.get('@rowIndex'); + }); +}); diff --git a/frontend/cypress/support/import/hasToastError.js b/frontend/cypress/support/import/hasToastError.js new file mode 100644 index 0000000000..cbfd559892 --- /dev/null +++ b/frontend/cypress/support/import/hasToastError.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('hasToastError', (error_msg) => { + cy.get('#toast-container .toast-error .toast-message').should('be.visible').contains(error_msg); +}); diff --git a/frontend/cypress/support/import/index.js b/frontend/cypress/support/import/index.js new file mode 100644 index 0000000000..aac0a74e1e --- /dev/null +++ b/frontend/cypress/support/import/index.js @@ -0,0 +1,27 @@ +import './backToImportList'; +import './checkCellValueExistsInColumn'; +import './checkCurrentPageIsImport'; +import './checkImportListSize'; +import './configureImportContentMapping'; +import './configureImportFieldMapping'; +import './configureImportFile'; +import './deleteCurrentImport'; +import './deleteFile'; +import './deleteFileIfExist'; +import './deleteImport'; +import './deleteCurrentImport'; +import './executeImport'; +import './geonatureInitImportList'; +import './getCellValueByColumnName'; +import './getGlobalConfig'; +import './getRowIndexByCellValue'; +import './hasToastError'; +import './loadImportFile'; +import './pickDataset'; +import './pickDestination'; +import './removeFirstImportInList'; +import './startImport'; +import './triggerImportVerification'; +import './verifyDownload'; +import './verifyImport'; +import './visitImport'; diff --git a/frontend/cypress/support/import/loadImportFile.js b/frontend/cypress/support/import/loadImportFile.js new file mode 100644 index 0000000000..6ad7cd239f --- /dev/null +++ b/frontend/cypress/support/import/loadImportFile.js @@ -0,0 +1,10 @@ +Cypress.Commands.add('loadImportFile', (fixture, nextStep = true) => { + cy.fixture(fixture).as('import_file'); + cy.get('[data-qa="import-new-upload-file"]').selectFile( + { contents: '@import_file', fileName: fixture.split(/(\\|\/)/g).pop() }, + { force: true } + ); + if (nextStep) { + cy.get('[data-qa="import-new-upload-validate"]').should('be.visible').click(); + } +}); diff --git a/frontend/cypress/support/import/pickDataset.js b/frontend/cypress/support/import/pickDataset.js new file mode 100644 index 0000000000..33ac7265a2 --- /dev/null +++ b/frontend/cypress/support/import/pickDataset.js @@ -0,0 +1,11 @@ +Cypress.Commands.add('pickDataset', (datasetName) => { + cy.get('[data-qa="import-new-upload-datasets"]') + .should('exist') + .click() + .get('ng-dropdown-panel') + .get('.ng-option') + .contains(datasetName) + .then((dataset) => { + cy.wrap(dataset).should('exist').click(); + }); +}); diff --git a/frontend/cypress/support/import/pickDestination.js b/frontend/cypress/support/import/pickDestination.js new file mode 100644 index 0000000000..289cbcc46f --- /dev/null +++ b/frontend/cypress/support/import/pickDestination.js @@ -0,0 +1,16 @@ +const DEFAULT_DESTINATION_NAME = 'Synthèse'; +Cypress.Commands.add('pickDestination', (destinationName) => { + destinationName = destinationName ?? DEFAULT_DESTINATION_NAME; + cy.get('[data-qa=import-new-modal-destinations]').within(() => { + cy.get('.ng-arrow-wrapper') + .should('exist') + .click({ force: true }) + .get('ng-dropdown-panel') + .get('.ng-option') + .contains(destinationName) + .then((destination) => { + cy.wrap(destination).should('exist').click({ force: true }); + }); + }); + cy.get('[data-qa=import-modal-destination-validate]').should('exist').click({ force: true }); +}); diff --git a/frontend/cypress/support/import/removeFirstImportInList.js b/frontend/cypress/support/import/removeFirstImportInList.js new file mode 100644 index 0000000000..e88dbbbd6d --- /dev/null +++ b/frontend/cypress/support/import/removeFirstImportInList.js @@ -0,0 +1,4 @@ +Cypress.Commands.add('removeFirstImportInList', () => { + cy.get('[data-qa="import-list-table-row-0-actions-delete"]').should('exist').click(); + cy.get('[data-qa="modal-delete-validate"]').should('exist').click(); +}); diff --git a/frontend/cypress/support/import/startImport.js b/frontend/cypress/support/import/startImport.js new file mode 100644 index 0000000000..8bd57d0286 --- /dev/null +++ b/frontend/cypress/support/import/startImport.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('startImport', (expectedLength) => { + cy.get('[data-qa="import-modal-destination-start"]').should('exist').click(); +}); diff --git a/frontend/cypress/support/import/triggerImportVerification.js b/frontend/cypress/support/import/triggerImportVerification.js new file mode 100644 index 0000000000..a6caf32f4a --- /dev/null +++ b/frontend/cypress/support/import/triggerImportVerification.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('triggerImportVerification', () => { + cy.get('[data-qa=import-new-verification-start]').should('exist').should('be.enabled').click(); +}); diff --git a/frontend/cypress/support/import/verifyDownload.js b/frontend/cypress/support/import/verifyDownload.js new file mode 100644 index 0000000000..e5192f0186 --- /dev/null +++ b/frontend/cypress/support/import/verifyDownload.js @@ -0,0 +1,8 @@ +Cypress.Commands.add('verifyDownload', (fileName, downloadFolder) => { + const filePath = `${downloadFolder}/${fileName}`; + + // Check if the file exists and is not empty + cy.readFile(filePath, { timeout: 15000 }).then((fileContent) => { + expect(fileContent).to.not.be.empty; + }); +}); diff --git a/frontend/cypress/support/import/verifyImport.js b/frontend/cypress/support/import/verifyImport.js new file mode 100644 index 0000000000..464fe624e8 --- /dev/null +++ b/frontend/cypress/support/import/verifyImport.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('verifyImport', () => { + cy.get('[data-qa="import-new-verification-start"]').should('exist').click(); +}); diff --git a/frontend/cypress/support/import/visitImport.js b/frontend/cypress/support/import/visitImport.js new file mode 100644 index 0000000000..6571ed7191 --- /dev/null +++ b/frontend/cypress/support/import/visitImport.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('visitImport', () => { + cy.visit('/#/import'); +}); diff --git a/frontend/cypress/support/index.ts b/frontend/cypress/support/index.ts deleted file mode 100644 index 54fbeaec80..0000000000 --- a/frontend/cypress/support/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// When a command from ./commands is ready to use, import with `import './commands'` syntax -import './commands'; diff --git a/frontend/cypress/support/users/geonatureLogin.js b/frontend/cypress/support/users/geonatureLogin.js new file mode 100644 index 0000000000..94f8999d8c --- /dev/null +++ b/frontend/cypress/support/users/geonatureLogin.js @@ -0,0 +1,27 @@ +const DEFAULT_LOGIN = { + username: 'admin', + password: 'admin', +}; + +Cypress.Commands.add('geonatureLogin', (username, password) => { + if (!username || !password) { + ({ username, password } = DEFAULT_LOGIN); + } + cy.session([username, password], () => { + cy.request({ + method: 'POST', + url: `${Cypress.env('apiEndpoint')}auth/login`, + body: { + login: username, + password: password, + }, + }) + .its('body') + .then((body) => { + window.localStorage.setItem('gn_expires_at', body.expires); + window.localStorage.setItem('gn_id_token', body.token); + window.localStorage.setItem('gn_current_user', JSON.stringify(body.user)); + }); + cy.log('Logged with: ' + username); + }); +}); diff --git a/frontend/cypress/support/users/geonatureLogout.js b/frontend/cypress/support/users/geonatureLogout.js new file mode 100644 index 0000000000..4900b95b52 --- /dev/null +++ b/frontend/cypress/support/users/geonatureLogout.js @@ -0,0 +1,6 @@ +Cypress.Commands.add('geonatureLogout', () => { + cy.request({ + method: 'GET', + url: `${Cypress.env('apiEndpoint')}auth/logout`, + }); +}); diff --git a/frontend/cypress/support/users/index.js b/frontend/cypress/support/users/index.js new file mode 100644 index 0000000000..65e23ae0dd --- /dev/null +++ b/frontend/cypress/support/users/index.js @@ -0,0 +1,2 @@ +import './geonatureLogin'; +import './geonatureLogout'; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9da0cc7bf6..3775c87892 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,6 +22,7 @@ "@angular/platform-browser": "15.1.1", "@angular/platform-browser-dynamic": "15.1.1", "@angular/router": "15.1.1", + "@bokeh/bokehjs": "3.4.1", "@circlon/angular-tree-component": "^11.0.2", "@ng-bootstrap/ng-bootstrap": "14.0.1", "@ng-select/ng-select": "^10.0.3", @@ -39,12 +40,15 @@ "chartjs-plugin-datalabels": "^2.2.0", "core-js": "^3.19.1", "fast-deep-equal": "^3.1.3", + "file-saver": "^2.0.5", "font-awesome": "^4.7.0", "leaflet": "~1.7.1", "leaflet-draw": "^1.0.4", + "leaflet-image": "^0.4.0", "leaflet.locatecontrol": "^0.79.0", "leaflet.markercluster": "^1.5.3", "lodash": "^4.17.21", + "material-design-icons": "^3.0.1", "moment": "^2.29.4", "ng2-charts": "^4.1.1", "ng2-cookies": "^1.0.12", @@ -57,6 +61,7 @@ "devDependencies": { "@angular-devkit/core": "^15.1.2", "@angular/compiler-cli": "15.1.1", + "@compodoc/compodoc": "^1.1.22", "@types/node": "^18.0.0", "cypress": "^13.4.0", "cypress-promise": "^1.1.0", @@ -64,10 +69,18 @@ "typescript": "4.9.4" } }, + "node_modules/@aduh95/viz.js": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@aduh95/viz.js/-/viz.js-3.4.0.tgz", + "integrity": "sha512-KI2nVf9JdwWCXqK6RVf+9/096G7VWN4Z84mnynlyZKao2xQENW8WNEjLmvdlxS5X8PNWXFC1zqwm7tveOXw/4A==", + "dev": true, + "license": "MIT" + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.1.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -80,6 +93,7 @@ "version": "0.1502.11", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1502.11.tgz", "integrity": "sha512-+hkG5UjIaKMRdo6SFLNQs+Cv7yAVeN8ijfDwI2z/mp7/otowuSEy+H3Tii195jfJ8TQ+y1B7svnx2D6O7oOYbQ==", + "license": "MIT", "dependencies": { "@angular-devkit/core": "15.2.11", "rxjs": "6.6.7" @@ -94,6 +108,7 @@ "version": "15.2.11", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-15.2.11.tgz", "integrity": "sha512-MnpVCJdk5jHuK7CH/cTcRT0JQkkKkRTEV3WTyOUhTm0O3PlKwvTM6/Sner+zyuhKyw5VFBBMypHh59aTUDEZ1A==", + "license": "MIT", "dependencies": { "@ampproject/remapping": "2.2.0", "@angular-devkit/architect": "0.1502.11", @@ -202,12 +217,14 @@ "node_modules/@angular-devkit/build-angular/node_modules/tslib": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "license": "0BSD" }, "node_modules/@angular-devkit/build-webpack": { "version": "0.1502.11", "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1502.11.tgz", "integrity": "sha512-OTONIRp770Jfems4+cULmtoeSzjnpx5UjV2EazojnhRXXBSJMWRMPvwD2QvQl9UO/6eOV3d2mgmP2xOZgc/D6w==", + "license": "MIT", "dependencies": { "@angular-devkit/architect": "0.1502.11", "rxjs": "6.6.7" @@ -226,6 +243,7 @@ "version": "15.2.11", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.2.11.tgz", "integrity": "sha512-zd6QelJ8pOPvz6TsehR0JqixjDjzgEOkKywBJBuwNXY+Nw3MJGayJeWS0UgC+Gk+LoTkpI21RoyaYELkAmD/tw==", + "license": "MIT", "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", @@ -251,6 +269,7 @@ "version": "15.1.6", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.1.6.tgz", "integrity": "sha512-cwmJFpS43zrdlmfwfHIxG/Nzg5rzFdtKrHx64ZXxNFm6JdyK2JTs/qrHUwv1FYWAcqhdiHn+00jYklMmvsvPOA==", + "license": "MIT", "dependencies": { "@angular-devkit/core": "15.1.6", "jsonc-parser": "3.2.0", @@ -268,6 +287,7 @@ "version": "15.1.6", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.1.6.tgz", "integrity": "sha512-jGgxyRjecVf6lEyqDxz7ltMEndNPxIg720pk6r40fgsu0dU8w9vjJSJe7k0XdJiXVRcN6wZa/J5nO/xcwWVIsA==", + "license": "MIT", "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", @@ -293,6 +313,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.13" }, @@ -304,6 +325,7 @@ "version": "15.1.1", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-15.1.1.tgz", "integrity": "sha512-oQrbO7uDsw1VcqhuSqvwQQPxEUjmssMM3nDbFUrs6A0MX7XIuhGnkB7mN35M6ZSd0Chj9DMzgbrYToPg1LoLHQ==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -315,14 +337,16 @@ } }, "node_modules/@angular/animations/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@angular/cdk": { "version": "15.2.9", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-15.2.9.tgz", "integrity": "sha512-koaM07N1AIQ5oHU27l0/FoQSSoYAwlAYwVZ4Di3bYrJsTBNCN2Xsby7wI8gZxdepMnV4Fe9si382BDBov+oO4Q==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -336,14 +360,16 @@ } }, "node_modules/@angular/cdk/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@angular/cli": { "version": "15.1.6", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-15.1.6.tgz", "integrity": "sha512-GmC9jZK2ipUWj0dlfTI5oEYia4y1fLfO3AtAKU5CylNYrGyB+DRytKY8Bx6Fs4kaNBY8V8YnyLi7E/78gziMdg==", + "license": "MIT", "dependencies": { "@angular-devkit/architect": "0.1501.6", "@angular-devkit/core": "15.1.6", @@ -377,6 +403,7 @@ "version": "0.1501.6", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1501.6.tgz", "integrity": "sha512-u07zZFlfrg0Qn4mu5M9Nz0pH2Yd2028XF/73980PsZMxwkSm4diF08v4bHk3UyR7yPT7phwvt4znj6ryZhx1gw==", + "license": "MIT", "dependencies": { "@angular-devkit/core": "15.1.6", "rxjs": "6.6.7" @@ -391,6 +418,7 @@ "version": "15.1.6", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.1.6.tgz", "integrity": "sha512-jGgxyRjecVf6lEyqDxz7ltMEndNPxIg720pk6r40fgsu0dU8w9vjJSJe7k0XdJiXVRcN6wZa/J5nO/xcwWVIsA==", + "license": "MIT", "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", @@ -416,6 +444,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -427,6 +456,7 @@ "version": "8.4.0", "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -443,6 +473,7 @@ "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -456,12 +487,14 @@ "node_modules/@angular/cli/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/@angular/common": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/@angular/common/-/common-15.1.1.tgz", "integrity": "sha512-P4f2rK/YBQQT4clQrcQ9goLf53RS63Q1x2d1dvz7Syr3gcOUyxO7NZQ7Au5afzvbIKZ5Okd+X0+TCGV4q9wQjw==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -474,14 +507,16 @@ } }, "node_modules/@angular/common/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@angular/compiler": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-15.1.1.tgz", "integrity": "sha512-A35iXLTTDEej4F2tm5t1flA+5Tv+jYAkQx+d0xvH6LDiWvsiDsOe5OjP8L2LD8dejwWl/JYUz2TH0JZcvw0uqA==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -501,6 +536,7 @@ "version": "15.1.1", "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-15.1.1.tgz", "integrity": "sha512-sBYvh6Y08aKuZPAVwzT1YGVfG63xeXElEfAfq6No9FLUHDo83QB9fU7ovXnu1RJFJiVIy16DHjmTpAmgvlomGA==", + "license": "MIT", "dependencies": { "@babel/core": "7.19.3", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -530,6 +566,7 @@ "version": "7.19.3", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -559,6 +596,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -567,6 +605,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.13" }, @@ -575,19 +614,22 @@ } }, "node_modules/@angular/compiler-cli/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@angular/compiler/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@angular/core": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/@angular/core/-/core-15.1.1.tgz", "integrity": "sha512-nsz+IXdkQanAGhqA2vcwTrGGyw5zIm3TgtYQ/JqK185qkmlhsQfSRvdSPsFPhmIFYp9ngZVUbkhY3D4P3gDzIg==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -600,14 +642,16 @@ } }, "node_modules/@angular/core/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@angular/forms": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-15.1.1.tgz", "integrity": "sha512-1c/oZD+eSbGzDCcmC+hOqkPH+AzLOiGHxII5QiOOj8so8M958UBrsFBwrnLz06d9OK5Z4IrR4UL2JJspZNbpjw==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -622,14 +666,16 @@ } }, "node_modules/@angular/forms/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@angular/localize": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-15.1.1.tgz", "integrity": "sha512-gOFVOq3yBx5/Ng/Og9Hy+DDM2SugrUFAqf2KYR/Qvrp3e6lRU6ScFzJbk4NKPttwV77H3eYoKrfdJXMLpTib7g==", + "license": "MIT", "dependencies": { "@babel/core": "7.19.3", "glob": "8.1.0", @@ -652,6 +698,7 @@ "version": "7.19.3", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -681,6 +728,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -689,6 +737,7 @@ "version": "15.2.9", "resolved": "https://registry.npmjs.org/@angular/material/-/material-15.2.9.tgz", "integrity": "sha512-emuFF/7+91Jq+6kVCl3FiVoFLtAZoh+woFQWNuK8nhx0HmD4ckLFI8d9a6ERYR3zRuKhq5deSRE2kYsfpjrrsQ==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/auto-init": "15.0.0-canary.684e33d25.0", @@ -750,14 +799,16 @@ } }, "node_modules/@angular/material/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@angular/platform-browser": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-15.1.1.tgz", "integrity": "sha512-U1MXowvyigmMpffsnMQI6e9gX7tA6Su7unY0pjN5u9wRYJIkEbKuFyVijguQ83bt+JK3ZBHXD0dvskYnscGUzg==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -779,6 +830,7 @@ "version": "15.1.1", "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-15.1.1.tgz", "integrity": "sha512-iCbvYduSGtyBWuQfTzm/MDZy7Rd5MNsjpknEtJ45nbjZzv9EL1tGo6qufaTgPXHP8Nbfcco/4UO9w84Gp5irLA==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -793,19 +845,22 @@ } }, "node_modules/@angular/platform-browser-dynamic/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@angular/platform-browser/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@angular/router": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/@angular/router/-/router-15.1.1.tgz", "integrity": "sha512-5l1+6MzeJW01BeO8ZNfYe/TEVzxIHDKHWUrsmGE6oQW01LezG5LUjwR1T23YeR8G5zzRs/p+AU72wTn/aPGQHg==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -820,21 +875,24 @@ } }, "node_modules/@angular/router/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@assemblyscript/loader": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", - "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==" + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "license": "Apache-2.0" }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -842,9 +900,10 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", + "integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -853,6 +912,7 @@ "version": "7.20.12", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -882,6 +942,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -890,6 +951,7 @@ "version": "7.20.14", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz", "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==", + "license": "MIT", "dependencies": { "@babel/types": "^7.20.7", "@jridgewell/gen-mapping": "^0.3.2", @@ -903,6 +965,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -916,6 +979,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "license": "MIT", "dependencies": { "@babel/types": "^7.18.6" }, @@ -924,24 +988,27 @@ } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", + "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -950,9 +1017,9 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", "funding": [ { "type": "opencollective", @@ -967,11 +1034,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -984,23 +1052,25 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", - "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.24.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.8.tgz", + "integrity": "sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", "semver": "^6.3.1" }, "engines": { @@ -1011,22 +1081,24 @@ } }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1036,16 +1108,18 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", + "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-annotate-as-pure": "^7.24.7", "regexpu-core": "^5.3.1", "semver": "^6.3.1" }, @@ -1057,11 +1131,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1071,6 +1146,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -1079,6 +1155,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.17.7", "@babel/helper-plugin-utils": "^7.16.7", @@ -1095,86 +1172,99 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", + "license": "MIT", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name/node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", - "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", + "integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1184,43 +1274,47 @@ } }, "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", + "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-wrap-function": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1230,24 +1324,26 @@ } }, "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1257,22 +1353,26 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1282,6 +1382,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "license": "MIT", "dependencies": { "@babel/types": "^7.18.6" }, @@ -1290,87 +1391,95 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", - "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", + "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", + "license": "MIT", "dependencies": { - "@babel/helper-function-name": "^7.23.0", - "@babel/template": "^7.24.0", - "@babel/types": "^7.24.5" + "@babel/helper-function-name": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function/node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", + "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", + "license": "MIT", "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers/node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -1380,9 +1489,10 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", + "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "license": "MIT", "bin": { "parser": "bin/babel-parser.js" }, @@ -1390,12 +1500,30 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", + "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", - "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", + "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1405,13 +1533,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", - "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1420,11 +1549,29 @@ "@babel/core": "^7.13.0" } }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", + "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-proposal-async-generator-functions": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", + "license": "MIT", "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-plugin-utils": "^7.20.2", @@ -1443,6 +1590,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1459,6 +1607,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead.", + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.21.0", "@babel/helper-plugin-utils": "^7.20.2", @@ -1476,6 +1625,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead.", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3" @@ -1492,6 +1642,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" @@ -1508,6 +1659,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead.", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-json-strings": "^7.8.3" @@ -1524,6 +1676,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" @@ -1540,6 +1693,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -1556,6 +1710,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -1572,6 +1727,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.20.5", "@babel/helper-compilation-targets": "^7.20.7", @@ -1591,6 +1747,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -1607,6 +1764,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", @@ -1624,6 +1782,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1640,6 +1799,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.18.6", "@babel/helper-create-class-features-plugin": "^7.21.0", @@ -1658,6 +1818,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.", + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1673,6 +1834,7 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1684,6 +1846,7 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -1695,6 +1858,7 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1709,6 +1873,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1720,6 +1885,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.3" }, @@ -1728,11 +1894,28 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", - "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1741,10 +1924,24 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1756,6 +1953,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1767,6 +1965,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1778,6 +1977,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1789,6 +1989,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1800,6 +2001,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1811,6 +2013,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1822,6 +2025,7 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1836,6 +2040,7 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1846,12 +2051,49 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", - "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", + "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7", + "@babel/plugin-syntax-async-generators": "^7.8.4" }, "engines": { "node": ">=6.9.0" @@ -1864,6 +2106,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.18.6", "@babel/helper-plugin-utils": "^7.20.2", @@ -1877,11 +2120,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", - "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1891,11 +2135,29 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", - "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", + "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1904,18 +2166,37 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", - "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.8.tgz", + "integrity": "sha512-VXy91c47uujj758ud9wx+OMgheXm4qJfyhj1P18YvlrQkNOSrwsteHk+EFS3OMGfhMhpZa0A+81eE7G4QC+3CA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", "globals": "^11.1.0" }, "engines": { @@ -1926,34 +2207,37 @@ } }, "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", - "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/template": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1963,24 +2247,26 @@ } }, "node_modules/@babel/plugin-transform-computed-properties/node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", - "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1990,12 +2276,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", - "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2005,11 +2292,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", - "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2018,13 +2306,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", - "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -2033,13 +2323,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", - "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2048,14 +2339,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", - "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -2064,12 +2356,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", - "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2078,12 +2372,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", - "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", + "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2092,13 +2389,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", - "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -2107,14 +2406,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", + "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2123,15 +2421,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", - "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { "node": ">=6.9.0" @@ -2140,13 +2438,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", - "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2155,27 +2453,149 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", - "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", + "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", + "license": "MIT", + "dependencies": { + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2185,12 +2605,30 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", - "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -2200,12 +2638,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", - "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { @@ -2216,11 +2655,48 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", - "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -2229,12 +2705,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", - "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2244,11 +2734,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", - "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "regenerator-transform": "^0.15.2" }, "engines": { @@ -2259,11 +2750,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", - "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2276,6 +2768,7 @@ "version": "7.19.6", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.18.6", "@babel/helper-plugin-utils": "^7.19.0", @@ -2295,16 +2788,18 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", - "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2314,12 +2809,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", - "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2329,11 +2825,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", - "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2343,11 +2840,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", - "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2357,11 +2855,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", - "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2371,11 +2870,29 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", - "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2385,12 +2902,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", - "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2399,10 +2917,28 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/preset-env": { "version": "7.20.2", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.20.1", "@babel/helper-compilation-targets": "^7.20.0", @@ -2491,6 +3027,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -2499,6 +3036,7 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", @@ -2513,12 +3051,14 @@ "node_modules/@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "license": "MIT" }, "node_modules/@babel/runtime": { "version": "7.20.13", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -2530,6 +3070,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.18.6", "@babel/parser": "^7.20.7", @@ -2540,18 +3081,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", + "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.8", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.8", + "@babel/types": "^7.24.8", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2560,11 +3102,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.24.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", + "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.24.9", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -2574,11 +3117,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2588,6 +3132,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -2598,22 +3143,86 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", + "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@bokeh/bokehjs": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@bokeh/bokehjs/-/bokehjs-3.4.1.tgz", + "integrity": "sha512-s7eCG9gEcGE5/e+eSCIS26rtqJ5G5gilnIOjPRwmulQ5I8XafZvRlCOZBuZmTvrI1jtK3QOdqC/YX4+GYMfHhw==", + "license": "BSD-3-Clause", + "workspaces": [ + "./make", + "./src/compiler", + "./src/lib", + "./src/server", + "./test" + ], + "dependencies": { + "@bokeh/numbro": "^1.6.2", + "@bokeh/slickgrid": "~2.4.4103", + "@types/geojson": "^7946.0.13", + "@types/google.maps": "^3.54.10", + "@types/proj4": "^2.5.5", + "@types/sprintf-js": "^1.1.4", + "choices.js": "^10.2.0", + "flatbush": "^4.2.0", + "flatpickr": "^4.6.13", + "mathjax-full": "^3.2.2", + "nouislider": "^15.7.1", + "proj4": "^2.9.2", + "regl": "^2.1.0", + "sprintf-js": "^1.1.3", + "timezone": "^1.0.23", + "tslib": "^2.6.2", + "underscore.template": "^0.1.7" + }, + "engines": { + "node": ">=16.0", + "npm": ">=8.0" + } + }, + "node_modules/@bokeh/bokehjs/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" + }, + "node_modules/@bokeh/numbro": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@bokeh/numbro/-/numbro-1.6.2.tgz", + "integrity": "sha512-owIECPc3T3QXHCb2v5Ez+/uE9SIxI7N4nd9iFlWnfBrOelr0/omvFn09VisRn37AAFAY39sJiCVgECwryHWUPA==", + "engines": { + "node": "*" + } + }, + "node_modules/@bokeh/slickgrid": { + "version": "2.4.4103", + "resolved": "https://registry.npmjs.org/@bokeh/slickgrid/-/slickgrid-2.4.4103.tgz", + "integrity": "sha512-wPitQJNUNUHw+86BPemvb1kIuV6UB4CpjEEDIXpU8G6Jges+8nx/iGHW8w9BwvICdeyhAmfX9Hmn5vRJMbe+uw==", + "license": "MIT", + "dependencies": { + "@types/slickgrid": "^2.1.30", + "jquery": ">=3.4.0", + "jquery-ui": ">=1.8.0", + "tslib": "^1.10.0" + } + }, "node_modules/@circlon/angular-tree-component": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/@circlon/angular-tree-component/-/angular-tree-component-11.0.4.tgz", "integrity": "sha512-Ck86mG6Z9eWG03RiOACDzrCjuzEDXU8rcEDi0aw0+Ku62x6ZY2mx8G0VX3CLEkS1BAXM2ef6luOIcoSKAKtDaA==", + "license": "MIT", "dependencies": { "mobx": "~4.14.1", "tslib": "^2.0.0" @@ -2624,83 +3233,917 @@ } }, "node_modules/@circlon/angular-tree-component/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.1.90" } }, - "node_modules/@cypress/request": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", - "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", + "node_modules/@compodoc/compodoc": { + "version": "1.1.25", + "resolved": "https://registry.npmjs.org/@compodoc/compodoc/-/compodoc-1.1.25.tgz", + "integrity": "sha512-MsTEv6S0JGkdXc8pFp3yB/r8Lw49YenD0TCXyIVAmQhWNDtGWi4m2TGz02hdiKAlTJ1McQJFuyXWiItTQtje0A==", "dev": true, + "hasInstallScript": true, + "license": "MIT", "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.10.4", - "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" + "@angular-devkit/schematics": "18.0.1", + "@babel/core": "^7.24.6", + "@babel/plugin-transform-private-methods": "^7.24.6", + "@babel/preset-env": "^7.24.6", + "@compodoc/live-server": "^1.2.3", + "@compodoc/ngd-transformer": "^2.1.3", + "bootstrap.native": "^5.0.12", + "chalk": "4.1.2", + "cheerio": "^1.0.0-rc.12", + "chokidar": "^3.6.0", + "colors": "1.4.0", + "commander": "^12.1.0", + "cosmiconfig": "^9.0.0", + "decache": "^4.6.2", + "es6-shim": "^0.35.8", + "fancy-log": "^2.0.0", + "fast-glob": "^3.3.2", + "fs-extra": "^11.2.0", + "glob": "^10.4.1", + "handlebars": "^4.7.8", + "html-entities": "^2.5.2", + "i18next": "^23.11.5", + "json5": "^2.2.3", + "lodash": "^4.17.21", + "loglevel": "^1.9.1", + "loglevel-plugin-prefix": "^0.8.4", + "lunr": "^2.3.9", + "marked": "7.0.3", + "minimist": "^1.2.8", + "opencollective-postinstall": "^2.0.3", + "os-name": "4.0.1", + "pdfmake": "^0.2.10", + "prismjs": "^1.29.0", + "semver": "^7.6.2", + "svg-pan-zoom": "^3.6.1", + "tablesort": "^5.3.0", + "traverse": "^0.6.9", + "ts-morph": "^22.0.0", + "uuid": "^9.0.1", + "vis": "^4.21.0-EOL", + "zepto": "^1.2.0" + }, + "bin": { + "compodoc": "bin/index-cli.js" }, "engines": { - "node": ">= 6" + "node": ">= 16.0.0" } }, - "node_modules/@cypress/xvfb": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "node_modules/@compodoc/compodoc/node_modules/@angular-devkit/core": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.0.1.tgz", + "integrity": "sha512-91eKZoObs+wRgwssw81Y/94Nvixj0WqJkNusBAg+gAfZTCEeJoGGZJkRK8wrONbM79C3Bx8lN/TfSIPRbjnfOQ==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" + "ajv": "8.13.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/@cypress/xvfb/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/@compodoc/compodoc/node_modules/@angular-devkit/schematics": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.0.1.tgz", + "integrity": "sha512-AKcEGa3fIgyXT6XTQZWEJZzgmcqlB89fcF7JFOuz4rgQfRmnE2xFw37lKE6ZclCOSiEoffAvgrL8acjdPI1ouw==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "@angular-devkit/core": "18.0.1", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.10", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, "engines": { - "node": ">=10.0.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.17.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.8.tgz", - "integrity": "sha512-0/rb91GYKhrtbeglJXOhAv9RuYimgI8h623TplY2X+vA4EXnk3Zj1fXZreJ0J3OJJu1bwmb0W7g+2cT/d8/l/w==", - "cpu": [ + "node_modules/@compodoc/compodoc/node_modules/@babel/core": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", + "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.9", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-module-transforms": "^7.24.9", + "@babel/helpers": "^7.24.8", + "@babel/parser": "^7.24.8", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@compodoc/compodoc/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@compodoc/compodoc/node_modules/@babel/generator": { + "version": "7.24.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", + "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/@babel/preset-env": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.8.tgz", + "integrity": "sha512-vObvMZB6hNWuDxhSaEPTKCwcqkAIuDtE+bQGn4XMXne1DSLzFVY8Vmj1bm+mUQXYNN8NmaQEO+r8MMbzPr1jBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.24.8", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.8", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@compodoc/compodoc/node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/@babel/template": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@compodoc/compodoc/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@compodoc/compodoc/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@compodoc/compodoc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@compodoc/compodoc/node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@compodoc/compodoc/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@compodoc/compodoc/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@compodoc/compodoc/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@compodoc/compodoc/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@compodoc/compodoc/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@compodoc/compodoc/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@compodoc/compodoc/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@compodoc/compodoc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@compodoc/compodoc/node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@compodoc/compodoc/node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/@compodoc/compodoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@compodoc/compodoc/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@compodoc/compodoc/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@compodoc/compodoc/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@compodoc/compodoc/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@compodoc/compodoc/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@compodoc/compodoc/node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@compodoc/live-server": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@compodoc/live-server/-/live-server-1.2.3.tgz", + "integrity": "sha512-hDmntVCyjjaxuJzPzBx68orNZ7TW4BtHWMnXlIVn5dqhK7vuFF/11hspO1cMmc+2QTYgqde1TBcb3127S7Zrow==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "colors": "1.4.0", + "connect": "^3.7.0", + "cors": "latest", + "event-stream": "4.0.1", + "faye-websocket": "0.11.x", + "http-auth": "4.1.9", + "http-auth-connect": "^1.0.5", + "morgan": "^1.10.0", + "object-assign": "latest", + "open": "8.4.0", + "proxy-middleware": "latest", + "send": "latest", + "serve-index": "^1.9.1" + }, + "bin": { + "live-server": "live-server.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@compodoc/live-server/node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@compodoc/ngd-core": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@compodoc/ngd-core/-/ngd-core-2.1.1.tgz", + "integrity": "sha512-Z+wE6wWZYVnudRYg6qunDlyh3Orw39Ib66Gvrz5kX5u7So+iu3tr6sQJdqH6yGS3hAjig5avlfhWLlgsb6/x1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "fancy-log": "^2.0.0", + "typescript": "^5.0.4" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@compodoc/ngd-core/node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@compodoc/ngd-transformer": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@compodoc/ngd-transformer/-/ngd-transformer-2.1.3.tgz", + "integrity": "sha512-oWxJza7CpWR8/FeWYfE6j+jgncnGBsTWnZLt5rD2GUpsGSQTuGrsFPnmbbaVLgRS5QIVWBJYke7QFBr/7qVMWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aduh95/viz.js": "3.4.0", + "@compodoc/ngd-core": "~2.1.1", + "dot": "^2.0.0-beta.1", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@cypress/request": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "http-signature": "~1.3.6", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.10.4", + "safe-buffer": "^5.1.2", + "tough-cookie": "^4.1.3", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.8.tgz", + "integrity": "sha512-0/rb91GYKhrtbeglJXOhAv9RuYimgI8h623TplY2X+vA4EXnk3Zj1fXZreJ0J3OJJu1bwmb0W7g+2cT/d8/l/w==", + "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -2716,6 +4159,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -2731,6 +4175,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -2746,6 +4191,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2761,6 +4207,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2776,6 +4223,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2791,6 +4239,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2806,6 +4255,7 @@ "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2821,6 +4271,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2836,6 +4287,7 @@ "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2851,6 +4303,7 @@ "cpu": [ "loong64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2866,6 +4319,7 @@ "cpu": [ "mips64el" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2881,6 +4335,7 @@ "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2896,6 +4351,7 @@ "cpu": [ "riscv64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2911,6 +4367,7 @@ "cpu": [ "s390x" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2926,6 +4383,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2941,6 +4399,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -2956,6 +4415,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -2971,6 +4431,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "sunos" @@ -2986,6 +4447,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -3001,6 +4463,7 @@ "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -3016,6 +4479,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -3024,15 +4488,72 @@ "node": ">=12" } }, + "node_modules/@foliojs-fork/fontkit": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@foliojs-fork/fontkit/-/fontkit-1.9.2.tgz", + "integrity": "sha512-IfB5EiIb+GZk+77TRB86AHroVaqfq8JRFlUbz0WEwsInyCG0epX2tCPOy+UfaWPju30DeVoUAXfzWXmhn753KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@foliojs-fork/restructure": "^2.0.2", + "brotli": "^1.2.0", + "clone": "^1.0.4", + "deep-equal": "^1.0.0", + "dfa": "^1.2.0", + "tiny-inflate": "^1.0.2", + "unicode-properties": "^1.2.2", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/@foliojs-fork/linebreak": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@foliojs-fork/linebreak/-/linebreak-1.1.2.tgz", + "integrity": "sha512-ZPohpxxbuKNE0l/5iBJnOAfUaMACwvUIKCvqtWGKIMv1lPYoNjYXRfhi9FeeV9McBkBLxsMFWTVVhHJA8cyzvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "1.3.1", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/@foliojs-fork/linebreak/node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@foliojs-fork/pdfkit": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@foliojs-fork/pdfkit/-/pdfkit-0.14.0.tgz", + "integrity": "sha512-nMOiQAv6id89MT3tVTCgc7HxD5ZMANwio2o5yvs5sexQkC0KI3BLaLakpsrHmFfeGFAhqPmZATZGbJGXTUebpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@foliojs-fork/fontkit": "^1.9.1", + "@foliojs-fork/linebreak": "^1.1.1", + "crypto-js": "^4.2.0", + "png-js": "^1.0.0" + } + }, + "node_modules/@foliojs-fork/restructure": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@foliojs-fork/restructure/-/restructure-2.0.2.tgz", + "integrity": "sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==", + "dev": true, + "license": "MIT" + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -3049,6 +4570,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -3060,6 +4582,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -3070,12 +4593,14 @@ "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -3092,6 +4617,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -3106,6 +4632,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -3122,6 +4649,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "license": "ISC", "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -3137,6 +4665,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "license": "MIT", "engines": { "node": ">=8" } @@ -3145,6 +4674,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -3157,6 +4687,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -3165,6 +4696,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -3173,6 +4705,7 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -3182,6 +4715,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -3192,14 +4726,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -3208,44 +4744,51 @@ "node_modules/@kurkle/color": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", + "license": "MIT" }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" }, "node_modules/@material/animation": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-5osi1z4JQIXcklPALbH/zTfOm2pDzHt9Fxm7ZyURy250xIZj6QjULRzPTnzOhC2ropfix9ra2Cfggbf0dcRbEQ==", + "license": "MIT", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/animation/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/auto-init": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-OigQTmrVzkcGvxNjOaIe5oItTFPgrO9xLewvharDI6m6yvO1z7OBnkcW+sFN6ggLNYNxd0O1u9v64vMsmeDABQ==", + "license": "MIT", "dependencies": { "@material/base": "15.0.0-canary.684e33d25.0", "tslib": "^2.1.0" } }, "node_modules/@material/auto-init/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/banner": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-PqtGp3KWzdu58rWv/DIvSfe38m5YKOBbAAbBinSvgadBb/da+IE1t5F7YPNKE1T5lJsQBGVUYx6QBIeXm+aI/A==", + "license": "MIT", "dependencies": { "@material/base": "15.0.0-canary.684e33d25.0", "@material/button": "15.0.0-canary.684e33d25.0", @@ -3262,27 +4805,31 @@ } }, "node_modules/@material/banner/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/base": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-oOaqb/SfjWwTKsdJUZmeh/Qrs41nIJI0N+zELsxnvbGjSIN1ZMAKYZFPMahqvC68OJ6+5CvJM8PoTNs5l+B8IQ==", + "license": "MIT", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/base/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/button": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-Nkekk4edeX+ObVOa7UlwavaHdmckPV5wU4SAJf3iA3R61cmz+KsgAgpzfcwv5WfNhIlc2nLu8QYEecpHdo9d/w==", + "license": "MIT", "dependencies": { "@material/density": "15.0.0-canary.684e33d25.0", "@material/dom": "15.0.0-canary.684e33d25.0", @@ -3300,14 +4847,16 @@ } }, "node_modules/@material/button/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/card": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-xhyB7XX5KkEiCEqwSPkl58ZGYL6xFdnY62zimyBXJRG/Eaa0Swj3kW20hVCpt4f7c9Zmp8Se27rg8vnKmhvO3g==", + "license": "MIT", "dependencies": { "@material/dom": "15.0.0-canary.684e33d25.0", "@material/elevation": "15.0.0-canary.684e33d25.0", @@ -3321,14 +4870,16 @@ } }, "node_modules/@material/card/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/checkbox": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-NFpM3TS924PmVsk2KQLNU95OYCf8ZwYgzeqfnAexU0bEfjUJXINBun2Go0AaeOUMjuvWUe+byjrXgv8SFYbMUA==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3343,14 +4894,16 @@ } }, "node_modules/@material/checkbox/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/chips": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-z4ajQ4NnsAQ/Si9tZ4xmxzjj2Qb+vW++4QjCjjjwAGIZbCe0xglAnMh2t66XLJUxt7RoKZuZVEO7ZqcFZpvJFQ==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3372,14 +4925,16 @@ } }, "node_modules/@material/chips/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/circular-progress": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-G6qD0nGNtEUwWnAMJuA9INYFpZoKtx7KFjBaPF4Ol2YLHtmShALNAYyn54TMAK8AZ2IpW08PXjGS7Ye88vrdEQ==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3392,14 +4947,16 @@ } }, "node_modules/@material/circular-progress/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/data-table": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-+wDw1DDDFfAsKAMzs84f/5GCjux39zjNfW8tL4wFbkWNwewmQrG9zaQMJhBpVOtLCrM8Gj6SOgOANqgqoCjvGg==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3423,27 +4980,31 @@ } }, "node_modules/@material/data-table/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/density": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-661yEVRMGrlq6S6WuSbPRO+ZwpdUOg2glCc7y96doM6itSLOa3UEAldjOLfsYZVB74GnKCiuDp//QmfoRyYTfA==", + "license": "MIT", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/density/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/dialog": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-szn0dHnfeQTSOC6SSRSGAzX6Tnx+4NnSMUwNkXm+3bwjds8ZVK26+DXwLrP5f3ID5F1K5sFsRf2INo5/TNTHyQ==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3463,14 +5024,16 @@ } }, "node_modules/@material/dialog/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/dom": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-7pEJLYov+tGgfuD8mZxoVU6rWtPI8ppjTAhz+F27Hz9FG0JETMWTKpDPBXLnKvX7vhIxL83GvZ9geNHCe8Hfog==", + "license": "MIT", "dependencies": { "@material/feature-targeting": "15.0.0-canary.684e33d25.0", "@material/rtl": "15.0.0-canary.684e33d25.0", @@ -3478,14 +5041,16 @@ } }, "node_modules/@material/dom/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/drawer": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-/KMckLf1PYU/H3PXnS4e0aFl03qG3JlSv4LGgX6juJufcONqGTl/m63EMO/L/eUy6H1CRrXmVDjik/jzHLyDhg==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3502,14 +5067,16 @@ } }, "node_modules/@material/drawer/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/elevation": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-WDF8SsRtq3rXUbVVbd9K4DUijIPH0bUFSOreVYxudpuxAfTlDS5+aeS1EK9UIBFYLuba4u5wVT2tDv6e1RTfrQ==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3520,14 +5087,16 @@ } }, "node_modules/@material/elevation/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/fab": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-KCu87rWOKEAe9vZcAm6K8XazYSWPNjMG+OhrbPjHW6bCO7as1YCgtmkBkhff7csY/rFmcVpIy884xtUfLmSudQ==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/dom": "15.0.0-canary.684e33d25.0", @@ -3545,27 +5114,31 @@ } }, "node_modules/@material/fab/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/feature-targeting": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-HyH1erNTSjS63sigNSUMaCd0nJhTNdDFeC+myrxwtDaQm+uYJ8troCNtQM3g6mx0XATNtX5aTOoPmrM6yVVi1A==", + "license": "MIT", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/feature-targeting/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/floating-label": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-f7TPp6bKpGvV3sYYiZHSGlrixXKkXXITW3Esp7KB9jRq42c0H82novmdwvY0eTef4ootmA2JEysr78KQfHBUPg==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3578,14 +5151,16 @@ } }, "node_modules/@material/floating-label/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/focus-ring": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-ikw2RVUfgzXChpWIzPH1VzRvTjYb5ZKj4H+CZf7jqPUXMstFOZg90Bp7ARLZHqYiyNMuUq3zUTHozS6iHorSqg==", + "license": "MIT", "dependencies": { "@material/dom": "15.0.0-canary.684e33d25.0", "@material/feature-targeting": "15.0.0-canary.684e33d25.0", @@ -3596,6 +5171,7 @@ "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-vpF9N/uq5no/7+8GAbEH0868FhOuBgxAWRr1Sfb+jthKfBr8OS/wPU/AHzZHdHdAm7PQynbeOXfDsX2dI//PDA==", + "license": "MIT", "dependencies": { "@material/base": "15.0.0-canary.684e33d25.0", "@material/feature-targeting": "15.0.0-canary.684e33d25.0", @@ -3607,14 +5183,16 @@ } }, "node_modules/@material/form-field/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/icon-button": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-wMI+XGzmIN/o2ePBKg2hLyx7H4pXCRAyyIKMQS1FMp1UKa2tYmiHVX/V8skhKwCqxg3i6Ls/LxMjfPxTR18WvQ==", + "license": "MIT", "dependencies": { "@material/base": "15.0.0-canary.684e33d25.0", "@material/density": "15.0.0-canary.684e33d25.0", @@ -3630,14 +5208,16 @@ } }, "node_modules/@material/icon-button/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/image-list": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-Ol+uaHYBe5R/cgzlfh5ONnMVX0wO6fV74JMUcQCQlxP6lXau/edARo4tkRc7A7UJUkU3VRv0EpEjLoCRNUPGaA==", + "license": "MIT", "dependencies": { "@material/feature-targeting": "15.0.0-canary.684e33d25.0", "@material/shape": "15.0.0-canary.684e33d25.0", @@ -3647,27 +5227,31 @@ } }, "node_modules/@material/image-list/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/layout-grid": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-ALXE1mqFNb/RB2lVRQ3/r1Aufw2mFZnOjRE+boYDVepmAG/xWyPCyaGoavELJF5l4GAb0tXi8wA/8HeGbLOpuA==", + "license": "MIT", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/layout-grid/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/line-ripple": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-7hRx8C/e9i0P6pgQpNOMfTwSS2r1fwEvBL72QDVGLtLuoKKwsjjgP6Z0Jat/GeHJe87u9LQvGBoD4upt+of/HA==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3677,14 +5261,16 @@ } }, "node_modules/@material/line-ripple/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/linear-progress": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-iJclt7mKmcMk6pqD7ocXKfCWZhqBoODp7N593jYlxVpTJuEz2wiVAjZUDn/YGj/Uz3CRH+2YFfOiLr9pwWjhDg==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3697,14 +5283,16 @@ } }, "node_modules/@material/linear-progress/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/list": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-rQ+FCSdzmwTcT00IYE0uRV3CS4oGSccKFl9hkcF+aHFW61L7ORh/SCGUDPrEfQFrFkMn5f8qroVJjpUAMXBz4g==", + "license": "MIT", "dependencies": { "@material/base": "15.0.0-canary.684e33d25.0", "@material/density": "15.0.0-canary.684e33d25.0", @@ -3720,14 +5308,16 @@ } }, "node_modules/@material/list/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/menu": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-r7wzDLSGSI9629/mfpvsMzkVxpmV75kcD3IrW0Pcu6/Bv/1xi0EvjcUXzNJJoQlwN4Zj35Ymz/PCjZkIDIz68Q==", + "license": "MIT", "dependencies": { "@material/base": "15.0.0-canary.684e33d25.0", "@material/dom": "15.0.0-canary.684e33d25.0", @@ -3747,6 +5337,7 @@ "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-RVO5GAYcfWPaKwxsF/NhUAmrYXQCQBKvRQW0TIlbmAJz6lcFeTs6YZqF3u1C7qrL3ZQGz+sur/7ywj6QU0oMow==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3759,19 +5350,22 @@ } }, "node_modules/@material/menu-surface/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/menu/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/notched-outline": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-9YHcBkvJLPVYzkHcWoTpBZAFrEd+j1hjhGxLhh0LuNrZe8VroUkZD1TTnUAPHRG3os6EqEWWaKb0RN+aPIF2yQ==", + "license": "MIT", "dependencies": { "@material/base": "15.0.0-canary.684e33d25.0", "@material/feature-targeting": "15.0.0-canary.684e33d25.0", @@ -3783,27 +5377,31 @@ } }, "node_modules/@material/notched-outline/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/progress-indicator": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-c0icji4faeNWUoqGENGC7Hav0Puxh0RwXIDVizffaUxKIGbajpIp5+4Zop73fK/xFLGMB/npg7TbP+aCGjQ3fw==", + "license": "MIT", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/progress-indicator/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/radio": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-U3Eh8sNUA8trDla1Bq8Bo02foxYvtoewaKeF8A8tAju81XZ4jRiftfOsOWZDZEHCVbbCB2QwvutvFlnay5n+Aw==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3818,14 +5416,16 @@ } }, "node_modules/@material/radio/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/ripple": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-RyePu7SjIm/OuyyEieZ/gxiPYkNZOZHeid72WRcN9ofdlljj2pifcdPvcfZA+v/DMS33xo5GjG2L/Qj6ClWrKw==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3837,28 +5437,32 @@ } }, "node_modules/@material/ripple/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/rtl": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-NqdJl8Ayupp1Th+vCNCpVQHbUFOuF7TCte9LD1norTIBUF/QizIxWby2W5uUEiPbnh5j9PmE1CJtfLwKun3pcw==", + "license": "MIT", "dependencies": { "@material/theme": "15.0.0-canary.684e33d25.0", "tslib": "^2.1.0" } }, "node_modules/@material/rtl/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/segmented-button": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-bEGgg8vgXNLyukyV8HRjFMuQ6t6nm5LQ4Pgm22um61Yc8qyi0BOqV41OR4SVdUrUqZxh1aVD+p+4NN03+LfQXw==", + "license": "MIT", "dependencies": { "@material/base": "15.0.0-canary.684e33d25.0", "@material/elevation": "15.0.0-canary.684e33d25.0", @@ -3871,14 +5475,16 @@ } }, "node_modules/@material/segmented-button/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/select": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-kf178/2TeEinTv0mgmSBcmmExQ2h7a7dtR1E3WuqQgisJ/R6+zVLMkC2CnfIyzxYX2vkuUTG0ue3Reh/6XiqSg==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3902,14 +5508,16 @@ } }, "node_modules/@material/select/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/shape": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-aEelpaTFmpnCji3TUGP9bVCS/bRVjUmLTHBPZtuu1gOrUVVtJ6kYOg73dZNJF+XOoNL2yOX/LRcKwsop29tptA==", + "license": "MIT", "dependencies": { "@material/feature-targeting": "15.0.0-canary.684e33d25.0", "@material/rtl": "15.0.0-canary.684e33d25.0", @@ -3918,14 +5526,16 @@ } }, "node_modules/@material/shape/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/slider": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-WVyK+2pSNSZmj07M2K/a3TADoQ9FBCndfNC/vE7/wGIg4dddJJK5KvQ+yruf9R2cSzTL/S1sZ5WpyyeM8E9HTw==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3941,14 +5551,16 @@ } }, "node_modules/@material/slider/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/snackbar": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-itO+DCkOannZzR1/cCHcqAm7ifhuFvXmDItNoA8qLEcAyJDJJRkhpwj3XQ01yuo9gBFcSctp7Txt7e+Hncm/Jg==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3967,14 +5579,16 @@ } }, "node_modules/@material/snackbar/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/switch": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-Jxi0gl92yvvZZsAPxvVHzXx2ga+T/djMow98jvEczmpUorWnAhgiCr9CsSSRoosahWyRB8NLZOxUQrACxvffjw==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -3993,14 +5607,16 @@ } }, "node_modules/@material/switch/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/tab": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-WQL3wj9syHNcfe8KbgGGUcA34M8C/xZ+n0Fkkh8Kk6puVwaU+xqUNihsxPY6YzKpmh4PZ4oJaBdiN8zvFT1zqQ==", + "license": "MIT", "dependencies": { "@material/base": "15.0.0-canary.684e33d25.0", "@material/elevation": "15.0.0-canary.684e33d25.0", @@ -4019,6 +5635,7 @@ "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-SW/cMaDsIGGkM1ag3A7GJRlmr8eXmObWsvitQJzh6Azr5zzZtSI+GQygkMesAEE1gbpqOVN8d40rh3H7VVIAcA==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -4035,14 +5652,16 @@ } }, "node_modules/@material/tab-bar/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/tab-indicator": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-kKICqSPqOlaf0lzaFFCmuOqPXJC+cK48Qmsc+m5o6fJhkmuZRCYpIwB2JeP+uZSOq/bTH+SrPtCtnVlgWg6ksA==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -4052,14 +5671,16 @@ } }, "node_modules/@material/tab-indicator/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/tab-scroller": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-H6EU/TSiK/M2DyyORX5GEtXD9rKYxTMHC2VxsNWARPMFJGzgeW2ugYkFv+rKI1/c0bs0CJ4e+qFnOlBsQXZvyQ==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -4070,19 +5691,22 @@ } }, "node_modules/@material/tab-scroller/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/tab/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/textfield": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-OvgpDXjvpyJTtAWskO69IDybFvDNzr9w2PN/Fk7yFm+uNVupaWz1Ew8lZ4gGslaTNSVmh2XcsvmzxcLINSiiNg==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -4102,28 +5726,32 @@ } }, "node_modules/@material/textfield/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/theme": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-AZxaXXAvRKzAi20RlMxzt2U5UmkCWyv7DMWEBXsxtG5Tk54mi1HsbVUp3fxDPTlmL7Pq8p1/DESg/o7TgRCVlw==", + "license": "MIT", "dependencies": { "@material/feature-targeting": "15.0.0-canary.684e33d25.0", "tslib": "^2.1.0" } }, "node_modules/@material/theme/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/tokens": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-wVwbQOTCXDPKYPdHQHLr026y36MMFelID1CmbfRk6mSol4O8yE9U0fXcShfRDW8Qo5E3X31w9c2A6T3neJY7wQ==", + "license": "MIT", "dependencies": { "@material/elevation": "15.0.0-canary.684e33d25.0" } @@ -4132,6 +5760,7 @@ "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-dtm26QjxyQdinc8btgz6yys07b7bUW4FZgNF2EBPeGrICrPg7jf+JEvDziz5g8VMaTBQLOQRSCGy0MKuRlOjLw==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -4149,14 +5778,16 @@ } }, "node_modules/@material/tooltip/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/top-app-bar": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-1M+oupUxflfW7u81P1XlxoLZB8bLzwtpKofIfDNRbEsiKhlLTERJR3Yak3BGE9xakNMysAaBHlkb5MrN5bNPFw==", + "license": "MIT", "dependencies": { "@material/animation": "15.0.0-canary.684e33d25.0", "@material/base": "15.0.0-canary.684e33d25.0", @@ -4170,14 +5801,16 @@ } }, "node_modules/@material/top-app-bar/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/touch-target": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-zdE69Slg8+T7sTn1OwqZ6H7WBYac9mxJ/JlJqfTqthzIjZRcCxBSYymQJcDHjsrPnUojOtr9U4Tpm5YZ96TEkQ==", + "license": "MIT", "dependencies": { "@material/base": "15.0.0-canary.684e33d25.0", "@material/feature-targeting": "15.0.0-canary.684e33d25.0", @@ -4187,14 +5820,16 @@ } }, "node_modules/@material/touch-target/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@material/typography": { "version": "15.0.0-canary.684e33d25.0", "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.684e33d25.0.tgz", "integrity": "sha512-aVnvgMwcfNa/K4wujzpKDIxjGl2hbkEL+m+OKDSQqWYjKcP9QrbzCXJruJBqxrBoPRHLbqo47k5f9uT8raSgjw==", + "license": "MIT", "dependencies": { "@material/feature-targeting": "15.0.0-canary.684e33d25.0", "@material/theme": "15.0.0-canary.684e33d25.0", @@ -4202,14 +5837,16 @@ } }, "node_modules/@material/typography/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@ng-bootstrap/ng-bootstrap": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-14.0.1.tgz", "integrity": "sha512-JF4U4IIix+g6VBFfG8stf0Un5K//ypoN+pTuRs6kjUhsHBsa2m7yKE6bCe3fMhatFZFr2fcSswDzRUnAUiHhWg==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -4223,14 +5860,16 @@ } }, "node_modules/@ng-bootstrap/ng-bootstrap/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@ng-select/ng-select": { "version": "10.0.4", "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-10.0.4.tgz", "integrity": "sha512-Vc/JIgcFkSgf47cX7+pQQo9HYhDktfqrY7o/ZPGMvu63P7E9d1MibVipqmcLbgms6Ac9lu621CDZPGHdxag7hA==", + "license": "MIT", "dependencies": { "tslib": "^2.3.1" }, @@ -4245,14 +5884,16 @@ } }, "node_modules/@ng-select/ng-select/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/@ngtools/webpack": { "version": "15.2.11", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.2.11.tgz", "integrity": "sha512-yqp+FziuJ+wIVij4eTqfhuiTPNaG1PU8ukeGOdqkVH4nQMlmzs9UldXy1iYC/6swzn6XO/pkqisU3m/jxemMzA==", + "license": "MIT", "engines": { "node": "^14.20.0 || ^16.13.0 || >=18.10.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", @@ -4268,6 +5909,7 @@ "version": "12.1.2", "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-12.1.2.tgz", "integrity": "sha512-ZudJsqIxTKlLmPoqK8gJY3UpMGujR0Xm7HfXL6AR79yGRS23QqpjAhMfx4v5qUCcHMmQ9/78bW8QJLfp31c7vQ==", + "license": "MIT", "peerDependencies": { "@angular/core": ">=8.0.0", "rxjs": ">=6.3.0", @@ -4278,6 +5920,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-4.0.0.tgz", "integrity": "sha512-x8LumqydWD7eX9yQTAVeoCM9gFUIGVTUjZqbxdAUavAA3qVnk9wCQux7iHLPXpydl8vyQmLoPQR+fFU+DUDOMA==", + "license": "MIT", "dependencies": { "tslib": "^1.9.0" }, @@ -4291,6 +5934,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -4303,6 +5947,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", "engines": { "node": ">= 8" } @@ -4311,6 +5956,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -4323,6 +5969,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "license": "ISC", "dependencies": { "semver": "^7.3.5" }, @@ -4334,6 +5981,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "license": "ISC", "dependencies": { "@npmcli/promise-spawn": "^6.0.0", "lru-cache": "^7.4.4", @@ -4352,6 +6000,7 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", "engines": { "node": ">=12" } @@ -4360,6 +6009,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -4374,6 +6024,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "license": "ISC", "dependencies": { "npm-bundled": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" @@ -4390,6 +6041,7 @@ "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" @@ -4402,6 +6054,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -4410,6 +6063,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "license": "ISC", "dependencies": { "which": "^3.0.0" }, @@ -4421,6 +6075,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -4435,6 +6090,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "license": "ISC", "dependencies": { "@npmcli/node-gyp": "^3.0.0", "@npmcli/promise-spawn": "^6.0.0", @@ -4450,6 +6106,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -4464,6 +6121,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", "optional": true, "engines": { "node": ">=14" @@ -4473,6 +6131,7 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -4482,6 +6141,7 @@ "version": "15.1.6", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-15.1.6.tgz", "integrity": "sha512-y2kIQ1wJL0wR6v/LM5+PFJUivrYtdaIJVRdOXLLWl0AB5aLwObiWgLzAuBsbGm/9//WPPhw9PglS5EFFxTBDzg==", + "license": "MIT", "dependencies": { "@angular-devkit/core": "15.1.6", "@angular-devkit/schematics": "15.1.6", @@ -4497,6 +6157,7 @@ "version": "15.1.6", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.1.6.tgz", "integrity": "sha512-jGgxyRjecVf6lEyqDxz7ltMEndNPxIg720pk6r40fgsu0dU8w9vjJSJe7k0XdJiXVRcN6wZa/J5nO/xcwWVIsA==", + "license": "MIT", "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", @@ -4522,6 +6183,7 @@ "version": "20.1.0", "resolved": "https://registry.npmjs.org/@swimlane/ngx-datatable/-/ngx-datatable-20.1.0.tgz", "integrity": "sha512-oHnnx1QRNmv10l5UME13v5JP3M3GesM9K3QH6TRYo2C7UbbhY7vL5EZ4HGqcvtMMW4FOzqNOSltE++IVL99F3g==", + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -4533,14 +6195,38 @@ } }, "node_modules/@swimlane/ngx-datatable/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" + }, + "node_modules/@thednp/event-listener": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@thednp/event-listener/-/event-listener-2.0.5.tgz", + "integrity": "sha512-Zns+CFEAIKIEyqmuBZ3K2DSvk5IppaWcioghxLZPMrzkV034aOA38lP7NIKSxkeu0Eqd4UPxC06FksO6Pb/tmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16", + "pnpm": ">=8.6.0" + } + }, + "node_modules/@thednp/shorty": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@thednp/shorty/-/shorty-2.0.3.tgz", + "integrity": "sha512-ngKP9/wQxM6JPDFjO6ak8lSz38ZA6cIFQy3gZbZM3xgUqArBr+VG9aoSoLHHEuaObyd9q9Jq/T0Wez7qrck0Gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16", + "pnpm": ">=8.6.0" + } }, "node_modules/@tmcw/togeojson": { "version": "5.8.1", "resolved": "https://registry.npmjs.org/@tmcw/togeojson/-/togeojson-5.8.1.tgz", "integrity": "sha512-2YNrbis3l5kS0XrYwiHEZcGwiRp0MJ5CvwGwtMWp2z2tsVlskeec2qgvKHnF0RCwI5GnjrrBOoKsWfndEnd3LA==", + "license": "BSD-2-Clause", "engines": { "node": "*" }, @@ -4552,14 +6238,61 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", "engines": { "node": ">= 10" } }, + "node_modules/@ts-morph/common": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.23.0.tgz", + "integrity": "sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "minimatch": "^9.0.3", + "mkdirp": "^3.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@ts-morph/common/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -4569,6 +6302,7 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -4577,6 +6311,7 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -4585,15 +6320,17 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==", + "license": "MIT", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4603,6 +6340,7 @@ "version": "3.7.7", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -4611,12 +6349,14 @@ "node_modules/@types/estree": { "version": "0.0.51", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "license": "MIT" }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -4625,9 +6365,10 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", - "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -4638,30 +6379,50 @@ "node_modules/@types/geojson": { "version": "7946.0.14", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", - "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "license": "MIT" + }, + "node_modules/@types/google.maps": { + "version": "3.55.12", + "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.55.12.tgz", + "integrity": "sha512-Q8MsLE+YYIrE1H8wdN69YHHAF8h7ApvF5MiMXh/zeCpP9Ut745mV9M0F4X4eobZ2WJe9k8tW2ryYjLa87IO2Sg==", + "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" }, "node_modules/@types/http-proxy": { "version": "1.17.14", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/jquery": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.30.tgz", + "integrity": "sha512-nbWKkkyb919DOUxjmRVk8vwtDb0/k8FKncmUKFi+NY+QXqWltooxTrswvz4LspQwxvLdvzBN1TImr6cw3aQx2A==", + "license": "MIT", + "dependencies": { + "@types/sizzle": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" }, "node_modules/@types/leaflet": { "version": "1.9.12", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz", "integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==", + "license": "MIT", "dependencies": { "@types/geojson": "*" } @@ -4670,6 +6431,7 @@ "version": "0.4.14", "resolved": "https://registry.npmjs.org/@types/leaflet-draw/-/leaflet-draw-0.4.14.tgz", "integrity": "sha512-TyOZtr5SZf9ELR5EMLFwDlZuCGyjG0saUA6hEguZNEoratDiag1G/2eAVeYwK2NOX9N0zxQ9eDCRsjJK420X9g==", + "license": "MIT", "dependencies": { "@types/leaflet": "*" } @@ -4678,6 +6440,7 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.4.tgz", "integrity": "sha512-tfMP8J62+wfsVLDLGh5Zh1JZxijCaBmVsMAX78MkLPwvPitmZZtSin5aWOVRhZrCS+pEOZwNzexbfWXlY+7yjg==", + "license": "MIT", "dependencies": { "@types/leaflet": "*" } @@ -4685,12 +6448,14 @@ "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", - "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", + "version": "18.19.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.42.tgz", + "integrity": "sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg==", + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -4699,6 +6464,7 @@ "version": "1.3.11", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -4706,27 +6472,43 @@ "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/proj4": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@types/proj4/-/proj4-2.5.5.tgz", + "integrity": "sha512-y4tHUVVoMEOm2nxRLQ2/ET8upj/pBmoutGxFw2LZJTQWPgWXI+cbxVEUFFmIzr/bpFR83hGDOTSXX6HBeObvZA==", + "license": "MIT" + }, + "node_modules/@types/proj4": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@types/proj4/-/proj4-2.5.5.tgz", + "integrity": "sha512-y4tHUVVoMEOm2nxRLQ2/ET8upj/pBmoutGxFw2LZJTQWPgWXI+cbxVEUFFmIzr/bpFR83hGDOTSXX6HBeObvZA==" }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", - "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -4736,6 +6518,7 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "license": "MIT", "dependencies": { "@types/express": "*" } @@ -4744,6 +6527,7 @@ "version": "1.15.7", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", @@ -4754,26 +6538,44 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/sizzle": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", - "dev": true + "license": "MIT" + }, + "node_modules/@types/slickgrid": { + "version": "2.1.40", + "resolved": "https://registry.npmjs.org/@types/slickgrid/-/slickgrid-2.1.40.tgz", + "integrity": "sha512-+HWaipRr4bw0cy4eS8PCWxo+Fi66aL+mh36unRLAWtatAvMnxpldTKfpe9T7zdNtgN+rq+/faux3JzFR1mj/dg==", + "license": "MIT", + "dependencies": { + "@types/jquery": "*" + } }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/sprintf-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/sprintf-js/-/sprintf-js-1.1.4.tgz", + "integrity": "sha512-aWK1reDYWxcjgcIIPmQi3u+OQDuYa9b+lr6eIsGWrekJ9vr1NSjr4Eab8oQ1iKuH1ltFHpXGyerAv1a3FMKxzQ==", + "license": "MIT" + }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.11", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.11.tgz", + "integrity": "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -4783,6 +6585,7 @@ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@types/node": "*" @@ -4792,6 +6595,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -4800,22 +6604,26 @@ "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -4825,12 +6633,14 @@ "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -4842,6 +6652,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -4850,6 +6661,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } @@ -4857,12 +6669,14 @@ "node_modules/@webassemblyjs/utf8": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -4878,6 +6692,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -4890,6 +6705,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -4901,6 +6717,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -4914,6 +6731,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -4922,33 +6740,39 @@ "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "license": "BSD-2-Clause" }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead" + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "license": "BSD-3-Clause" }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -4958,9 +6782,10 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -4972,6 +6797,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "license": "MIT", "peerDependencies": { "acorn": "^8" } @@ -4980,6 +6806,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", "regex-parser": "^2.2.11" @@ -4992,6 +6819,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -5005,6 +6833,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", "dependencies": { "debug": "4" }, @@ -5016,6 +6845,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "license": "MIT", "dependencies": { "humanize-ms": "^1.2.1" }, @@ -5027,6 +6857,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -5039,6 +6870,7 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -5054,6 +6886,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -5070,6 +6903,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -5081,6 +6915,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -5089,6 +6924,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -5106,6 +6942,7 @@ "engines": [ "node >= 0.8.0" ], + "license": "Apache-2.0", "bin": { "ansi-html": "bin/ansi-html" } @@ -5114,6 +6951,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -5122,6 +6960,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -5133,6 +6972,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5141,10 +6981,34 @@ "node": ">= 8" } }, + "node_modules/apache-crypt": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.6.tgz", + "integrity": "sha512-072WetlM4blL8PREJVeY+WHiUh1R5VNt2HfceGS8aKqttPHcmqE5pkKuXPz/ULmJOFkc8Hw3kfKl6vy7Qka6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unix-crypt-td-js": "^1.1.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/apache-md5": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.8.tgz", + "integrity": "sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" }, "node_modules/arch": { "version": "2.2.0", @@ -5164,12 +7028,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/are-we-there-yet": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -5182,20 +7049,69 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } }, + "node_modules/argparse/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": "~2.1.0" } @@ -5205,6 +7121,7 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8" } @@ -5214,6 +7131,7 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5222,19 +7140,22 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, + "license": "ISC", "engines": { "node": ">= 4.0.0" } @@ -5253,6 +7174,7 @@ "url": "https://tidelift.com/funding/github/npm/autoprefixer" } ], + "license": "MIT", "dependencies": { "browserslist": "^4.21.4", "caniuse-lite": "^1.0.30001426", @@ -5271,25 +7193,44 @@ "postcss": "^8.1.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "*" } }, "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", + "dev": true, + "license": "MIT" }, "node_modules/babel-loader": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", + "license": "MIT", "dependencies": { "find-cache-dir": "^3.3.2", "schema-utils": "^4.0.0" @@ -5306,6 +7247,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "license": "BSD-3-Clause", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -5321,6 +7263,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.17.7", "@babel/helper-define-polyfill-provider": "^0.3.3", @@ -5334,6 +7277,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -5342,6 +7286,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.3.3", "core-js-compat": "^3.25.1" @@ -5354,6 +7299,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.3.3" }, @@ -5364,7 +7310,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -5383,26 +7330,57 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "license": "MIT" }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "tweetnacl": "^0.14.3" } }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "license": "MIT", "engines": { "node": "*" } @@ -5411,6 +7389,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -5422,6 +7401,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -5432,18 +7412,21 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -5467,6 +7450,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -5475,6 +7459,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -5482,12 +7467,26 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } }, "node_modules/body-parser/node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.4" }, @@ -5502,6 +7501,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" @@ -5510,12 +7510,14 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" }, "node_modules/bootstrap": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==", + "license": "MIT", "engines": { "node": ">=6" }, @@ -5524,25 +7526,52 @@ "popper.js": "^1.14.7" } }, + "node_modules/bootstrap.native": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/bootstrap.native/-/bootstrap.native-5.0.13.tgz", + "integrity": "sha512-SiiTxaK3LjuOjPaXEnDBQNY3w0t28Qdx6I8drortuFg6Ch3q6cWoOxlFHThcGOPewziVarQAA4WPE00GFQmbWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@thednp/event-listener": "^2.0.4", + "@thednp/shorty": "^2.0.0" + }, + "engines": { + "node": ">=16", + "pnpm": ">=8.6.0" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, "node_modules/browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", @@ -5557,6 +7586,7 @@ "url": "https://tidelift.com/funding/github/npm/browserslist" } ], + "license": "MIT", "dependencies": { "caniuse-lite": "^1.0.30001449", "electron-to-chromium": "^1.4.284", @@ -5588,6 +7618,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -5598,6 +7629,7 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -5605,12 +7637,14 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -5619,6 +7653,7 @@ "version": "17.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.4.tgz", "integrity": "sha512-Z/nL3gU+zTUjz5pCA5vVjYM8pmaw2kxM7JEiE0fv3w77Wj+sFbi70CrBruUWH0uNcEdvLDixFpgA2JM4F4DBjA==", + "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", @@ -5642,6 +7677,7 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", "engines": { "node": ">=12" } @@ -5651,6 +7687,7 @@ "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5659,6 +7696,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -5673,10 +7711,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -5685,14 +7733,15 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001620", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz", - "integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==", + "version": "1.0.30001643", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", + "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", "funding": [ { "type": "opencollective", @@ -5706,18 +7755,21 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -5730,12 +7782,14 @@ "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "license": "MIT" }, "node_modules/chart.js": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", - "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz", + "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==", + "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -5747,6 +7801,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", + "license": "MIT", "peerDependencies": { "chart.js": ">=3.0.0" } @@ -5756,10 +7811,72 @@ "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/choices.js": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/choices.js/-/choices.js-10.2.0.tgz", + "integrity": "sha512-8PKy6wq7BMjNwDTZwr3+Zry6G2+opJaAJDDA/j3yxvqSCnvkKe7ZIFfIyOhoc7htIWFhsfzF9tJpGUATcpUtPg==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.2.2", + "fuse.js": "^6.6.2", + "redux": "^4.2.0" + } + }, + "node_modules/choices.js": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/choices.js/-/choices.js-10.2.0.tgz", + "integrity": "sha512-8PKy6wq7BMjNwDTZwr3+Zry6G2+opJaAJDDA/j3yxvqSCnvkKe7ZIFfIyOhoc7htIWFhsfzF9tJpGUATcpUtPg==", + "dependencies": { + "deepmerge": "^4.2.2", + "fuse.js": "^6.6.2", + "redux": "^4.2.0" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -5770,6 +7887,7 @@ "url": "https://paulmillr.com/funding/" } ], + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -5790,14 +7908,16 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", "engines": { "node": ">=6.0" } @@ -5813,6 +7933,7 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], + "license": "MIT", "engines": { "node": ">=8" } @@ -5821,6 +7942,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", "engines": { "node": ">=6" } @@ -5829,6 +7951,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" }, @@ -5840,6 +7963,7 @@ "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", "engines": { "node": ">=6" }, @@ -5852,6 +7976,7 @@ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, + "license": "MIT", "dependencies": { "string-width": "^4.2.0" }, @@ -5867,6 +7992,7 @@ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", "dev": true, + "license": "MIT", "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" @@ -5882,6 +8008,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "license": "ISC", "engines": { "node": ">= 10" } @@ -5890,6 +8017,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -5903,6 +8031,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", "engines": { "node": ">=0.8" } @@ -5911,6 +8040,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -5920,10 +8050,18 @@ "node": ">=6" } }, + "node_modules/code-block-writer": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.1.tgz", + "integrity": "sha512-c5or4P6erEA69TxaxTNcHUNcIn+oyxSRTOWV+pSYF+z4epXqNvwvJ70XPGjPNgue83oAFAPBRQYwpAJ/Hpe/Sg==", + "dev": true, + "license": "MIT" + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -5931,12 +8069,14 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", "bin": { "color-support": "bin.js" } @@ -5944,13 +8084,25 @@ "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -5959,12 +8111,13 @@ } }, "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=18" } }, "node_modules/common-tags": { @@ -5972,6 +8125,7 @@ "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -5979,12 +8133,14 @@ "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -5996,6 +8152,7 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "license": "MIT", "dependencies": { "accepts": "~1.3.5", "bytes": "3.0.0", @@ -6013,6 +8170,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -6020,17 +8178,20 @@ "node_modules/compression/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/compression/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" }, "node_modules/concat-stream": { "version": "1.5.2", @@ -6039,16 +8200,24 @@ "engines": [ "node >= 0.8" ], + "license": "MIT", "dependencies": { "inherits": "~2.0.1", "readable-stream": "~2.0.0", "typedarray": "~0.0.5" } }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/concat-stream/node_modules/readable-stream": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -6061,25 +8230,62 @@ "node_modules/concat-stream/node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "license": "MIT", "engines": { "node": ">=0.8" } }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -6091,6 +8297,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6098,12 +8305,14 @@ "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6111,12 +8320,14 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, "node_modules/copy-anything": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "license": "MIT", "dependencies": { "is-what": "^3.14.1" }, @@ -6128,6 +8339,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "license": "MIT", "dependencies": { "fast-glob": "^3.2.11", "glob-parent": "^6.0.1", @@ -6151,6 +8363,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -6163,6 +8376,7 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", "hasInstallScript": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -6172,6 +8386,7 @@ "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0" }, @@ -6181,9 +8396,9 @@ } }, "node_modules/core-js-compat/node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", "funding": [ { "type": "opencollective", @@ -6198,11 +8413,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -6214,12 +8430,28 @@ "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -6235,6 +8467,7 @@ "version": "0.0.16", "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.16.tgz", "integrity": "sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A==", + "license": "Apache-2.0", "dependencies": { "chalk": "^4.1.0", "css-select": "^4.2.0", @@ -6248,6 +8481,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -6262,6 +8496,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -6277,6 +8512,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -6287,12 +8523,82 @@ "node_modules/critters/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/critters/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/critters/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/critters/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/critters/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/critters/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/critters/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -6300,12 +8606,23 @@ "node_modules/critters/node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "license": "MIT" + }, + "node_modules/critters/node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } }, "node_modules/critters/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -6317,6 +8634,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6326,10 +8644,18 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "dev": true, + "license": "MIT" + }, "node_modules/css-loader": { "version": "6.7.3", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.19", @@ -6352,14 +8678,16 @@ } }, "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", "nth-check": "^2.0.1" }, "funding": { @@ -6370,6 +8698,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -6381,6 +8710,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -6389,11 +8719,12 @@ } }, "node_modules/cypress": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz", - "integrity": "sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==", + "version": "13.13.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.13.1.tgz", + "integrity": "sha512-8F9UjL5MDUdgC/S5hr8CGLHbS5gGht5UOV184qc2pFny43fnkoaKxlzH/U6//zmGu/xRTaKimNfjknLT8+UDFg==", "dev": true, "hasInstallScript": true, + "license": "MIT", "dependencies": { "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", @@ -6434,7 +8765,7 @@ "request-progress": "^3.0.0", "semver": "^7.5.3", "supports-color": "^8.1.1", - "tmp": "~0.2.1", + "tmp": "~0.2.3", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -6449,13 +8780,15 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/cypress-promise/-/cypress-promise-1.1.0.tgz", "integrity": "sha512-DhIf5PJ/a0iY+Yii6n7Rbwq+9TJxU4pupXYzf9mZd8nPG0AzQrj9i+pqINv4xbI2EV1p+PKW3maCkR7oPG4GrA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cypress/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -6471,6 +8804,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -6487,6 +8821,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -6499,6 +8834,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -6510,13 +8846,41 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/cypress/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cypress/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } }, "node_modules/cypress/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6526,6 +8890,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -6536,11 +8901,18 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/d3-queue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-2.0.3.tgz", + "integrity": "sha512-ejbdHqZYEmk9ns/ljSbEcD6VRiuNwAkZMdFf6rsUb3vHROK5iMFd8xewDQnUVr6m/ba2BG63KmR/LySfsluxbg==", + "license": "BSD-3-Clause" + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" }, @@ -6548,16 +8920,72 @@ "node": ">=0.10" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/dayjs": { - "version": "1.11.11", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", - "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", - "dev": true + "version": "1.11.12", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz", + "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==", + "dev": true, + "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -6570,10 +8998,59 @@ } } }, + "node_modules/decache": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/decache/-/decache-4.6.2.tgz", + "integrity": "sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsite": "^1.0.0" + } + }, + "node_modules/deep-equal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.5.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "license": "BSD-2-Clause", "dependencies": { "execa": "^5.0.0" }, @@ -6585,6 +9062,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -6607,6 +9085,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -6618,6 +9097,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } @@ -6626,6 +9106,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", "dependencies": { "clone": "^1.0.2" }, @@ -6637,6 +9118,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -6653,15 +9135,35 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -6669,12 +9171,14 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6683,6 +9187,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -6691,6 +9196,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -6699,12 +9205,21 @@ "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "dev": true, + "license": "MIT" }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -6716,6 +9231,7 @@ "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -6724,13 +9240,15 @@ } }, "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, "funding": { "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" @@ -6745,14 +9263,17 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "domelementtype": "^2.2.0" + "domelementtype": "^2.3.0" }, "engines": { "node": ">= 4" @@ -6762,28 +9283,46 @@ } }, "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" }, "funding": { "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dot": { + "version": "2.0.0-beta.1", + "resolved": "https://registry.npmjs.org/dot/-/dot-2.0.0-beta.1.tgz", + "integrity": "sha512-kxM7fSnNQTXOmaeGuBSXM8O3fEsBb7XSDBllkGbRwa0lJSJTxxDE/4eSNGLKZUmlFw0f1vJ5qSV2BljrgQtgIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, + "license": "MIT", "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -6793,27 +9332,41 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.772", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.772.tgz", - "integrity": "sha512-jFfEbxR/abTTJA3ci+2ok1NTuOBBtB4jH+UT6PUmRN+DY3WSD4FFRsgoVQ+QNIJ0T7wrXwzsWCI2WKC46b++2A==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.1.tgz", + "integrity": "sha512-FKbOCOQ5QRB3VlIbl1LZQefWIYwszlBloaXcY2rbfpu9ioJnNh3TK03YtIDKDo3WKBi8u+YV4+Fn2CkEozgf4w==", + "license": "ISC" + }, + "node_modules/emitter-component": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.2.tgz", + "integrity": "sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -6822,6 +9375,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6830,6 +9384,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -6839,6 +9394,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -6852,14 +9408,16 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, + "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -6873,6 +9431,7 @@ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -6882,9 +9441,13 @@ } }, "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -6893,6 +9456,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", "engines": { "node": ">=6" } @@ -6900,12 +9464,14 @@ "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT" }, "node_modules/errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "license": "MIT", "optional": true, "dependencies": { "prr": "~1.0.1" @@ -6918,14 +9484,77 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -6937,6 +9566,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -6944,13 +9574,68 @@ "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-shim": { + "version": "0.35.8", + "resolved": "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.8.tgz", + "integrity": "sha512-Twf7I2v4/1tLoIXMT8HlqaBSS5H2wQTs2wx3MNYCI8K1R1/clXyCazrcVCPm/FuO9cyV8+leEaZOWD5C253NDg==", + "dev": true, + "license": "MIT" }, "node_modules/esbuild": { "version": "0.17.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.8.tgz", "integrity": "sha512-g24ybC3fWhZddZK6R3uD2iF/RIPnRpwJAqLov6ouX3hMbY4+tKolP0VMF3zuIYCaXun+yHwS5IPQ91N2BT191g==", "hasInstallScript": true, + "license": "MIT", "optional": true, "bin": { "esbuild": "bin/esbuild" @@ -6987,6 +9672,7 @@ "version": "0.17.8", "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.17.8.tgz", "integrity": "sha512-zCmpxv95E0FuCmvdw1K836UHnj4EdiQnFfjTby35y3LAjRPtXMj3sbHDRHjbD8Mqg5lTwq3knacr/1qIFU51CQ==", + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -6998,6 +9684,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -7005,12 +9692,14 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -7019,6 +9708,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -7027,10 +9717,20 @@ "node": ">=8.0.0" } }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -7043,6 +9743,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -7054,6 +9755,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -7062,6 +9764,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -7070,6 +9773,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -7078,30 +9782,51 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/event-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", + "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.1", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" + } + }, "node_modules/eventemitter-asyncresource": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", - "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==" + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "license": "MIT" }, "node_modules/eventemitter2": { "version": "6.4.7", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", "engines": { "node": ">=0.8.x" } @@ -7111,6 +9836,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -7134,6 +9860,7 @@ "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", "dev": true, + "license": "MIT", "dependencies": { "pify": "^2.2.0" }, @@ -7144,12 +9871,14 @@ "node_modules/exponential-backoff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "license": "Apache-2.0" }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -7191,19 +9920,52 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } }, "node_modules/express/node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.4" }, @@ -7214,16 +9976,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -7237,6 +10010,7 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -7249,6 +10023,7 @@ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -7271,17 +10046,33 @@ "dev": true, "engines": [ "node >=0.6.0" - ] + ], + "license": "MIT" + }, + "node_modules/fancy-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-2.0.0.tgz", + "integrity": "sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-support": "^1.1.3" + }, + "engines": { + "node": ">=10.13.0" + } }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -7296,12 +10087,14 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -7310,6 +10103,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -7322,6 +10116,7 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, + "license": "MIT", "dependencies": { "pend": "~1.2.0" } @@ -7330,6 +10125,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -7340,10 +10136,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7352,16 +10155,18 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.3.0", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~1.5.0", "unpipe": "~1.0.0" }, "engines": { @@ -7372,6 +10177,8 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -7379,12 +10186,15 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" }, "node_modules/find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "license": "MIT", "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -7401,6 +10211,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -7409,6 +10220,30 @@ "node": ">=8" } }, + "node_modules/flatbush": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/flatbush/-/flatbush-4.4.0.tgz", + "integrity": "sha512-cf6G+sfy/+/FLH7Ls1URQ5GCRlXgwgqUZiEsMNrMZqb3Us3EkKmzUlKbnyoBy/4wI4oLJ+8cyCQoKJIVm92Fmg==", + "license": "ISC", + "dependencies": { + "flatqueue": "^2.0.3" + } + }, + "node_modules/flatpickr": { + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz", + "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==", + "license": "MIT" + }, + "node_modules/flatqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/flatqueue/-/flatqueue-2.0.3.tgz", + "integrity": "sha512-RZCWZNkmxzUOh8jqEcEGZCycb3B8KAfpPwg3H//cURasunYxsg1eIvE+QDSjX+ZPHTIVfINfK1aLTrVKKO0i4g==", + "license": "ISC", + "engines": { + "node": ">= 12.17.0" + } + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -7419,6 +10254,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -7432,14 +10268,26 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", + "license": "(OFL-1.1 AND MIT)", "engines": { "node": ">=0.10.3" } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -7455,6 +10303,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", "engines": { "node": ">=14" }, @@ -7467,6 +10316,7 @@ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "*" } @@ -7476,6 +10326,7 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -7489,6 +10340,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7497,6 +10349,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", "engines": { "node": "*" }, @@ -7509,29 +10362,38 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true, + "license": "MIT" + }, "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, + "license": "MIT", "dependencies": { - "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=14.14" } }, "node_modules/fs-minipass": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -7540,9 +10402,10 @@ } }, "node_modules/fs-minipass/node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -7550,18 +10413,21 @@ "node_modules/fs-monkey": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==" + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", + "license": "Unlicense" }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -7574,14 +10440,55 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fuse.js": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz", + "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, "node_modules/gauge": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", @@ -7600,6 +10507,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -7608,6 +10516,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -7616,6 +10525,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -7634,6 +10544,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -7643,6 +10554,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -7653,11 +10565,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/getos": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", "dev": true, + "license": "MIT", "dependencies": { "async": "^3.2.0" } @@ -7667,6 +10598,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" } @@ -7675,6 +10607,8 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7693,6 +10627,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -7703,13 +10638,15 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" }, "node_modules/global-dirs": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "dev": true, + "license": "MIT", "dependencies": { "ini": "2.0.0" }, @@ -7725,6 +10662,7 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -7733,14 +10671,33 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/globby": { "version": "13.2.2", "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "license": "MIT", "dependencies": { "dir-glob": "^3.0.1", "fast-glob": "^3.3.0", @@ -7759,6 +10716,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "license": "MIT", "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -7769,17 +10727,72 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", "engines": { "node": ">=4" } @@ -7788,6 +10801,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -7799,6 +10813,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7810,6 +10825,23 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -7820,12 +10852,14 @@ "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -7837,6 +10871,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "license": "BSD", "dependencies": { "@assemblyscript/loader": "^0.10.1", "base64-js": "^1.2.0", @@ -7846,12 +10881,14 @@ "node_modules/hdr-histogram-percentiles-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", - "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==" + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "license": "MIT" }, "node_modules/hosted-git-info": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "license": "ISC", "dependencies": { "lru-cache": "^7.5.1" }, @@ -7863,6 +10900,7 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", "engines": { "node": ">=12" } @@ -7871,6 +10909,7 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "license": "MIT", "dependencies": { "inherits": "^2.0.1", "obuf": "^1.0.0", @@ -7878,15 +10917,23 @@ "wbuf": "^1.1.0" } }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/hpack.js/node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, "node_modules/hpack.js/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7900,12 +10947,14 @@ "node_modules/hpack.js/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/hpack.js/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -7923,22 +10972,82 @@ "type": "patreon", "url": "https://patreon.com/mdevils" } - ] + ], + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-auth": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-4.1.9.tgz", + "integrity": "sha512-kvPYxNGc9EKGTXvOMnTBQw2RZfuiSihK/mLw/a4pbtRueTE45S55Lw/3k5CktIf7Ak0veMKEIteDj4YkNmCzmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.4.3", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-auth-connect": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/http-auth-connect/-/http-auth-connect-1.0.6.tgz", + "integrity": "sha512-yaO0QSCPqGCjPrl3qEEHjJP+lwZ6gMpXLuCBE06eWwcXomkI5TARtu0kxf9teFuBj6iaV3Ybr15jaWUvbzNzHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/http-auth/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause" }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "license": "MIT" }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -7950,15 +11059,26 @@ "node": ">= 0.8" } }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-parser-js": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "license": "MIT" }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -7972,6 +11092,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", "dependencies": { "@tootallnate/once": "2", "agent-base": "6", @@ -7985,6 +11106,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -8009,6 +11131,7 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", @@ -8022,6 +11145,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", "dependencies": { "agent-base": "6", "debug": "4" @@ -8035,6 +11159,7 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=8.12.0" } @@ -8043,14 +11168,60 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", "dependencies": { "ms": "^2.0.0" } }, + "node_modules/i18next": { + "version": "23.12.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.12.2.tgz", + "integrity": "sha512-XIeh5V+bi8SJSWGL3jqbTEBW5oD6rbP5L+E7dVQh1MNTxxYef0x15rhJVcRb7oiuq4jLtgy2SD8eFlf6P2cmqg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next/node_modules/@babel/runtime": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", + "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/i18next/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -8062,6 +11233,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -8086,12 +11258,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -8100,6 +11274,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "license": "ISC", "dependencies": { "minimatch": "^9.0.0" }, @@ -8108,9 +11283,10 @@ } }, "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -8125,6 +11301,7 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "license": "MIT", "optional": true, "bin": { "image-size": "bin/image-size.js" @@ -8134,14 +11311,16 @@ } }, "node_modules/immutable": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", - "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==" + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "license": "MIT" }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -8157,6 +11336,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -8165,6 +11345,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -8173,6 +11354,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -8180,12 +11362,15 @@ "node_modules/infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC" }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -8194,12 +11379,14 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ini": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", + "license": "ISC", "engines": { "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } @@ -8208,6 +11395,7 @@ "version": "8.2.4", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "license": "MIT", "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -8233,6 +11421,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -8247,6 +11436,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8262,6 +11452,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -8272,12 +11463,14 @@ "node_modules/inquirer/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/inquirer/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -8286,6 +11479,7 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -8294,6 +11488,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -8302,14 +11497,31 @@ } }, "node_modules/inquirer/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -8318,28 +11530,73 @@ "node": ">= 12" } }, - "node_modules/ip-address/node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" - }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", "engines": { "node": ">= 10" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -8347,24 +11604,91 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-ci": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "dev": true, + "license": "MIT", "dependencies": { "ci-info": "^3.2.0" }, - "bin": { - "is-ci": "bin.js" + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8374,6 +11698,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -8388,6 +11713,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8396,6 +11722,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -8404,6 +11731,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -8416,6 +11744,7 @@ "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", "dev": true, + "license": "MIT", "dependencies": { "global-dirs": "^3.0.0", "is-path-inside": "^3.0.2" @@ -8431,6 +11760,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", "engines": { "node": ">=8" } @@ -8438,21 +11768,53 @@ "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -8461,6 +11823,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -8472,6 +11835,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, @@ -8479,10 +11843,44 @@ "node": ">=0.10.0" } }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -8490,16 +11888,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -8507,15 +11955,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-what": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==" + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "license": "MIT" }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -8524,19 +11987,23 @@ } }, "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8545,12 +12012,14 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -8559,6 +12028,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -8574,20 +12044,19 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -8599,6 +12068,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -8612,6 +12082,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -8620,6 +12091,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -8634,17 +12106,28 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "peer": true + "license": "MIT" + }, + "node_modules/jquery-ui": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.3.tgz", + "integrity": "sha512-D2YJfswSJRh/B8M/zCowDpNFfwsDmtfnMPwjJTyvl+CBqzpYwQ+gFYIbUUlzijy/Qvoy30H1YhoSui4MNYpRwA==", + "license": "MIT", + "dependencies": { + "jquery": ">=1.8.0 <4.0.0" + } }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -8656,12 +12139,14 @@ "node_modules/jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -8672,29 +12157,34 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -8705,13 +12195,15 @@ "node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "license": "MIT" }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -8725,7 +12217,8 @@ "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "engines": [ "node >= 0.2.0" - ] + ], + "license": "MIT" }, "node_modules/jsprim": { "version": "2.0.2", @@ -8735,6 +12228,7 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -8746,14 +12240,22 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "license": "MIT", "dependencies": { "source-map-support": "^0.5.5" } }, + "node_modules/keycharm": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.2.0.tgz", + "integrity": "sha512-i/XBRTiLqRConPKioy2oq45vbv04e8x59b0mnsIRQM+7Ec/8BC7UcL5pnC4FMeGb8KwG7q4wOMw7CtNZf5tiIg==", + "dev": true + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8762,6 +12264,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", "engines": { "node": ">= 8" } @@ -8771,6 +12274,7 @@ "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", "dev": true, + "license": "MIT", "engines": { "node": "> 0.8" } @@ -8778,22 +12282,35 @@ "node_modules/leaflet": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", - "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==" + "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==", + "license": "BSD-2-Clause" }, "node_modules/leaflet-draw": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/leaflet-draw/-/leaflet-draw-1.0.4.tgz", - "integrity": "sha512-rsQ6saQO5ST5Aj6XRFylr5zvarWgzWnrg46zQ1MEOEIHsppdC/8hnN8qMoFvACsPvTioAuysya/TVtog15tyAQ==" + "integrity": "sha512-rsQ6saQO5ST5Aj6XRFylr5zvarWgzWnrg46zQ1MEOEIHsppdC/8hnN8qMoFvACsPvTioAuysya/TVtog15tyAQ==", + "license": "MIT" + }, + "node_modules/leaflet-image": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/leaflet-image/-/leaflet-image-0.4.0.tgz", + "integrity": "sha512-J/vLCHiYNXlcQ/SZbHhj/VF5k3thxTryWijoqMO9sB20KV7hlMNUZDgxcDzXnfjk4hcYcFfGbveVc1tyQ9FgYw==", + "license": "BSD-2-Clause", + "dependencies": { + "d3-queue": "2.0.3" + } }, "node_modules/leaflet.locatecontrol": { "version": "0.79.0", "resolved": "https://registry.npmjs.org/leaflet.locatecontrol/-/leaflet.locatecontrol-0.79.0.tgz", - "integrity": "sha512-h64QIHFkypYdr90lkSfjKvPvvk8/b8UnP3m9WuoWdp5p2AaCWC0T1NVwyuj4rd5U4fBW3tQt4ppmZ2LceHMIDg==" + "integrity": "sha512-h64QIHFkypYdr90lkSfjKvPvvk8/b8UnP3m9WuoWdp5p2AaCWC0T1NVwyuj4rd5U4fBW3tQt4ppmZ2LceHMIDg==", + "license": "MIT" }, "node_modules/leaflet.markercluster": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz", "integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==", + "license": "MIT", "peerDependencies": { "leaflet": "^1.3.1" } @@ -8802,6 +12319,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "license": "Apache-2.0", "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -8827,6 +12345,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "license": "MIT", "dependencies": { "klona": "^2.0.4" }, @@ -8846,6 +12365,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "license": "MIT", "optional": true, "dependencies": { "pify": "^4.0.1", @@ -8859,6 +12379,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", "optional": true, "engines": { "node": ">=6" @@ -8868,6 +12389,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", "optional": true, "bin": { "semver": "bin/semver" @@ -8877,20 +12399,23 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/less/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/license-webpack-plugin": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "license": "ISC", "dependencies": { "webpack-sources": "^3.0.0" }, @@ -8906,13 +12431,15 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/listr2": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", "dev": true, + "license": "MIT", "dependencies": { "cli-truncate": "^2.1.0", "colorette": "^2.0.16", @@ -8940,20 +12467,23 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/listr2/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "license": "MIT", "engines": { "node": ">=6.11.5" } @@ -8962,6 +12492,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "license": "MIT", "engines": { "node": ">= 12.13.0" } @@ -8970,6 +12501,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -8980,28 +12512,33 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -9017,6 +12554,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -9031,6 +12569,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -9046,6 +12585,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -9056,12 +12596,14 @@ "node_modules/log-symbols/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/log-symbols/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -9070,6 +12612,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -9082,6 +12625,7 @@ "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", @@ -9100,6 +12644,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -9115,6 +12660,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -9126,13 +12672,15 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-update/node_modules/slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -9150,6 +12698,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -9159,18 +12708,61 @@ "node": ">=8" } }, + "node_modules/loglevel": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/macos-release": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", + "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/magic-string": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.29.0.tgz", "integrity": "sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==", + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.13" }, @@ -9182,6 +12774,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", "dependencies": { "semver": "^6.0.0" }, @@ -9196,6 +12789,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -9204,6 +12798,7 @@ "version": "10.2.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "license": "ISC", "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", @@ -9230,6 +12825,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "license": "ISC", "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" @@ -9242,6 +12838,7 @@ "version": "16.1.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "license": "ISC", "dependencies": { "@npmcli/fs": "^2.1.0", "@npmcli/move-file": "^2.0.0", @@ -9270,6 +12867,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -9281,6 +12879,7 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", "engines": { "node": ">=12" } @@ -9289,6 +12888,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -9300,6 +12900,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "license": "ISC", "dependencies": { "minipass": "^3.1.1" }, @@ -9311,6 +12912,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "license": "ISC", "dependencies": { "unique-slug": "^3.0.0" }, @@ -9322,6 +12924,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -9332,12 +12935,52 @@ "node_modules/make-fetch-happen/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/marked": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-7.0.3.tgz", + "integrity": "sha512-ev2uM40p0zQ/GbvqotfKcSWEa59fJwluGZj5dcaUOwDRrB1F3dncdXy8NWUApk4fi8atU3kTBOwjyjZ0ud0dxw==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/material-design-icons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/material-design-icons/-/material-design-icons-3.0.1.tgz", + "integrity": "sha512-t19Z+QZBwSZulxptEu05kIm+UyfIdJY1JDwI+nx02j269m6W414whiQz9qfvQIiLrdx71RQv+T48nHhuQXOCIQ==", + "license": "Apache-2.0" + }, + "node_modules/mathjax-full": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", + "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", + "license": "Apache-2.0", + "dependencies": { + "esm": "^3.2.25", + "mhchemparser": "^4.1.0", + "mj-context-menu": "^0.6.1", + "speech-rule-engine": "^4.0.6" + } }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9346,6 +12989,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "license": "Unlicense", "dependencies": { "fs-monkey": "^1.0.4" }, @@ -9356,17 +13000,20 @@ "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "license": "MIT" }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", "engines": { "node": ">= 8" } @@ -9375,16 +13022,30 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/mgrs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz", + "integrity": "sha512-awNbTOqCxK1DBGjalK3xqWIstBZgN6fxsMSiXLs9/spqWkF2pAhb2rrYCFSsr1/tT7PhcDGjZndG8SWYn0byYA==", + "license": "MIT" + }, + "node_modules/mhchemparser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz", + "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==", + "license": "Apache-2.0" + }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -9395,6 +13056,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -9406,6 +13068,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9414,6 +13077,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -9425,6 +13089,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -9433,6 +13098,7 @@ "version": "2.7.2", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz", "integrity": "sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==", + "license": "MIT", "dependencies": { "schema-utils": "^4.0.0" }, @@ -9450,12 +13116,14 @@ "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" }, "node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -9467,6 +13135,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9475,6 +13144,7 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "license": "ISC", "engines": { "node": ">=8" } @@ -9483,6 +13153,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -9494,6 +13165,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -9504,12 +13176,14 @@ "node_modules/minipass-collect/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/minipass-fetch": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "license": "MIT", "dependencies": { "minipass": "^3.1.6", "minipass-sized": "^1.0.3", @@ -9526,6 +13200,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -9536,12 +13211,14 @@ "node_modules/minipass-fetch/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -9553,6 +13230,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -9563,12 +13241,14 @@ "node_modules/minipass-flush/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/minipass-json-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "license": "MIT", "dependencies": { "jsonparse": "^1.3.1", "minipass": "^3.0.0" @@ -9578,6 +13258,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -9588,12 +13269,14 @@ "node_modules/minipass-json-stream/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -9605,6 +13288,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -9615,12 +13299,14 @@ "node_modules/minipass-pipeline/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -9632,6 +13318,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -9642,12 +13329,14 @@ "node_modules/minipass-sized/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -9660,6 +13349,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -9670,12 +13360,20 @@ "node_modules/minizlib/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/mj-context-menu": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", + "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==", + "license": "Apache-2.0" }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -9686,25 +13384,63 @@ "node_modules/mobx": { "version": "4.14.1", "resolved": "https://registry.npmjs.org/mobx/-/mobx-4.14.1.tgz", - "integrity": "sha512-Oyg7Sr7r78b+QPYLufJyUmxTWcqeQ96S1nmtyur3QL8SeI6e0TqcKKcxbG+sVJLWANhHQkBW/mDmgG5DDC4fdw==" + "integrity": "sha512-Oyg7Sr7r78b+QPYLufJyUmxTWcqeQ96S1nmtyur3QL8SeI6e0TqcKKcxbG+sVJLWANhHQkBW/mDmgG5DDC4fdw==", + "license": "MIT" }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, "engines": { - "node": "*" + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" } }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" }, "node_modules/multicast-dns": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" @@ -9716,7 +13452,8 @@ "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" }, "node_modules/nanoid": { "version": "3.3.7", @@ -9728,6 +13465,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -9739,6 +13477,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.3", @@ -9755,6 +13494,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -9767,6 +13507,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9774,12 +13515,14 @@ "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" }, "node_modules/ng2-charts": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-4.1.1.tgz", "integrity": "sha512-iHwXDbmX86lfeH8VRcsaW2tJATsuAZo4kvvC/Yk2l35zOHjevja1qBvO6BAibiDazi9r9aS6ZRJOqWPsz1pP2w==", + "license": "ISC", "dependencies": { "lodash-es": "^4.17.15", "tslib": "^2.3.0" @@ -9793,14 +13536,16 @@ } }, "node_modules/ng2-charts/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/ng2-cookies": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/ng2-cookies/-/ng2-cookies-1.0.12.tgz", "integrity": "sha512-oWRcKAAX81VrVb6ZVzLCf7mmaFM18eebsPOSxQG+bC3AtEm3LNT9zULfOE2Xwsddf5UM2STGcAocEKWNw84vSg==", + "license": "MIT", "engines": { "node": ">=4.1.0" } @@ -9809,6 +13554,7 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-16.2.0.tgz", "integrity": "sha512-7X6UhOKiaUeC2eTOsUqctZH0ZWKG8VlzDxWJhdEF1N3kPod46SU/Vn2l0hbt8EDwTjxHKfKyevU2idkYDoZYog==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -9819,15 +13565,17 @@ } }, "node_modules/ngx-toastr/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "!win32" @@ -9841,12 +13589,14 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "license": "MIT", "optional": true }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" } @@ -9855,6 +13605,7 @@ "version": "9.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", @@ -9879,6 +13630,7 @@ "version": "4.8.1", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "license": "MIT", "optional": true, "bin": { "node-gyp-build": "bin.js", @@ -9890,6 +13642,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9899,6 +13652,8 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9918,6 +13673,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9926,14 +13682,16 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "license": "MIT" }, "node_modules/nopt": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "license": "ISC", "dependencies": { "abbrev": "^1.0.0" }, @@ -9948,6 +13706,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^6.0.0", "is-core-module": "^2.8.1", @@ -9962,6 +13721,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -9970,14 +13730,22 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/nouislider": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.8.1.tgz", + "integrity": "sha512-93TweAi8kqntHJSPiSWQ1o/uZ29VWOmal9YKb6KKGGlCkugaNfAupT7o1qTHqdJvNQ7S0su5rO6qRFCjP8fxtw==", + "license": "MIT" + }, "node_modules/npm-bundled": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "license": "ISC", "dependencies": { "npm-normalize-package-bin": "^3.0.0" }, @@ -9989,6 +13757,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, @@ -10000,6 +13769,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -10008,6 +13778,7 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "license": "ISC", "dependencies": { "hosted-git-info": "^6.0.0", "proc-log": "^3.0.0", @@ -10022,6 +13793,7 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "license": "ISC", "dependencies": { "ignore-walk": "^6.0.0" }, @@ -10033,6 +13805,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "license": "ISC", "dependencies": { "npm-install-checks": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", @@ -10047,6 +13820,7 @@ "version": "14.0.5", "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", + "license": "ISC", "dependencies": { "make-fetch-happen": "^11.0.0", "minipass": "^5.0.0", @@ -10064,6 +13838,7 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", "engines": { "node": ">=12" } @@ -10072,6 +13847,7 @@ "version": "11.1.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "license": "ISC", "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^17.0.0", @@ -10097,6 +13873,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", "engines": { "node": ">=8" } @@ -10105,6 +13882,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -10118,9 +13896,10 @@ } }, "node_modules/npm-registry-fetch/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -10129,6 +13908,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -10140,6 +13920,8 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", @@ -10154,6 +13936,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -10161,10 +13944,70 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10172,12 +14015,15 @@ "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" }, "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -10189,6 +14035,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -10197,6 +14044,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -10205,6 +14053,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -10219,6 +14068,7 @@ "version": "8.4.1", "resolved": "https://registry.npmjs.org/open/-/open-8.4.1.tgz", "integrity": "sha512-/4b7qZNhv6Uhd7jjnREh1NjnPxlTq+XNWPG88Ydkj5AILcA5m3ajvcg57pB24EQjKv0dK62XnDqk9c/hkIG5Kg==", + "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -10231,10 +14081,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "dev": true, + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "license": "MIT", "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -10257,6 +14118,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -10271,6 +14133,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -10286,6 +14149,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -10296,12 +14160,14 @@ "node_modules/ora/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/ora/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -10310,6 +14176,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -10317,10 +14184,28 @@ "node": ">=8" } }, + "node_modules/os-name": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", + "integrity": "sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "macos-release": "^2.5.0", + "windows-release": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10329,12 +14214,14 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -10349,6 +14236,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -10360,6 +14248,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" }, @@ -10374,6 +14263,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" @@ -10386,6 +14276,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -10394,14 +14285,22 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "license": "BlueOak-1.0.0" + }, "node_modules/pacote": { "version": "15.0.8", "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.0.8.tgz", "integrity": "sha512-UlcumB/XS6xyyIMwg/WwMAyUmga+RivB5KgkRwA1hZNtrx+0Bt41KxHCvg1kr0pZ/ZeD8qjhW4fph6VaYRCbLw==", + "license": "ISC", "dependencies": { "@npmcli/git": "^4.0.0", "@npmcli/installed-package-contents": "^2.0.1", @@ -10431,12 +14330,14 @@ "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -10448,6 +14349,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -10465,6 +14367,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -10473,6 +14376,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "license": "MIT", "dependencies": { "entities": "^4.4.0" }, @@ -10484,6 +14388,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "license": "MIT", "dependencies": { "entities": "^4.3.0", "parse5": "^7.0.0", @@ -10493,34 +14398,25 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5-html-rewriting-stream/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "license": "MIT", "dependencies": { - "parse5": "^6.0.1" + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, "node_modules/parse5-sax-parser": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "license": "MIT", "dependencies": { "parse5": "^7.0.0" }, @@ -10528,29 +14424,27 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", "engines": { "node": ">=8" } @@ -10559,6 +14453,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10567,6 +14462,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", "engines": { "node": ">=8" } @@ -10574,12 +14470,14 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -10592,17 +14490,16 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/path-scurry/node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -10610,37 +14507,85 @@ "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "license": [ + "MIT", + "Apache2" + ], + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/pdfmake": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.2.10.tgz", + "integrity": "sha512-doipFnmE1UHSk+Z3wfQuVweVQqx2pE/Ns2G5gCqZmWwqjDj+mZHnZYH/ryXWoIfD+iVdZUAutgI/VHkTCN+Xrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@foliojs-fork/linebreak": "^1.1.1", + "@foliojs-fork/pdfkit": "^0.14.0", + "iconv-lite": "^0.6.3", + "xmldoc": "^1.1.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/pdfmake/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -10653,6 +14598,7 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10661,6 +14607,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", "integrity": "sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==", + "license": "MIT", "dependencies": { "eventemitter-asyncresource": "^1.0.0", "hdr-histogram-js": "^2.0.1", @@ -10674,6 +14621,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, @@ -10681,17 +14629,34 @@ "node": ">=8" } }, + "node_modules/png-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==", + "dev": true + }, "node_modules/popper.js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "license": "MIT", "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -10710,6 +14675,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -10723,6 +14689,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", "integrity": "sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==", + "license": "MIT", "dependencies": { "cosmiconfig": "^7.0.0", "klona": "^2.0.5", @@ -10744,6 +14711,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -10755,6 +14723,7 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^6.0.2", @@ -10771,6 +14740,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "license": "ISC", "dependencies": { "postcss-selector-parser": "^6.0.4" }, @@ -10785,6 +14755,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", "dependencies": { "icss-utils": "^5.0.0" }, @@ -10796,9 +14767,10 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -10810,13 +14782,15 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" }, "node_modules/prettier": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -10831,6 +14805,7 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "license": "MIT", "engines": { "node": ">=6" }, @@ -10838,10 +14813,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/proc-log": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -10851,6 +14837,7 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -10858,17 +14845,30 @@ "node_modules/process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==" + "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==", + "license": "MIT" + }, + "node_modules/proj4": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.11.0.tgz", + "integrity": "sha512-SasuTkAx8HnWQHfIyhkdUNJorSJqINHAN3EyMWYiQRVorftz9DHz650YraFgczwgtHOxqnfuDxSNv3C8MUnHeg==", + "license": "MIT", + "dependencies": { + "mgrs": "1.0.0", + "wkt-parser": "^1.3.3" + } }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC" }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -10877,10 +14877,21 @@ "node": ">=10" } }, + "node_modules/propagating-hammerjs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-1.5.0.tgz", + "integrity": "sha512-3PUXWmomwutoZfydC+lJwK1bKCh6sK6jZGB31RUX6+4EXzsbkDZrK4/sVR7gBrvJaEIwpTVyxQUAd29FKkmVdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hammerjs": "^2.0.8" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -10893,6 +14904,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -10901,25 +14913,39 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "license": "MIT", "optional": true }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -10929,6 +14955,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -10938,6 +14965,7 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.4" }, @@ -10952,7 +14980,8 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -10971,12 +15000,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -10985,6 +15016,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10993,6 +15025,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -11007,6 +15040,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -11015,6 +15049,8 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "license": "ISC", "dependencies": { "glob": "^10.2.2", "json-parse-even-better-errors": "^3.0.0", @@ -11029,6 +15065,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "license": "ISC", "dependencies": { "json-parse-even-better-errors": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" @@ -11041,27 +15078,27 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "license": "MIT", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/read-package-json/node_modules/glob": { - "version": "10.3.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", - "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.11.0" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -11070,14 +15107,16 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "license": "MIT", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/read-package-json/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -11089,9 +15128,10 @@ } }, "node_modules/read-package-json/node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -11100,6 +15140,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -11113,6 +15154,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -11120,20 +15162,32 @@ "node": ">=8.10.0" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/reflect-metadata": { "version": "0.1.14", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", - "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "license": "Apache-2.0" }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -11144,12 +15198,14 @@ "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" }, "node_modules/regenerator-transform": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.4" } @@ -11157,12 +15213,33 @@ "node_modules/regex-parser": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", - "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==" + "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "license": "MIT", "dependencies": { "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", @@ -11179,6 +15256,7 @@ "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "license": "BSD-2-Clause", "dependencies": { "jsesc": "~0.5.0" }, @@ -11194,11 +15272,18 @@ "jsesc": "bin/jsesc" } }, + "node_modules/regl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/regl/-/regl-2.1.0.tgz", + "integrity": "sha512-oWUce/aVoEvW5l2V0LK7O5KJMzUSKeiOwFuJehzpSFd43dO5spP9r+sSUfhKtsky4u6MCqWJaRL+abzExynfTg==", + "license": "MIT" + }, "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", "dev": true, + "license": "MIT", "dependencies": { "throttleit": "^1.0.0" } @@ -11207,6 +15292,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11215,6 +15301,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11222,12 +15309,14 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "license": "MIT", "dependencies": { "is-core-module": "^2.9.0", "path-parse": "^1.0.7", @@ -11244,6 +15333,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", "engines": { "node": ">=8" } @@ -11252,6 +15342,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "license": "MIT", "dependencies": { "adjust-sourcemap-loader": "^4.0.0", "convert-source-map": "^1.7.0", @@ -11267,6 +15358,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -11280,6 +15372,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -11288,6 +15381,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -11300,6 +15394,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -11308,21 +15403,25 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", - "dev": true + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -11337,6 +15436,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11346,6 +15446,8 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11365,6 +15467,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -11376,6 +15479,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -11398,6 +15502,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -11406,6 +15511,7 @@ "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^1.9.0" }, @@ -11416,7 +15522,27 @@ "node_modules/rxjs-compat": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.6.7.tgz", - "integrity": "sha512-szN4fK+TqBPOFBcBcsR0g2cmTTUF/vaFEOZNuSdfU8/pGFnNmmn2u8SystYXG1QMrjOPBc6XTKHMVfENDf6hHw==" + "integrity": "sha512-szN4fK+TqBPOFBcBcsR0g2cmTTUF/vaFEOZNuSdfU8/pGFnNmmn2u8SystYXG1QMrjOPBc6XTKHMVfENDf6hHw==", + "license": "Apache-2.0" + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/safe-buffer": { "version": "5.2.1", @@ -11435,22 +15561,44 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/safevalues": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz", - "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==" + "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==", + "license": "Apache-2.0" }, "node_modules/sass": { "version": "1.58.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.1.tgz", "integrity": "sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==", + "license": "MIT", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -11467,6 +15615,7 @@ "version": "13.2.0", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz", "integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==", + "license": "MIT", "dependencies": { "klona": "^2.0.4", "neo-async": "^2.6.2" @@ -11501,15 +15650,17 @@ } }, "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", - "optional": true + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "devOptional": true, + "license": "ISC" }, "node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -11527,12 +15678,14 @@ "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "license": "MIT" }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "license": "MIT", "dependencies": { "@types/node-forge": "^1.3.0", "node-forge": "^1" @@ -11545,6 +15698,7 @@ "version": "7.5.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -11559,6 +15713,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -11569,12 +15724,14 @@ "node_modules/semver/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -11598,6 +15755,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -11605,17 +15763,41 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -11624,6 +15806,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "license": "MIT", "dependencies": { "accepts": "~1.3.4", "batch": "0.6.1", @@ -11641,6 +15824,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -11649,6 +15833,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -11657,6 +15842,7 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "license": "MIT", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -11670,30 +15856,26 @@ "node_modules/serve-index/node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" }, "node_modules/serve-index/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/serve-index/node_modules/setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "engines": { - "node": ">= 0.6" - } + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "license": "ISC" }, "node_modules/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "license": "MIT", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -11707,12 +15889,14 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -11725,15 +15909,33 @@ "node": ">= 0.4" } }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", "dependencies": { "kind-of": "^6.0.2" }, @@ -11745,6 +15947,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -11756,6 +15959,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", "engines": { "node": ">=8" } @@ -11764,6 +15968,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -11780,12 +15985,14 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" }, "node_modules/slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -11798,6 +16005,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -11812,6 +16020,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -11827,6 +16036,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -11838,12 +16048,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -11853,16 +16065,27 @@ "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "license": "MIT", "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "license": "MIT", "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -11876,6 +16099,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "license": "MIT", "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", @@ -11889,6 +16113,7 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", "engines": { "node": ">= 8" } @@ -11897,6 +16122,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -11905,6 +16131,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "license": "MIT", "dependencies": { "abab": "^2.0.6", "iconv-lite": "^0.6.3", @@ -11925,6 +16152,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -11936,6 +16164,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -11945,6 +16174,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -11953,6 +16183,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -11961,26 +16192,30 @@ "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "node_modules/spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==" + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", + "license": "CC0-1.0" }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "license": "MIT", "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", @@ -11996,6 +16231,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "license": "MIT", "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", @@ -12005,16 +16241,54 @@ "wbuf": "^1.7.3" } }, + "node_modules/speech-rule-engine": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", + "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==", + "license": "Apache-2.0", + "dependencies": { + "commander": "9.2.0", + "wicked-good-xpath": "1.3.0", + "xmldom-sre": "0.1.31" + }, + "bin": { + "sre": "bin/sre" + } + }, + "node_modules/speech-rule-engine/node_modules/commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, + "license": "MIT", "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -12039,12 +16313,14 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ssri": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -12053,25 +16329,39 @@ } }, "node_modules/ssri/node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" + } + }, + "node_modules/stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "~0.1.1", + "through": "~2.3.4" } }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } @@ -12080,6 +16370,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -12094,6 +16385,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -12103,10 +16395,63 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -12119,6 +16464,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -12130,6 +16476,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -12138,6 +16485,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -12149,6 +16497,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -12156,18 +16505,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pan-zoom": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/svg-pan-zoom/-/svg-pan-zoom-3.6.1.tgz", + "integrity": "sha512-JaKkGHHfGvRrcMPdJWkssLBeWqM+Isg/a09H7kgNNajT1cX5AztDTNs+C8UzpCxjCTRrG34WbquwaovZbmSk9g==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "license": "MIT", "engines": { "node": ">=0.10" } }, + "node_modules/tablesort": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/tablesort/-/tablesort-5.3.0.tgz", + "integrity": "sha512-WkfcZBHsp47gVH9CBHG0ZXopriG01IA87arGrchvIe868d4RiXVvoYPS1zMq9IdW05kBs5iGsqxTABqLyWonbg==", + "dev": true, + "license": "MIT" + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -12176,6 +16541,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -12192,6 +16558,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -12203,6 +16570,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -12214,6 +16582,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", "engines": { "node": ">=8" } @@ -12221,12 +16590,14 @@ "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/terser": { "version": "5.16.3", "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.3.tgz", "integrity": "sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q==", + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", @@ -12244,6 +16615,7 @@ "version": "5.3.10", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", @@ -12277,6 +16649,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12292,6 +16665,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } @@ -12299,17 +16673,20 @@ "node_modules/terser-webpack-plugin/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" }, "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -12324,9 +16701,10 @@ } }, "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.31.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", - "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", + "version": "5.31.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.3.tgz", + "integrity": "sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==", + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -12343,12 +16721,14 @@ "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -12362,6 +16742,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12371,6 +16752,8 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -12390,6 +16773,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -12400,13 +16784,15 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "license": "MIT" }, "node_modules/throttleit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -12414,18 +16800,39 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" + }, + "node_modules/timezone": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/timezone/-/timezone-1.0.23.tgz", + "integrity": "sha512-yhQgk6qmSLB+TF8HGmApZAVI5bfzR1CoKUGr+WMZWmx75ED1uDewAZA8QMGCQ70TEv4GmM8pDB9jrHuxdaQ1PA==", + "license": "MIT" + }, + "node_modules/timezone": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/timezone/-/timezone-1.0.23.tgz", + "integrity": "sha512-yhQgk6qmSLB+TF8HGmApZAVI5bfzR1CoKUGr+WMZWmx75ED1uDewAZA8QMGCQ70TEv4GmM8pDB9jrHuxdaQ1PA==" + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true, + "license": "MIT" }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.14" } @@ -12434,6 +16841,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", "engines": { "node": ">=4" } @@ -12442,6 +16850,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -12453,6 +16862,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -12462,6 +16872,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -12477,28 +16888,61 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4.0.0" } }, + "node_modules/traverse": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.9.tgz", + "integrity": "sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "gopd": "^1.0.1", + "typedarray.prototype.slice": "^1.0.3", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "license": "MIT", "bin": { "tree-kill": "cli.js" } }, + "node_modules/ts-morph": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-22.0.0.tgz", + "integrity": "sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.23.0", + "code-block-writer": "^13.0.1" + } + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -12510,12 +16954,14 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true + "dev": true, + "license": "Unlicense" }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -12527,6 +16973,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -12535,15 +16982,115 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typed-assert": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", - "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==" + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "license": "MIT" }, "node_modules/typedarray": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.7.tgz", "integrity": "sha512-ueeb9YybpjhivjbHP2LdFDAjbS948fGEPj+ACAMs4xCMmh72OCOMQWBQKlaN4ZNQ04yfLSDLSx1tGRIoWimObQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.3.tgz", + "integrity": "sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-errors": "^1.3.0", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-offset": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12552,6 +17099,7 @@ "version": "4.9.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12560,15 +17108,53 @@ "node": ">=4.2.0" } }, + "node_modules/uglify-js": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.0.tgz", + "integrity": "sha512-wNKHUY2hYYkf6oSFfhwwiHo4WCHzHmzcXsqXYTN9ja3iApYIFbb2U6ics9hBcYLHcYGQoAlwnZlTrf3oF+BL/Q==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/underscore.template": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/underscore.template/-/underscore.template-0.1.7.tgz", + "integrity": "sha512-wQ25n9/J0Olo3A92QDbSCkD04FKRrjVOn6wooaFDKsrmV9alkc7vZ3MmldbVo4NbJCk9ycWSEcfGn4NzFZIcvA==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -12577,6 +17163,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -12589,22 +17176,54 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/unicode-property-aliases-ecmascript": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true, + "license": "MIT" + }, "node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" }, @@ -12616,6 +17235,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -12628,14 +17248,23 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } }, + "node_modules/unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -12645,14 +17274,15 @@ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "funding": [ { "type": "opencollective", @@ -12667,6 +17297,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "escalade": "^3.1.2", "picocolors": "^1.0.1" @@ -12682,6 +17313,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -12691,6 +17323,7 @@ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, + "license": "MIT", "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -12699,20 +17332,28 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -12721,6 +17362,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -12730,6 +17372,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -12738,6 +17381,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -12750,16 +17394,33 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, + "node_modules/vis": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/vis/-/vis-4.21.0.tgz", + "integrity": "sha512-jonDXTGm2mFU/X6Kg9pvkZEQtXh2J6+NlDJD1tDP7TDCFy+qNeKlsTcXKQtv4nAtUIiKo6sphCt4kbRlEKw75A==", + "deprecated": "Please consider using https://github.com/visjs", + "dev": true, + "license": "(Apache-2.0 OR MIT)", + "dependencies": { + "emitter-component": "^1.1.1", + "hammerjs": "^2.0.8", + "keycharm": "^0.2.0", + "moment": "^2.18.1", + "propagating-hammerjs": "^1.4.6" + } + }, "node_modules/watchpack": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -12772,6 +17433,7 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "license": "MIT", "dependencies": { "minimalistic-assert": "^1.0.0" } @@ -12780,6 +17442,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "license": "MIT", "dependencies": { "defaults": "^1.0.3" } @@ -12788,6 +17451,7 @@ "version": "5.76.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", + "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -12834,6 +17498,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.2.tgz", "integrity": "sha512-Wu+EHmX326YPYUpQLKmKbTyZZJIB8/n6R09pTmB03kJmnMsVPTo9COzHZFr01txwaCAuZvfBJE4ZCHRcKs5JaQ==", + "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.12", @@ -12861,6 +17526,7 @@ "version": "4.11.1", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -12915,6 +17581,7 @@ "version": "5.3.4", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -12937,6 +17604,7 @@ "version": "5.8.0", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", "wildcard": "^2.0.0" @@ -12949,6 +17617,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "license": "MIT", "engines": { "node": ">=10.13.0" } @@ -12957,6 +17626,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "license": "MIT", "dependencies": { "typed-assert": "^1.0.8" }, @@ -12977,6 +17647,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12992,6 +17663,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } @@ -12999,12 +17671,14 @@ "node_modules/webpack/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -13022,6 +17696,7 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -13035,6 +17710,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", "engines": { "node": ">=0.8.0" } @@ -13043,6 +17719,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/wellknown/-/wellknown-0.5.0.tgz", "integrity": "sha512-za5vTLuPF9nmrVOovYQwNEWE/PwJCM+yHMAj4xN1WWUvtq9OElsvKiPL0CR9rO8xhrYqL7NpI7IknqR8r6eYOg==", + "license": "BSD", "dependencies": { "concat-stream": "~1.5.0", "minimist": "~1.2.0" @@ -13055,6 +17732,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -13065,10 +17743,54 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wicked-good-xpath": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", + "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==", + "license": "MIT" + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -13076,12 +17798,43 @@ "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==" + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "license": "MIT" + }, + "node_modules/windows-release": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", + "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^4.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wkt-parser": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.3.3.tgz", + "integrity": "sha512-ZnV3yH8/k58ZPACOXeiHaMuXIiaTk1t0hSUVisbO0t4RjA5wPpUytcxeyiN2h+LZRrmuHIh/1UlrR9e7DHDvTw==", + "license": "MIT" + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -13099,6 +17852,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -13115,6 +17869,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -13129,6 +17884,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -13139,12 +17895,14 @@ "node_modules/wrap-ansi-cjs/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -13159,6 +17917,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -13169,17 +17928,20 @@ "node_modules/wrap-ansi/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -13196,10 +17958,30 @@ } } }, + "node_modules/xmldoc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.3.0.tgz", + "integrity": "sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + } + }, + "node_modules/xmldom-sre": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==", + "license": "(LGPL-2.0 or MIT)", + "engines": { + "node": ">=0.1" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", "engines": { "node": ">=10" } @@ -13207,12 +17989,14 @@ "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", "engines": { "node": ">= 6" } @@ -13221,6 +18005,7 @@ "version": "17.6.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -13238,6 +18023,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { "node": ">=12" } @@ -13247,23 +18033,33 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, + "node_modules/zepto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zepto/-/zepto-1.2.0.tgz", + "integrity": "sha512-C1x6lfvBICFTQIMgbt3JqMOno3VOtkWat/xEakLTOurskYIHPmzJrzd1e8BnmtdDVJlGuk5D+FxyCA8MPmkIyA==", + "dev": true, + "license": "MIT" + }, "node_modules/zone.js": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.8.tgz", "integrity": "sha512-82bctBg2hKcEJ21humWIkXRlLBBmrc3nN7DFh5LGGhcyycO2S7FN8NmdvlcKaGFDNVL4/9kFLmwmInTavdJERA==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" } }, "node_modules/zone.js/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" } } } diff --git a/frontend/package.json b/frontend/package.json index 0180308c46..b1f88f0619 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,8 +6,8 @@ "ng": "ng", "start": "ng serve", "build": "ng build", - "format": "prettier --config .prettierrc --ignore-path .prettierignore --write '**/*.{ts,html,scss}' '../contrib/*/frontend/**/*.{ts,html,scss}'", - "format:check": "prettier --config .prettierrc --ignore-path .prettierignore --check '**/*.{ts,html,scss}' '../contrib/*/frontend/**/*.{ts,html,scss}'", + "format": "prettier --config .prettierrc --ignore-path .prettierignore --write '**/*.{js, ts,html,scss}' '../contrib/*/frontend/**/*.{js, ts,html,scss}'", + "format:check": "prettier --config .prettierrc --ignore-path .prettierignore --check '**/*.{js, ts,html,scss}' '../contrib/*/frontend/**/*.{js, ts,html,scss}'", "cypress:open": "cypress open", "cypress:run": "cypress run", "docBuild": "npx compodoc -p tsconfig.doc.json -d ../docs/build/html/documentation_js/" @@ -27,6 +27,7 @@ "@angular/platform-browser": "15.1.1", "@angular/platform-browser-dynamic": "15.1.1", "@angular/router": "15.1.1", + "@bokeh/bokehjs": "3.4.1", "@circlon/angular-tree-component": "^11.0.2", "@ng-bootstrap/ng-bootstrap": "14.0.1", "@ng-select/ng-select": "^10.0.3", @@ -44,12 +45,15 @@ "chartjs-plugin-datalabels": "^2.2.0", "core-js": "^3.19.1", "fast-deep-equal": "^3.1.3", + "file-saver": "^2.0.5", "font-awesome": "^4.7.0", "leaflet": "~1.7.1", "leaflet-draw": "^1.0.4", + "leaflet-image": "^0.4.0", "leaflet.locatecontrol": "^0.79.0", "leaflet.markercluster": "^1.5.3", "lodash": "^4.17.21", + "material-design-icons": "^3.0.1", "moment": "^2.29.4", "ng2-charts": "^4.1.1", "ng2-cookies": "^1.0.12", @@ -62,6 +66,7 @@ "devDependencies": { "@angular-devkit/core": "^15.1.2", "@angular/compiler-cli": "15.1.1", + "@compodoc/compodoc": "^1.1.22", "@types/node": "^18.0.0", "cypress": "^13.4.0", "cypress-promise": "^1.1.0", diff --git a/frontend/src/app/GN2CommonModule/form/data-form.service.ts b/frontend/src/app/GN2CommonModule/form/data-form.service.ts index 4adeb7e89f..f144c9006b 100644 --- a/frontend/src/app/GN2CommonModule/form/data-form.service.ts +++ b/frontend/src/app/GN2CommonModule/form/data-form.service.ts @@ -17,6 +17,8 @@ export interface ParamsDict { [key: string]: any; } +export type Profile = GeoJSON.Feature; + export const FormatMapMime = new Map([ ['csv', 'text/csv'], ['json', 'application/json'], @@ -31,6 +33,10 @@ export class DataFormService { public config: ConfigService ) {} + getTaxhubAPI() { + return `${this.config.API_ENDPOINT}/taxhub${this.config.TAXHUB.API_PREFIX}`; + } + getNomenclature( codeNomenclatureType: string, regne?: string, @@ -140,36 +146,26 @@ export class DataFormService { }); } - getTaxonInfo(cd_nom: number, areasStatus?: Array) { + getTaxonInfo(cd_nom: number, fields?: Array, areasStatus?: Array) { let query_string = new HttpParams(); if (areasStatus) { query_string = query_string.append('areas_status', areasStatus.join(',')); } - return this._http.get(`${this.config.API_TAXHUB}/taxref/${cd_nom}`, { - params: query_string, - }); - } - - getTaxonAttributsAndMedia(cd_nom: number, id_attributs?: Array) { - let query_string = new HttpParams(); - if (id_attributs) { - id_attributs.forEach((id) => { - query_string = query_string.append('id_attribut', id.toString()); - }); + if (fields) { + query_string = query_string.append('fields', fields.join(',')); } - - return this._http.get(`${this.config.API_TAXHUB}/bibnoms/taxoninfo/${cd_nom}`, { + return this._http.get(`${this.getTaxhubAPI()}/taxref/${cd_nom}`, { params: query_string, }); } getTaxaBibList() { - return this._http.get(`${this.config.API_TAXHUB}/biblistes/`).pipe(map((d) => d.data)); + return this._http.get(`${this.getTaxhubAPI()}/biblistes/`).pipe(map((d) => d.data)); } async getTaxonInfoSynchrone(cd_nom: number): Promise { const response = await this._http - .get(`${this.config.API_TAXHUB}/taxref/${cd_nom}`) + .get(`${this.getTaxhubAPI()}/taxref/${cd_nom}`) .toPromise(); return response; } @@ -179,7 +175,7 @@ export class DataFormService { params = params.set('rank_limit', rank); params = params.set('fields', 'lb_auteur,nom_complet_html'); - let url = `${this.config.API_TAXHUB}/taxref/search/lb_nom`; + let url = `${this.getTaxhubAPI()}/taxref/search/lb_nom`; if (search) { url = `${url}/${search}`; } @@ -224,19 +220,19 @@ export class DataFormService { } getRegneAndGroup2Inpn() { - return this._http.get(`${this.config.API_TAXHUB}/taxref/regnewithgroupe2`); + return this._http.get(`${this.getTaxhubAPI()}/taxref/regnewithgroupe2`); } getGroup3Inpn() { - return this._http.get(`${this.config.API_TAXHUB}/taxref/groupe3_inpn`); + return this._http.get(`${this.getTaxhubAPI()}/taxref/groupe3_inpn`); } getTaxhubBibAttributes() { - return this._http.get(`${this.config.API_TAXHUB}/bibattributs/`); + return this._http.get(`${this.getTaxhubAPI()}/bibattributs/`); } getTaxonomyHabitat() { - return this._http.get(`${this.config.API_TAXHUB}/taxref/bib_habitats`); + return this._http.get(`${this.getTaxhubAPI()}/taxref/bib_habitats`); } getTypologyHabitat(id_list: number) { @@ -534,7 +530,7 @@ export class DataFormService { application === 'GeoNature' ? `${this.config.API_ENDPOINT}/${api}` : application === 'TaxHub' - ? `${this.config.API_TAXHUB}/${api}` + ? `${this.getTaxhubAPI()}/${api}` : api; if (data !== undefined) { @@ -639,11 +635,13 @@ export class DataFormService { } getStatusValues(statusType: String) { - return this._http.get(`${this.config.API_TAXHUB}/bdc_statuts/status_values/${statusType}`); + return this._http.get(`${this.getTaxhubAPI()}/bdc_statuts/status_values/${statusType}`); } - getProfile(cdRef) { - return this._http.get(`${this.config.API_ENDPOINT}/gn_profiles/valid_profile/${cdRef}`); + getProfile(cdRef): Observable { + return this._http.get( + `${this.config.API_ENDPOINT}/gn_profiles/valid_profile/${cdRef}` + ); } getPhenology(cdRef, idNomenclatureLifeStage?) { @@ -672,7 +670,7 @@ export class DataFormService { if (statusTypes) { queryString = queryString.set('codes', statusTypes.join(',')); } - return this._http.get(`${this.config.API_TAXHUB}/bdc_statuts/status_types`, { + return this._http.get(`${this.getTaxhubAPI()}/bdc_statuts/status_types`, { params: queryString, }); } diff --git a/frontend/src/app/GN2CommonModule/form/dynamic-form/dynamic-form.component.html b/frontend/src/app/GN2CommonModule/form/dynamic-form/dynamic-form.component.html index 88d1ec825e..04200a1793 100644 --- a/frontend/src/app/GN2CommonModule/form/dynamic-form/dynamic-form.component.html +++ b/frontend/src/app/GN2CommonModule/form/dynamic-form/dynamic-form.component.html @@ -208,25 +208,30 @@
- - + + +
diff --git a/frontend/src/app/GN2CommonModule/form/dynamic-form/dynamic-form.component.scss b/frontend/src/app/GN2CommonModule/form/dynamic-form/dynamic-form.component.scss index aa7f92eb3a..5c5ec31ac6 100644 --- a/frontend/src/app/GN2CommonModule/form/dynamic-form/dynamic-form.component.scss +++ b/frontend/src/app/GN2CommonModule/form/dynamic-form/dynamic-form.component.scss @@ -17,6 +17,17 @@ border: 0px !important; } +.radio-group-container { + padding-left: 0px; +} + +.border-red { + border-left: 4px solid red; + border-radius: 4px 0 0 4px; + outline: none; + padding-left: 10px; +} + // // factorisation patchs présents dans media et monitoring // diff --git a/frontend/src/app/GN2CommonModule/form/form.service.ts b/frontend/src/app/GN2CommonModule/form/form.service.ts index 47452a5457..6fe0312923 100644 --- a/frontend/src/app/GN2CommonModule/form/form.service.ts +++ b/frontend/src/app/GN2CommonModule/form/form.service.ts @@ -5,6 +5,8 @@ import { UntypedFormGroup, UntypedFormControl, FormControl, + Validators, + ValidationErrors, } from '@angular/forms'; import { Subscription, Observable, forkJoin } from 'rxjs'; import { distinctUntilChanged, map, filter, pairwise, tap, startWith } from 'rxjs/operators'; @@ -149,4 +151,115 @@ export class FormService { minControl.setValue(maxControl.value); } } + /** + * Validator function that checks if the reference control is not null, then the current control must not be null. + * + * @param {string[]} referenceControlNames - The name of the control in the same form group that holds the reference value. + * @return {ValidatorFn} A validator function + */ + + /** + * Validator function that checks if all of reference controls are not null, then the current control must not be null. + * + * @param {string[]} referenceControlNames - The names of the controls in the same form group that holds the reference value. + * @param {AbstractControl} currentControl - The control to be validated. + * @return {boolean} True if the validation is successful, false otherwise. + */ + areAllRefControlsNotNull( + referenceControlNames: string[], + currentControl: AbstractControl + ): boolean { + let validation: boolean = true; + referenceControlNames.forEach((referenceControlName) => { + const referenceControl = currentControl.parent.get(referenceControlName); + + // Throw an error if the reference control is null or undefined + if (referenceControl == null) throw Error('Reference formControl is null or undefined'); + + // Check if the reference control value is null or undefined + const refValueIsNullOrUndefined = + referenceControl.value == null || referenceControl.value == undefined; + // Check if the current control value is null or undefined + const currentControlValueIsNullOrUndefined = + currentControl.value == null || currentControl.value == undefined; + + // Return the validation result. + // Return a validation error if the reference control is not null or undefined and the current control is null + validation = + validation && (!refValueIsNullOrUndefined || !currentControlValueIsNullOrUndefined); + }); + return validation; + } + + /** + * Checks if any of the reference controls are not null. + * + * @param {string[]} referenceControlNames - The names of the controls in the same form group that holds the reference value. + * @param {AbstractControl} currentControl - The control to be validated. + * @return {boolean} True if any of the reference controls is not null, false otherwise. + */ + areAnyRefControlsNotNull( + referenceControlNames: string[], + currentControl: AbstractControl + ): boolean { + let result = false; + referenceControlNames.forEach((referenceControlName) => { + const referenceControl = currentControl.parent.get(referenceControlName); + + // Throw an error if the reference control is null or undefined + if (referenceControl == null) throw Error('Reference formControl is null or undefined'); + + if (referenceControl.value !== null && referenceControl.value !== undefined) { + result = true; + } + }); + return result; + } + + /** + * Generates a validator function that requires the current control if any of the reference controls is not null. + * + * @param {string[]} referenceControlNames - The name of the control in the same form group that holds the reference value. + * @return {ValidatorFn} The validator function. + */ + RequiredIfControlIsNotNullValidator( + referenceControlNames: string[], + entityControls: string[] + ): ValidatorFn { + return (currentControl: AbstractControl): ValidationErrors | null => { + if (!this.areAnyRefControlsNotNull(entityControls, currentControl)) { + return null; + } + return this.areAllRefControlsNotNull(referenceControlNames, currentControl) + ? Validators.required(currentControl) + : null; + }; + } + /** + * Generates a validator function that makes the current control not required if any of the reference controls is not null. + * + * @param {string[]} referenceControlNames - The name of the control in the same form group that holds the reference value. + * @return {ValidatorFn} The validator function. + */ + NotRequiredIfControlIsNotNullValidator( + referenceControlNames: string[], + entityControls: string[] + ): ValidatorFn { + return (currentControl: AbstractControl): ValidationErrors | null => { + if (!this.areAnyRefControlsNotNull(entityControls, currentControl)) { + return null; + } + return this.areAnyRefControlsNotNull(referenceControlNames, currentControl) + ? null + : Validators.required(currentControl); + }; + } + + NotRequiredIfNoOther(entityControls: string[]): ValidatorFn { + return (currentControl: AbstractControl): ValidationErrors | null => { + return this.areAnyRefControlsNotNull(entityControls, currentControl) + ? Validators.required(currentControl) + : null; + }; + } } diff --git a/frontend/src/app/GN2CommonModule/form/synthese-form/advanced-form/synthese-advanced-form-component.ts b/frontend/src/app/GN2CommonModule/form/synthese-form/advanced-form/synthese-advanced-form-component.ts index 4446ea07f2..b6703b0f36 100644 --- a/frontend/src/app/GN2CommonModule/form/synthese-form/advanced-form/synthese-advanced-form-component.ts +++ b/frontend/src/app/GN2CommonModule/form/synthese-form/advanced-form/synthese-advanced-form-component.ts @@ -8,6 +8,7 @@ import { DynamicFormService } from '@geonature_common/form/dynamic-form-generato import { SyntheseFormService } from '@geonature_common/form/synthese-form/synthese-form.service'; import { TaxonAdvancedStoreService } from '@geonature_common/form/synthese-form/advanced-form/synthese-advanced-form-store.service'; import { ConfigService } from '@geonature/services/config.service'; +import { DataFormService } from '@geonature_common/form/data-form.service'; @Component({ selector: 'pnx-validation-taxon-advanced', @@ -33,10 +34,11 @@ export class TaxonAdvancedModalComponent implements OnInit, AfterContentInit { public activeModal: NgbActiveModal, public formService: SyntheseFormService, public storeService: TaxonAdvancedStoreService, - public config: ConfigService + public config: ConfigService, + public _ds: DataFormService ) { // Set config parameters - this.URL_AUTOCOMPLETE = this.config.API_TAXHUB + '/taxref/search/lb_nom'; + this.URL_AUTOCOMPLETE = this._ds.getTaxhubAPI() + '/taxref/search/lb_nom'; const actionMapping: IActionMapping = { mouse: { diff --git a/frontend/src/app/GN2CommonModule/form/synthese-form/dynamicFormConfig.ts b/frontend/src/app/GN2CommonModule/form/synthese-form/dynamicFormConfig.ts index 73b67a05db..b6c1efb1fc 100644 --- a/frontend/src/app/GN2CommonModule/form/synthese-form/dynamicFormConfig.ts +++ b/frontend/src/app/GN2CommonModule/form/synthese-form/dynamicFormConfig.ts @@ -276,6 +276,12 @@ export const DYNAMIC_FORM_DEF = [ attribut_name: 'id_synthese', required: false, }, + { + type_widget: 'number', + attribut_label: 'ID import', + attribut_name: 'id_import', + required: false, + }, { type_widget: 'datalist', attribut_name: 'id_source', diff --git a/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts index 98fac70769..78a2f064f5 100644 --- a/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts +++ b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts @@ -55,6 +55,12 @@ export class SyntheseDataService { return this._api.get(`${this.config.API_ENDPOINT}/synthese/general_stats`); } + getSyntheseTaxonSheetStat(cd_ref: number, areaType: string = 'COM') { + return this._api.get(`${this.config.API_ENDPOINT}/synthese/taxon_stats/${cd_ref}`, { + params: new HttpParams().append('area_type', areaType), + }); + } + getTaxaCount(params = {}) { let queryString = new HttpParams(); for (let key in params) { @@ -223,8 +229,10 @@ export class SyntheseDataService { document.body.removeChild(link); } - getReports(params) { - return this._api.get(`${this.config.API_ENDPOINT}/synthese/reports?${params}`); + getReports(params, idSynthese = null) { + const baseUrl = `${this.config.API_ENDPOINT}/synthese/reports`; + const url = idSynthese ? `${baseUrl}/${idSynthese}` : baseUrl; + return this._api.get(`${url}?${params}`); } createReport(params) { diff --git a/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-form.service.ts b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-form.service.ts index 4635b6b128..d28de2ce04 100644 --- a/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-form.service.ts +++ b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-form.service.ts @@ -72,6 +72,7 @@ export class SyntheseFormService { observers_list: null, id_organism: null, id_dataset: null, + id_import: null, id_acquisition_framework: null, id_nomenclature_valid_status: null, modif_since_validation: [false, null], diff --git a/frontend/src/app/GN2CommonModule/form/taxonomy/taxonomy.component.ts b/frontend/src/app/GN2CommonModule/form/taxonomy/taxonomy.component.ts index cb722f31a4..40281d85f6 100644 --- a/frontend/src/app/GN2CommonModule/form/taxonomy/taxonomy.component.ts +++ b/frontend/src/app/GN2CommonModule/form/taxonomy/taxonomy.component.ts @@ -130,9 +130,9 @@ export class TaxonomyComponent implements OnInit, OnChanges { setApiEndPoint(idList) { if (idList) { - this.apiEndPoint = `${this.config.API_TAXHUB}/taxref/allnamebylist/${idList}`; + this.apiEndPoint = `${this._dfService.getTaxhubAPI()}/taxref/allnamebylist/${idList}`; } else { - this.apiEndPoint = `${this.config.API_TAXHUB}/taxref/allnamebylist`; + this.apiEndPoint = `${this._dfService.getTaxhubAPI()}/taxref/allnamebylist`; } } diff --git a/frontend/src/app/GN2CommonModule/map/leaflet-draw/leaflet-draw.component.ts b/frontend/src/app/GN2CommonModule/map/leaflet-draw/leaflet-draw.component.ts index 9689f65c4c..0225025ee0 100644 --- a/frontend/src/app/GN2CommonModule/map/leaflet-draw/leaflet-draw.component.ts +++ b/frontend/src/app/GN2CommonModule/map/leaflet-draw/leaflet-draw.component.ts @@ -151,14 +151,29 @@ export class LeafletDrawComponent implements OnInit, OnChanges { }); this.map.on(this._Le.Draw.Event.DRAWSTOP, (e) => { - if (this._currentGeojson) { - const layer: L.Layer = this.mapservice.createGeojson(this._currentGeojson.geometry, false); - if (!this.mapservice.leafletDrawFeatureGroup.hasLayer(layer)) { - this.loadDrawfromGeoJson(this._currentGeojson.geometry); - } + const geojson = this._currentGeojson?.geometry || this.geojson; + + if (this._currentDraw) { + this.handleDrawStopWithCurrentDraw(geojson); + } else { + this.handleDrawStopWithoutCurrentDraw(geojson); } }); } + handleDrawStopWithCurrentDraw(geojson: any): void { + if (!this.mapservice.leafletDrawFeatureGroup.hasLayer(this._currentDraw) && geojson) { + this.loadDrawfromGeoJson(geojson); + } + } + + handleDrawStopWithoutCurrentDraw(geojson: any): void { + this.mapservice.removeAllLayers(this.map, this.mapservice.fileLayerFeatureGroup); + const layer: L.Layer = this.mapservice.createGeojson(geojson, false); + + if (!this.mapservice.leafletDrawFeatureGroup.hasLayer(layer)) { + this.loadDrawfromGeoJson(geojson); + } + } getGeojsonFromFeatureGroup(layerType) { let geojson: any = this.mapservice.leafletDrawFeatureGroup.toGeoJSON(); diff --git a/frontend/src/app/GN2CommonModule/service/common.service.ts b/frontend/src/app/GN2CommonModule/service/common.service.ts index 77c2d47bac..60c21d672e 100644 --- a/frontend/src/app/GN2CommonModule/service/common.service.ts +++ b/frontend/src/app/GN2CommonModule/service/common.service.ts @@ -14,7 +14,7 @@ export class CommonService { private current: any = {}; - translateToaster(messageType: string, messageValue: string): void { + translateToaster(messageType: string, messageValue: string, parameters: Object = {}): void { // si toaster contenant le message est en cours on ne fait rien if (this.current[messageValue]) { return; @@ -23,7 +23,7 @@ export class CommonService { this.current[messageValue] = true; this.translate - .get(messageValue, { value: messageValue }) + .get(messageValue, parameters) .subscribe((res) => this.toastrService[messageType](res, '')); // on supprime le message de current au bout de 5s diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index f0d438fb3d..09c9f3798f 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -17,6 +17,10 @@ import { GN2CommonModule } from '@geonature_common/GN2Common.module'; import { AppComponent } from './app.component'; import { routing } from './routing/app-routing.module'; // RoutingModule import { HomeContentComponent } from './components/home-content/home-content.component'; +import { HomeDiscussionsTableComponent } from './components/home-content/home-discussions/home-discussions-table/home-discussions-table.component'; +import { HomeDiscussionsComponent } from './components/home-content/home-discussions/home-discussions.component'; +import { HomeDiscussionsToggleComponent } from './components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component'; + import { SidenavItemsComponent } from './components/sidenav-items/sidenav-items.component'; import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component'; import { NavHomeComponent } from './components/nav-home/nav-home.component'; @@ -51,7 +55,7 @@ export function createTranslateLoader(http: HttpClient) { import { UserDataService } from './userModule/services/user-data.service'; import { NotificationDataService } from './components/notification/notification-data.service'; -import { UserCasGuard, UserPublicGuard } from '@geonature/modules/login/routes-guard.service'; +import { UserPublicGuard } from '@geonature/modules/login/routes-guard.service'; export function loadConfig(injector) { const configService = injector.get(ConfigService); @@ -97,6 +101,7 @@ export function initApp(injector) { }, }), LoginModule, + HomeDiscussionsComponent, ], declarations: [ AppComponent, @@ -111,7 +116,6 @@ export function initApp(injector) { ], providers: [ AuthService, - UserCasGuard, AuthGuard, ModuleService, ToastrService, diff --git a/frontend/src/app/components/auth/auth.service.ts b/frontend/src/app/components/auth/auth.service.ts index 12f5a421df..fd9bdd6ae1 100644 --- a/frontend/src/app/components/auth/auth.service.ts +++ b/frontend/src/app/components/auth/auth.service.ts @@ -5,13 +5,12 @@ import { HttpClient } from '@angular/common/http'; import { CookieService } from 'ng2-cookies'; import 'rxjs/add/operator/delay'; -import { forkJoin } from 'rxjs'; -import { tap } from 'rxjs/operators'; import * as moment from 'moment'; import { CruvedStoreService } from '@geonature_common/service/cruved-store.service'; import { ModuleService } from '../../services/module.service'; import { RoutingService } from '@geonature/routing/routing.service'; import { ConfigService } from '@geonature/services/config.service'; +import { Provider } from '@geonature/modules/login/providers'; export interface User { user_login: string; @@ -20,6 +19,7 @@ export interface User { prenom_role?: string; nom_role?: string; nom_complet?: string; + providers?: string[]; } @Injectable() @@ -39,12 +39,23 @@ export class AuthService { private _routingService: RoutingService, private moduleService: ModuleService, public config: ConfigService - ) {} + ) { + this.refreshCurrentUserData(); + } + refreshCurrentUserData() { + if (!this.currentUser) { + this.currentUser = this.getCurrentUser(); + } + } setCurrentUser(user) { localStorage.setItem(this.prefix + 'current_user', JSON.stringify(user)); } + getAuthProviders(): Observable> { + return this._http.get>(`${this.config.API_ENDPOINT}/auth/providers`); + } + getCurrentUser() { let currentUser = localStorage.getItem(this.prefix + 'current_user'); return JSON.parse(currentUser); @@ -67,6 +78,7 @@ export class AuthService { nom_role: data.user.nom_role, nom_complet: data.user.nom_role + ' ' + data.user.prenom_role, id_organisme: data.user.id_organisme, + providers: data.user.providers.map((provider) => provider.name), }; this.setCurrentUser(userForFront); this.loginError = false; @@ -77,13 +89,8 @@ export class AuthService { localStorage.setItem(this.prefix + 'expires_at', authResult.expires); } - signinUser(user: any) { - const options = { - login: user.username, - password: user.password, - }; - - return this._http.post(`${this.config.API_ENDPOINT}/auth/login`, options); + signinUser(form: any) { + return this._http.post(`${this.config.API_ENDPOINT}/auth/login`, form); } signinPublicUser(): Observable { @@ -134,16 +141,8 @@ export class AuthService { logout() { this.cleanLocalStorage(); this.cruvedService.clearCruved(); - // call the logout route to delete the session - this._http.get(`${this.config.API_ENDPOINT}/auth/logout`).subscribe(() => { - location.reload(); - }); - - if (this.config.CAS_PUBLIC.CAS_AUTHENTIFICATION) { - document.location.href = `${this.config.CAS_PUBLIC.CAS_URL_LOGOUT}?service=${this.config.URL_APPLICATION}`; - } else { - this.router.navigate(['/login']); - } + let logout_url = `${this.config.API_ENDPOINT}/auth/logout`; + location.href = logout_url; } private cleanLocalStorage() { @@ -170,4 +169,9 @@ export class AuthService { disableLoader() { this.isLoading = false; } + + canBeLoggedWithLocalProvider(): boolean { + this.refreshCurrentUserData(); + return this.currentUser.providers.includes('local_provider'); + } } diff --git a/frontend/src/app/components/home-content/home-content.component.html b/frontend/src/app/components/home-content/home-content.component.html index 5edfdd29ad..df02967b13 100644 --- a/frontend/src/app/components/home-content/home-content.component.html +++ b/frontend/src/app/components/home-content/home-content.component.html @@ -38,7 +38,20 @@ - +
+ + + + + + +
= new Subject(); public cluserOrSimpleFeatureGroup = null; + @ViewChild('table') + table: DatatableComponent; + discussions = []; + columns = []; + currentPage = 1; + perPage = 2; + totalPages = 1; + totalRows: Number; + myReportsOnly = false; + sort = 'desc'; + orderby = 'date'; + params: URLSearchParams = new URLSearchParams(); constructor( private _SideNavService: SideNavService, private _syntheseApi: SyntheseDataService, private _mapService: MapService, private _moduleService: ModuleService, private translateService: TranslateService, - public config: ConfigService + public config: ConfigService, + private datePipe: DatePipe ) { // this work here thanks to APP_INITIALIZER on ModuleService let synthese_module = this._moduleService.getModule('SYNTHESE'); @@ -74,6 +89,10 @@ export class HomeContentComponent implements OnInit, AfterViewInit { this.destroy$.unsubscribe(); } + get isExistBlockToDisplay(): boolean { + return this.config.HOME.DISPLAY_LATEST_DISCUSSIONS; // NOTES [projet ARB]: ajouter les autres config à afficher ici || this.config.HOME.DISPLAY_LATEST_VALIDATIONS ..; + } + private computeMapBloc() { this.cluserOrSimpleFeatureGroup.addTo(this._mapService.map); this._syntheseApi diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.html b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.html new file mode 100644 index 0000000000..cfb2340e53 --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.html @@ -0,0 +1,133 @@ + diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.scss b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.ts b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.ts new file mode 100644 index 0000000000..11cb0c39a9 --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.ts @@ -0,0 +1,73 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'; +import { Router } from '@angular/router'; +import { GN2CommonModule } from '@geonature_common/GN2Common.module'; +import { DatatableComponent } from '@swimlane/ngx-datatable'; + +@Component({ + standalone: true, + selector: 'pnx-home-discussions-table', + templateUrl: './home-discussions-table.component.html', + styleUrls: ['./home-discussions-table.component.scss'], + imports: [GN2CommonModule, CommonModule], +}) +export class HomeDiscussionsTableComponent { + @Input() discussions = []; + @Input() currentPage = 1; + @Input() perPage = 2; + @Input() totalPages = 1; + @Input() totalRows = 0; + @Input() totalFilteredRows = 0; + headerHeight: number = 50; + footerHeight: number = 50; + rowHeight: string | number = 'auto'; + limit: number = 10; + count: number = 0; + offset: number = 0; + columnMode: string = 'force'; + rowDetailHeight: number = 150; + columns = []; + sort = 'desc'; + orderby = 'creation_date'; + + @Output() sortChange = new EventEmitter(); + @Output() orderbyChange = new EventEmitter(); + @Output() currentPageChange = new EventEmitter(); + + @ViewChild('table', { static: false }) table: DatatableComponent | undefined; + + constructor(private _router: Router) {} + + ngOnInit() { + this.columns = this.getColumnsConfig(); + } + + handleExpandRow(row: any) { + this.table.rowDetail.toggleExpandRow(row); + } + + handlePageChange(event: any) { + this.currentPage = event.page; + this.currentPageChange.emit(this.currentPage); + } + + onColumnSort(event: any) { + this.sort = event.sorts[0].dir; + this.orderby = event.sorts[0].prop; + this.sortChange.emit({ sort: this.sort, orderby: this.orderby }); + } + + onRowClick(row: any) { + // TODO: ajouter au chemin 'discussions' une fois que la PR https://github.com/PnX-SI/GeoNature/pull/3169 a été reviewed et mergé + this._router.navigate(['/synthese', 'occurrence', row.id_synthese]); + } + + getColumnsConfig() { + return [ + { prop: 'creation_date', name: 'Date commentaire', sortable: true }, + { prop: 'user.nom_complet', name: 'Auteur', sortable: true }, + { prop: 'content', name: 'Contenu', sortable: true }, + { prop: 'observation', name: 'Observation', sortable: false, maxWidth: 500 }, + ]; + } +} diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.html b/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.html new file mode 100644 index 0000000000..f02b2c2902 --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.html @@ -0,0 +1,6 @@ + + Mes discussions uniquement + diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.scss b/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.ts b/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.ts new file mode 100644 index 0000000000..8ea32fb8fc --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.ts @@ -0,0 +1,18 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { GN2CommonModule } from '@geonature_common/GN2Common.module'; + +@Component({ + standalone: true, + selector: 'pnx-home-discussions-toggle', + templateUrl: './home-discussions-toggle.component.html', + styleUrls: ['./home-discussions-toggle.component.scss'], + imports: [GN2CommonModule], +}) +export class HomeDiscussionsToggleComponent { + @Input() isChecked: boolean = false; + @Output() toggle = new EventEmitter(); + + onToggle(event: any): void { + this.toggle.emit(event.checked); + } +} diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions.component.html b/frontend/src/app/components/home-content/home-discussions/home-discussions.component.html new file mode 100644 index 0000000000..b98d5b77c1 --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions.component.html @@ -0,0 +1,16 @@ +
+ +
+ diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions.component.scss b/frontend/src/app/components/home-content/home-discussions/home-discussions.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions.component.ts b/frontend/src/app/components/home-content/home-discussions/home-discussions.component.ts new file mode 100644 index 0000000000..637bb33d0c --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions.component.ts @@ -0,0 +1,124 @@ +import { Component, OnInit, ViewChild, OnDestroy, Input } from '@angular/core'; +import { SyntheseDataService } from '@geonature_common/form/synthese-form/synthese-data.service'; +import { ConfigService } from '@geonature/services/config.service'; +import { DatatableComponent } from '@swimlane/ngx-datatable'; +import { DatePipe } from '@angular/common'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { HomeDiscussionsTableComponent } from './home-discussions-table/home-discussions-table.component'; +import { HomeDiscussionsToggleComponent } from './home-discussions-toggle/home-discussions-toggle.component'; + +@Component({ + standalone: true, + selector: 'pnx-home-discussions', + templateUrl: './home-discussions.component.html', + styleUrls: ['./home-discussions.component.scss'], + providers: [DatePipe], + imports: [HomeDiscussionsTableComponent, HomeDiscussionsToggleComponent], +}) +export class HomeDiscussionsComponent implements OnInit, OnDestroy { + @ViewChild('table') table: DatatableComponent; + + discussions = []; + currentPage = 1; + perPage = 2; + totalPages = 1; + totalRows = 0; + totalFilteredRows = 0; + myReportsOnly = false; + sort = 'desc'; + orderby = 'creation_date'; + private destroy$ = new Subject(); + + constructor( + private syntheseApi: SyntheseDataService, + public config: ConfigService, + private datePipe: DatePipe + ) {} + + async ngOnInit() { + this.getDiscussions(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + getDiscussions() { + const params = this.buildQueryParams(); + this.syntheseApi + .getReports(params.toString()) + .pipe(takeUntil(this.destroy$)) + .subscribe((response) => { + this.setDiscussions(response); + }); + } + + buildQueryParams(): URLSearchParams { + const params = new URLSearchParams(); + params.set('type', 'discussion'); + params.set('sort', this.sort); + params.set('orderby', this.orderby); + params.set('page', this.currentPage.toString()); + params.set('per_page', this.perPage.toString()); + params.set('my_reports', this.myReportsOnly.toString()); + return params; + } + + setDiscussions(data: any) { + this.discussions = this.transformDiscussions(data.items); + this.totalRows = data.total; + this.totalPages = data.pages; + this.totalFilteredRows = data.total_filtered; + } + + transformDiscussions(items: any[]): any[] { + return items.map((item) => ({ + ...item, + observation: this.formatObservation(item.synthese), + })); + } + + formatObservation(synthese: any): string { + return ` + Nom Cité: ${synthese.nom_cite || 'N/A'}
+ Observateurs: ${synthese.observers || 'N/A'}
+ Date Observation: ${ + this.formatDateRange(synthese.date_min, synthese.date_max) || 'N/A' + } + `; + } + + toggleMyReports(isMyReports: boolean) { + this.myReportsOnly = isMyReports; + this.currentPage = 1; + this.getDiscussions(); + } + + formatDateRange(dateMin: string, dateMax: string): string { + if (!dateMin) return 'N/A'; + + const formattedDateMin = this.datePipe.transform(dateMin, 'dd-MM-yyyy'); + const formattedDateMax = this.datePipe.transform(dateMax, 'dd-MM-yyyy'); + + if (!dateMax || formattedDateMin === formattedDateMax) { + return formattedDateMin || 'N/A'; + } + + return `${formattedDateMin} - ${formattedDateMax}`; + } + + // Event handlers for updates from the child component + // NOTES: utilisation de service à la place ? + onSortChange(sortAndOrberby: { sort: string; orderby: string }) { + this.sort = sortAndOrberby.sort; + this.orderby = sortAndOrberby.orderby; + this.getDiscussions(); + } + + onCurrentPageChange(newPage: number) { + this.currentPage = newPage; + this.getDiscussions(); + } +} diff --git a/frontend/src/app/components/nav-home/nav-home.component.html b/frontend/src/app/components/nav-home/nav-home.component.html index ef6eca94f3..f824de2be6 100644 --- a/frontend/src/app/components/nav-home/nav-home.component.html +++ b/frontend/src/app/components/nav-home/nav-home.component.html @@ -53,8 +53,7 @@

{{ config.appName }}

> diff --git a/frontend/src/app/components/nav-home/nav-home.component.ts b/frontend/src/app/components/nav-home/nav-home.component.ts index b7f80c2d35..7c4077ea68 100644 --- a/frontend/src/app/components/nav-home/nav-home.component.ts +++ b/frontend/src/app/components/nav-home/nav-home.component.ts @@ -24,6 +24,7 @@ export class NavHomeComponent implements OnInit, OnDestroy { public locale: string; public moduleUrl: string; public notificationNumber: number; + public useLocalProvider: boolean; // Indicate if the user is logged in using a non local provider @ViewChild('sidenav', { static: true }) public sidenav: MatSidenav; @@ -50,6 +51,7 @@ export class NavHomeComponent implements OnInit, OnDestroy { // Put the user name in navbar this.currentUser = this.authService.getCurrentUser(); + this.useLocalProvider = this.authService.canBeLoggedWithLocalProvider(); } private extractLocaleFromUrl() { diff --git a/frontend/src/app/components/sidenav-items/sidenav-items.component.html b/frontend/src/app/components/sidenav-items/sidenav-items.component.html index ac1a833ee3..f8cda29fc6 100644 --- a/frontend/src/app/components/sidenav-items/sidenav-items.component.html +++ b/frontend/src/app/components/sidenav-items/sidenav-items.component.html @@ -27,24 +27,42 @@ {{ home_page.module_label }} + + + + + {{ module.module_label }} + + - - - - {{ module.module_label }} - - + + + {{ module.module_label }} + + +