From 130d76c8a125a05f7a1b25d55c4f4bd80de0ed23 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 17 Sep 2024 12:00:37 +0200 Subject: [PATCH 001/103] WIP : first commit --- antarest/study/business/link_management.py | 58 +++++++++- .../rawstudy/model/filesystem/config/links.py | 35 ++++++ tests/integration/test_integration.py | 8 ++ .../storage/business/test_arealink_manager.py | 106 +++++++++++++++++- 4 files changed, 196 insertions(+), 11 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 6f696d2a5e..fdaff6f651 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -19,8 +19,10 @@ from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy -from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties +from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties, LinkStyle, \ + TransmissionCapacity, AssetType from antarest.study.storage.storage_service import StudyStorageService +from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_link import CreateLink from antarest.study.storage.variantstudy.model.command.remove_link import RemoveLink from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig @@ -31,12 +33,20 @@ class LinkUIDTO(BaseModel): color: str width: float - style: str + style: LinkStyle class LinkInfoDTO(BaseModel): area1: str area2: str + hurdles_cost: t.Optional[bool] = False + loop_flow: t.Optional[bool] = False + use_phase_shifter: t.Optional[bool] = False + transmission_capacities: t.Optional[TransmissionCapacity] = "enabled" + asset_type: t.Optional[AssetType] = "ac" + display_comments: t.Optional[bool] = True + filter_synthesis: t.Optional[str] = FilteringOptions.FILTER_SYNTHESIS + filter_year_by_year: t.Optional[str] = FilteringOptions.FILTER_YEAR_BY_YEAR ui: t.Optional[LinkUIDTO] = None @@ -56,9 +66,7 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn file_study = self.storage_service.get_storage(study).get_raw(study) result = [] for area_id, area in file_study.config.areas.items(): - links_config: t.Optional[t.Dict[str, t.Any]] = None - if with_ui: - links_config = file_study.tree.get(["input", "links", area_id, "properties"]) + links_config = file_study.tree.get(["input", "links", area_id, "properties"]) for link in area.links: ui_info: t.Optional[LinkUIDTO] = None if with_ui and links_config and link in links_config: @@ -67,7 +75,21 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn width=links_config[link].get("link-width", 1), style=links_config[link].get("link-style", "plain"), ) - result.append(LinkInfoDTO(area1=area_id, area2=link, ui=ui_info)) + result.append( + LinkInfoDTO( + area1=area_id, + area2=link, + hurdles_cost=links_config[link].get("hurdles-cost"), + loop_flow=links_config[link].get("loop-flow"), + use_phase_shifter=links_config[link].get("use-phase-shifter"), + transmission_capacities=links_config[link].get("transmission-capacities"), + asset_type=links_config[link].get("asset-type"), + display_comments=links_config[link].get("display-comments"), + filter_synthesis=links_config[link].get("filter-synthesis"), + filter_year_by_year=links_config[link].get("filter-year-by-year"), + ui=ui_info + ) + ) return result @@ -77,12 +99,21 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkI command = CreateLink( area1=link_creation_info.area1, area2=link_creation_info.area2, + parameters=self.create_parameters(link_creation_info), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) execute_or_add_commands(study, file_study, [command], self.storage_service) return LinkInfoDTO( area1=link_creation_info.area1, area2=link_creation_info.area2, + hurdles_cost=link_creation_info.hurdles_cost, + loop_flow=link_creation_info.loop_flow, + use_phase_shifter=link_creation_info.use_phase_shifter, + transmission_capacities=link_creation_info.transmission_capacities, + asset_type=link_creation_info.asset_type, + display_comments=link_creation_info.display_comments, + filter_synthesis=link_creation_info.filter_synthesis, + filter_year_by_year=link_creation_info.filter_year_by_year ) def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: @@ -157,3 +188,18 @@ def update_links_props( @staticmethod def get_table_schema() -> JSON: return LinkOutput.schema() + + @staticmethod + def create_parameters(link_creation_info: LinkInfoDTO) -> dict[str, str]: + parameters = { + "hurdles-cost": link_creation_info.hurdles_cost, + "loop-flow": link_creation_info.loop_flow, + "use-phase-shifter": link_creation_info.use_phase_shifter, + "transmission-capacities": link_creation_info.transmission_capacities, + "asset-type": link_creation_info.asset_type, + "display-comments": link_creation_info.display_comments, + "filter-synthesis": link_creation_info.filter_synthesis, + "filter-year-by-year": link_creation_info.filter_year_by_year + } + + return parameters \ No newline at end of file diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/links.py b/antarest/study/storage/rawstudy/model/filesystem/config/links.py index 88059ef50e..b9fd25ccac 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/links.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/links.py @@ -65,6 +65,41 @@ class TransmissionCapacity(EnumIgnoreCase): IGNORE = "ignore" ENABLED = "enabled" +class LinkStyle(EnumIgnoreCase): + """ + Enum representing the style of a link in a network visualization. + + Attributes: + DOT: Represents a dotted line style. + PLAIN: Represents a solid line style. + DASH: Represents a dashed line style. + DOT_DASH: Represents a line style with alternating dots and dashes. + """ + + DOT = "dot" + PLAIN = "plain" + DASH = "dash" + DOT_DASH = "dotdash" + + +class FilterOption(EnumIgnoreCase): + """ + Enum representing the time filter options for data visualization or analysis in Antares Web. + + Attributes: + HOURLY: Represents filtering data by the hour. + DAILY: Represents filtering data by the day. + WEEKLY: Represents filtering data by the week. + MONTHLY: Represents filtering data by the month. + ANNUAL: Represents filtering data by the year. + """ + + HOURLY = "hourly" + DAILY = "daily" + WEEKLY = "weekly" + MONTHLY = "monthly" + ANNUAL = "annual" + class LinkProperties(IniProperties): """ diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index a136184969..e60b9a2a37 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -620,6 +620,14 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: { "area1": "area 1", "area2": "area 2", + "asset_type": 'ac', + "display_comments": True, + "filter_synthesis": 'hourly, daily, weekly, monthly, annual', + "filter_year_by_year": 'hourly, daily, weekly, monthly, annual', + "hurdles_cost": False, + "loop_flow": False, + "transmission_capacities": 'enabled', + "use_phase_shifter": False, "ui": {"color": "112,112,112", "style": "plain", "width": 1.0}, } ] diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index cb1d29c971..ac7f2261e7 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -29,6 +29,7 @@ from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService from antarest.study.storage.rawstudy.model.filesystem.config.files import build +from antarest.study.storage.rawstudy.model.filesystem.config.links import TransmissionCapacity, AssetType from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, DistrictSet, FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.config.thermal import ThermalConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy @@ -37,10 +38,11 @@ from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.business.matrix_constants_generator import GeneratorMatrixConstants from antarest.study.storage.variantstudy.command_factory import CommandFactory -from antarest.study.storage.variantstudy.model.command.common import CommandName +from antarest.study.storage.variantstudy.model.command.common import CommandName, FilteringOptions from antarest.study.storage.variantstudy.model.dbmodel import VariantStudy from antarest.study.storage.variantstudy.model.model import CommandDTO from antarest.study.storage.variantstudy.variant_study_service import VariantStudyService +from tests.conftest_services import study_storage_service_fixture from tests.helpers import with_db_context from tests.storage.business.assets import ASSETS_DIR @@ -225,7 +227,16 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): args={ "area1": "test", "area2": "test2", - "parameters": None, + "parameters": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": True, + 'filter-synthesis': 'hourly, daily, weekly, monthly, annual', + 'filter-year-by-year': 'hourly, daily, weekly, monthly, annual' + }, }, ), ], @@ -416,11 +427,96 @@ def test_get_all_area(): all_areas = area_manager.get_all_areas(study) assert expected_all == [area.model_dump() for area in all_areas] + file_tree_mock.get.side_effect = [ + { + 'a2': { + 'hurdles-cost': True, + 'loop-flow': True, + 'use-phase-shifter': False, + 'transmission-capacities': TransmissionCapacity.ENABLED, + 'asset-type': AssetType.DC, + 'display-comments': False, + 'filter-synthesis': FilteringOptions.FILTER_SYNTHESIS, + 'filter-year-by-year': FilteringOptions.FILTER_YEAR_BY_YEAR, + }, + 'a3': { + 'hurdles-cost': True, + 'loop-flow': False, + 'use-phase-shifter': True, + 'transmission-capacities': TransmissionCapacity.ENABLED, + 'asset-type': AssetType.AC, + 'display-comments': True, + 'filter-synthesis': FilteringOptions.FILTER_SYNTHESIS, + 'filter-year-by-year': FilteringOptions.FILTER_YEAR_BY_YEAR, + }, + }, + { + 'a3': { + 'hurdles-cost': True, + 'loop-flow': False, + 'use-phase-shifter': True, + 'transmission-capacities': TransmissionCapacity.ENABLED, + 'asset-type': AssetType.AC, + 'display-comments': True, + 'filter-synthesis': FilteringOptions.FILTER_SYNTHESIS, + 'filter-year-by-year': FilteringOptions.FILTER_YEAR_BY_YEAR, + } + }, + { + 'a3': { + 'hurdles-cost': False, + 'loop-flow': False, + 'use-phase-shifter': True, + 'transmission-capacities': TransmissionCapacity.ENABLED, + 'asset-type': AssetType.AC, + 'display-comments': True, + 'filter-synthesis': FilteringOptions.FILTER_SYNTHESIS, + 'filter-year-by-year': FilteringOptions.FILTER_YEAR_BY_YEAR, + } + } + ] + links = link_manager.get_all_links(study) assert [ - {"area1": "a1", "area2": "a2", "ui": None}, - {"area1": "a1", "area2": "a3", "ui": None}, - {"area1": "a2", "area2": "a3", "ui": None}, + { + "area1": "a1", + "area2": "a2", + "asset_type": AssetType.DC, + "display_comments": False, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": True, + "loop_flow": True, + "transmission_capacities": TransmissionCapacity.ENABLED, + "ui": None, + "use_phase_shifter": False + }, + { + "area1": "a1", + "area2": "a3", + "asset_type": AssetType.AC, + "display_comments": True, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": True, + "loop_flow": False, + "transmission_capacities": TransmissionCapacity.ENABLED, + "ui": None, + "use_phase_shifter": True + }, + { + "area1": "a2", + "area2": "a3", + "asset_type": AssetType.AC, + "display_comments": True, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": True, + "loop_flow": False, + "transmission_capacities": TransmissionCapacity.ENABLED, + "ui": None, + "use_phase_shifter": True + } ] == [link.model_dump() for link in links] From 2d2027b5d134ae06871557923429f8a6361f45d5 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 17 Sep 2024 14:22:34 +0200 Subject: [PATCH 002/103] isort and black --- antarest/study/business/link_management.py | 19 ++- .../rawstudy/model/filesystem/config/links.py | 1 + .../storage/variantstudy/command_factory.py | 2 +- tests/integration/test_integration.py | 8 +- .../storage/business/test_arealink_manager.py | 158 +++++++++--------- 5 files changed, 97 insertions(+), 91 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index fdaff6f651..d87a31446b 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -11,6 +11,7 @@ # This file is part of the Antares project. import typing as t +from typing import Any, Dict, Tuple, Union from pydantic import BaseModel @@ -19,8 +20,12 @@ from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy -from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties, LinkStyle, \ - TransmissionCapacity, AssetType +from antarest.study.storage.rawstudy.model.filesystem.config.links import ( + AssetType, + LinkProperties, + LinkStyle, + TransmissionCapacity, +) from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_link import CreateLink @@ -87,7 +92,7 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn display_comments=links_config[link].get("display-comments"), filter_synthesis=links_config[link].get("filter-synthesis"), filter_year_by_year=links_config[link].get("filter-year-by-year"), - ui=ui_info + ui=ui_info, ) ) @@ -113,7 +118,7 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkI asset_type=link_creation_info.asset_type, display_comments=link_creation_info.display_comments, filter_synthesis=link_creation_info.filter_synthesis, - filter_year_by_year=link_creation_info.filter_year_by_year + filter_year_by_year=link_creation_info.filter_year_by_year, ) def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: @@ -125,7 +130,7 @@ def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: ) execute_or_add_commands(study, file_study, [command], self.storage_service) - def get_all_links_props(self, study: RawStudy) -> t.Mapping[t.Tuple[str, str], LinkOutput]: + def get_all_links_props(self, study: RawStudy) -> dict[tuple[Union[str, Any], Union[str, Any]], BaseModel]: """ Retrieves all links properties from the study. @@ -161,7 +166,7 @@ def update_links_props( self, study: RawStudy, update_links_by_ids: t.Mapping[t.Tuple[str, str], LinkOutput], - ) -> t.Mapping[t.Tuple[str, str], LinkOutput]: + ) -> dict[tuple[str, str], BaseModel]: old_links_by_ids = self.get_all_links_props(study) new_links_by_ids = {} file_study = self.storage_service.get_storage(study).get_raw(study) @@ -199,7 +204,7 @@ def create_parameters(link_creation_info: LinkInfoDTO) -> dict[str, str]: "asset-type": link_creation_info.asset_type, "display-comments": link_creation_info.display_comments, "filter-synthesis": link_creation_info.filter_synthesis, - "filter-year-by-year": link_creation_info.filter_year_by_year + "filter-year-by-year": link_creation_info.filter_year_by_year, } return parameters \ No newline at end of file diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/links.py b/antarest/study/storage/rawstudy/model/filesystem/config/links.py index b9fd25ccac..31c4c72f2e 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/links.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/links.py @@ -65,6 +65,7 @@ class TransmissionCapacity(EnumIgnoreCase): IGNORE = "ignore" ENABLED = "enabled" + class LinkStyle(EnumIgnoreCase): """ Enum representing the style of a link in a network visualization. diff --git a/antarest/study/storage/variantstudy/command_factory.py b/antarest/study/storage/variantstudy/command_factory.py index d178cfa79f..409cf81bb5 100644 --- a/antarest/study/storage/variantstudy/command_factory.py +++ b/antarest/study/storage/variantstudy/command_factory.py @@ -10,8 +10,8 @@ # # This file is part of the Antares project. -import typing as t import copy +import typing as t from antarest.core.model import JSON from antarest.matrixstore.service import ISimpleMatrixService diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index e60b9a2a37..0eab2ddd81 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -620,13 +620,13 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: { "area1": "area 1", "area2": "area 2", - "asset_type": 'ac', + "asset_type": "ac", "display_comments": True, - "filter_synthesis": 'hourly, daily, weekly, monthly, annual', - "filter_year_by_year": 'hourly, daily, weekly, monthly, annual', + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", "hurdles_cost": False, "loop_flow": False, - "transmission_capacities": 'enabled', + "transmission_capacities": "enabled", "use_phase_shifter": False, "ui": {"color": "112,112,112", "style": "plain", "width": 1.0}, } diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index ac7f2261e7..9ff97098e4 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -29,7 +29,7 @@ from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService from antarest.study.storage.rawstudy.model.filesystem.config.files import build -from antarest.study.storage.rawstudy.model.filesystem.config.links import TransmissionCapacity, AssetType +from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, TransmissionCapacity from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, DistrictSet, FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.config.thermal import ThermalConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy @@ -234,8 +234,8 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "transmission-capacities": TransmissionCapacity.ENABLED, "asset-type": AssetType.AC, "display-comments": True, - 'filter-synthesis': 'hourly, daily, weekly, monthly, annual', - 'filter-year-by-year': 'hourly, daily, weekly, monthly, annual' + "filter-synthesis": "hourly, daily, weekly, monthly, annual", + "filter-year-by-year": "hourly, daily, weekly, monthly, annual", }, }, ), @@ -429,94 +429,94 @@ def test_get_all_area(): file_tree_mock.get.side_effect = [ { - 'a2': { - 'hurdles-cost': True, - 'loop-flow': True, - 'use-phase-shifter': False, - 'transmission-capacities': TransmissionCapacity.ENABLED, - 'asset-type': AssetType.DC, - 'display-comments': False, - 'filter-synthesis': FilteringOptions.FILTER_SYNTHESIS, - 'filter-year-by-year': FilteringOptions.FILTER_YEAR_BY_YEAR, + "a2": { + "hurdles-cost": True, + "loop-flow": True, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.DC, + "display-comments": False, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, }, - 'a3': { - 'hurdles-cost': True, - 'loop-flow': False, - 'use-phase-shifter': True, - 'transmission-capacities': TransmissionCapacity.ENABLED, - 'asset-type': AssetType.AC, - 'display-comments': True, - 'filter-synthesis': FilteringOptions.FILTER_SYNTHESIS, - 'filter-year-by-year': FilteringOptions.FILTER_YEAR_BY_YEAR, + "a3": { + "hurdles-cost": True, + "loop-flow": False, + "use-phase-shifter": True, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": True, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, }, }, { - 'a3': { - 'hurdles-cost': True, - 'loop-flow': False, - 'use-phase-shifter': True, - 'transmission-capacities': TransmissionCapacity.ENABLED, - 'asset-type': AssetType.AC, - 'display-comments': True, - 'filter-synthesis': FilteringOptions.FILTER_SYNTHESIS, - 'filter-year-by-year': FilteringOptions.FILTER_YEAR_BY_YEAR, + "a3": { + "hurdles-cost": True, + "loop-flow": False, + "use-phase-shifter": True, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": True, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, } }, { - 'a3': { - 'hurdles-cost': False, - 'loop-flow': False, - 'use-phase-shifter': True, - 'transmission-capacities': TransmissionCapacity.ENABLED, - 'asset-type': AssetType.AC, - 'display-comments': True, - 'filter-synthesis': FilteringOptions.FILTER_SYNTHESIS, - 'filter-year-by-year': FilteringOptions.FILTER_YEAR_BY_YEAR, + "a3": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": True, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": True, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, } - } + }, ] links = link_manager.get_all_links(study) assert [ - { - "area1": "a1", - "area2": "a2", - "asset_type": AssetType.DC, - "display_comments": False, - "filter_synthesis": "hourly, daily, weekly, monthly, annual", - "filter_year_by_year": "hourly, daily, weekly, monthly, annual", - "hurdles_cost": True, - "loop_flow": True, - "transmission_capacities": TransmissionCapacity.ENABLED, - "ui": None, - "use_phase_shifter": False - }, - { - "area1": "a1", - "area2": "a3", - "asset_type": AssetType.AC, - "display_comments": True, - "filter_synthesis": "hourly, daily, weekly, monthly, annual", - "filter_year_by_year": "hourly, daily, weekly, monthly, annual", - "hurdles_cost": True, - "loop_flow": False, - "transmission_capacities": TransmissionCapacity.ENABLED, - "ui": None, - "use_phase_shifter": True - }, - { - "area1": "a2", - "area2": "a3", - "asset_type": AssetType.AC, - "display_comments": True, - "filter_synthesis": "hourly, daily, weekly, monthly, annual", - "filter_year_by_year": "hourly, daily, weekly, monthly, annual", - "hurdles_cost": True, - "loop_flow": False, - "transmission_capacities": TransmissionCapacity.ENABLED, - "ui": None, - "use_phase_shifter": True - } + { + "area1": "a1", + "area2": "a2", + "asset_type": AssetType.DC, + "display_comments": False, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": True, + "loop_flow": True, + "transmission_capacities": TransmissionCapacity.ENABLED, + "ui": None, + "use_phase_shifter": False, + }, + { + "area1": "a1", + "area2": "a3", + "asset_type": AssetType.AC, + "display_comments": True, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": True, + "loop_flow": False, + "transmission_capacities": TransmissionCapacity.ENABLED, + "ui": None, + "use_phase_shifter": True, + }, + { + "area1": "a2", + "area2": "a3", + "asset_type": AssetType.AC, + "display_comments": True, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": True, + "loop_flow": False, + "transmission_capacities": TransmissionCapacity.ENABLED, + "ui": None, + "use_phase_shifter": True, + }, ] == [link.model_dump() for link in links] From 5cc7cea6c2a734e495c82906d456fe42fbe9c7b4 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 18 Sep 2024 15:23:10 +0200 Subject: [PATCH 003/103] add versioning --- antarest/study/business/link_management.py | 112 ++++++++++++------ antarest/study/service.py | 8 +- .../variantstudy/model/command/create_link.py | 5 +- antarest/study/web/study_data_blueprint.py | 8 +- .../storage/business/test_arealink_manager.py | 34 +++++- 5 files changed, 114 insertions(+), 53 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index d87a31446b..c4b07b8004 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -15,7 +15,7 @@ from pydantic import BaseModel -from antarest.core.exceptions import ConfigFileNotFound +from antarest.core.exceptions import ConfigFileNotFound, InvalidFieldForVersionError from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands @@ -37,11 +37,11 @@ class LinkUIDTO(BaseModel): color: str - width: float - style: LinkStyle + width: float = 1 + style: LinkStyle = "plain" -class LinkInfoDTO(BaseModel): +class LinkInfoDTOBase(BaseModel): area1: str area2: str hurdles_cost: t.Optional[bool] = False @@ -50,9 +50,24 @@ class LinkInfoDTO(BaseModel): transmission_capacities: t.Optional[TransmissionCapacity] = "enabled" asset_type: t.Optional[AssetType] = "ac" display_comments: t.Optional[bool] = True + ui: t.Optional[LinkUIDTO] = None + + +class LinkInfoDTO820(LinkInfoDTOBase): filter_synthesis: t.Optional[str] = FilteringOptions.FILTER_SYNTHESIS filter_year_by_year: t.Optional[str] = FilteringOptions.FILTER_YEAR_BY_YEAR - ui: t.Optional[LinkUIDTO] = None + + +LinkInfoDTOType = t.Union[LinkInfoDTO820, LinkInfoDTOBase] + + +class LinkInfoFactory: + @staticmethod + def create_link_info(version: int, **kwargs) -> LinkInfoDTOType: + if version >= 820 and ("filter_synthesis" in kwargs or "filter_year_by_year" in kwargs): + return LinkInfoDTO820(**kwargs) + else: + return LinkInfoDTOBase(**kwargs) @all_optional_model @@ -67,7 +82,7 @@ class LinkManager: def __init__(self, storage_service: StudyStorageService) -> None: self.storage_service = storage_service - def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkInfoDTO]: + def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkInfoDTOType]: file_study = self.storage_service.get_storage(study).get_raw(study) result = [] for area_id, area in file_study.config.areas.items(): @@ -80,35 +95,29 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn width=links_config[link].get("link-width", 1), style=links_config[link].get("link-style", "plain"), ) - result.append( - LinkInfoDTO( - area1=area_id, - area2=link, - hurdles_cost=links_config[link].get("hurdles-cost"), - loop_flow=links_config[link].get("loop-flow"), - use_phase_shifter=links_config[link].get("use-phase-shifter"), - transmission_capacities=links_config[link].get("transmission-capacities"), - asset_type=links_config[link].get("asset-type"), - display_comments=links_config[link].get("display-comments"), - filter_synthesis=links_config[link].get("filter-synthesis"), - filter_year_by_year=links_config[link].get("filter-year-by-year"), - ui=ui_info, - ) - ) + link_info_dto = LinkInfoFactory.create_link_info( + version=int(study.version), + area1=area_id, + area2=link, + hurdles_cost=links_config[link].get("hurdles-cost"), + loop_flow=links_config[link].get("loop-flow"), + use_phase_shifter=links_config[link].get("use-phase-shifter"), + transmission_capacities=links_config[link].get("transmission-capacities"), + asset_type=links_config[link].get("asset-type"), + display_comments=links_config[link].get("display-comments"), + ui=ui_info, + filter_synthesis=links_config[link].get("filter-synthesis") if int(study.version) >= 820 else None, + filter_year_by_year=links_config[link].get("filter-year-by-year") + if int(study.version) >= 820 + else None, + ) + result.append(link_info_dto) return result - def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: - storage_service = self.storage_service.get_storage(study) - file_study = storage_service.get_raw(study) - command = CreateLink( - area1=link_creation_info.area1, - area2=link_creation_info.area2, - parameters=self.create_parameters(link_creation_info), - command_context=self.storage_service.variant_study_service.command_factory.command_context, - ) - execute_or_add_commands(study, file_study, [command], self.storage_service) - return LinkInfoDTO( + def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: + link_info_dto = LinkInfoFactory.create_link_info( + version=int(study.version), area1=link_creation_info.area1, area2=link_creation_info.area2, hurdles_cost=link_creation_info.hurdles_cost, @@ -117,10 +126,25 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkI transmission_capacities=link_creation_info.transmission_capacities, asset_type=link_creation_info.asset_type, display_comments=link_creation_info.display_comments, - filter_synthesis=link_creation_info.filter_synthesis, - filter_year_by_year=link_creation_info.filter_year_by_year, + filter_synthesis=link_creation_info.filter_synthesis if int(study.version) >= 820 else None, + filter_year_by_year=link_creation_info.filter_year_by_year if int(study.version) >= 820 else None, + ) + self.check_version_coherence(int(study.version), link_info_dto) + + storage_service = self.storage_service.get_storage(study) + file_study = storage_service.get_raw(study) + + command = CreateLink( + area1=link_creation_info.area1, + area2=link_creation_info.area2, + parameters=self.create_parameters(int(study.version), link_creation_info), + command_context=self.storage_service.variant_study_service.command_factory.command_context, ) + execute_or_add_commands(study, file_study, [command], self.storage_service) + + return link_info_dto + def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) command = RemoveLink( @@ -190,12 +214,22 @@ def update_links_props( execute_or_add_commands(study, file_study, commands, self.storage_service) return new_links_by_ids + @staticmethod + def check_version_coherence(study_version: int, link_creation_info: LinkInfoDTOType) -> None: + if study_version < 820: + if isinstance(link_creation_info, LinkInfoDTO820): + if link_creation_info.filter_synthesis or link_creation_info.filter_year_by_year: + raise InvalidFieldForVersionError( + f"You cannot specify a filter synthesis or filter year by year as your study version is earlier than v8.2: " + f"{link_creation_info.filter_synthesis, link_creation_info.filter_year_by_year}" + ) + @staticmethod def get_table_schema() -> JSON: return LinkOutput.schema() @staticmethod - def create_parameters(link_creation_info: LinkInfoDTO) -> dict[str, str]: + def create_parameters(study_version: int, link_creation_info: LinkInfoDTOType) -> dict[str, str]: parameters = { "hurdles-cost": link_creation_info.hurdles_cost, "loop-flow": link_creation_info.loop_flow, @@ -203,8 +237,10 @@ def create_parameters(link_creation_info: LinkInfoDTO) -> dict[str, str]: "transmission-capacities": link_creation_info.transmission_capacities, "asset-type": link_creation_info.asset_type, "display-comments": link_creation_info.display_comments, - "filter-synthesis": link_creation_info.filter_synthesis, - "filter-year-by-year": link_creation_info.filter_year_by_year, } - return parameters \ No newline at end of file + if study_version >= 820 and isinstance(link_creation_info, LinkInfoDTO820): + parameters["filter-synthesis"] = link_creation_info.filter_synthesis + parameters["filter-year-by-year"] = link_creation_info.filter_year_by_year + + return parameters diff --git a/antarest/study/service.py b/antarest/study/service.py index 125c4e1531..93cc9fe2b3 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -80,7 +80,7 @@ from antarest.study.business.correlation_management import CorrelationManager from antarest.study.business.district_manager import DistrictManager from antarest.study.business.general_management import GeneralManager -from antarest.study.business.link_management import LinkInfoDTO, LinkManager +from antarest.study.business.link_management import LinkInfoDTOType, LinkManager from antarest.study.business.matrix_management import MatrixManager, MatrixManagerError from antarest.study.business.optimization_management import OptimizationManager from antarest.study.business.playlist_management import PlaylistManager @@ -1817,7 +1817,7 @@ def get_all_links( uuid: str, with_ui: bool, params: RequestParameters, - ) -> t.List[LinkInfoDTO]: + ) -> t.List[LinkInfoDTOType]: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.READ) return self.links.get_all_links(study, with_ui) @@ -1844,9 +1844,9 @@ def create_area( def create_link( self, uuid: str, - link_creation_dto: LinkInfoDTO, + link_creation_dto: LinkInfoDTOType, params: RequestParameters, - ) -> LinkInfoDTO: + ) -> LinkInfoDTOType: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.WRITE) self._assert_study_unarchived(study) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index ef2d21f0a9..2b7aece2d9 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -17,6 +17,7 @@ from antarest.core.model import JSON from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData +from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, TransmissionCapacity from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix @@ -30,8 +31,8 @@ class LinkProperties: LOOP_FLOW: bool = False USE_PHASE_SHIFTER: bool = False DISPLAY_COMMENTS: bool = True - TRANSMISSION_CAPACITIES: str = "enabled" - ASSET_TYPE: str = "ac" + TRANSMISSION_CAPACITIES: TransmissionCapacity = "enabled" + ASSET_TYPE: AssetType = "ac" LINK_STYLE: str = "plain" LINK_WIDTH: int = 1 COLORR: int = 112 diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 9b1cca0fc5..408befb187 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -68,7 +68,7 @@ ) from antarest.study.business.district_manager import DistrictCreationDTO, DistrictInfoDTO, DistrictUpdateDTO from antarest.study.business.general_management import GeneralFormFields -from antarest.study.business.link_management import LinkInfoDTO +from antarest.study.business.link_management import LinkInfoDTOType from antarest.study.business.optimization_management import OptimizationFormFields from antarest.study.business.playlist_management import PlaylistColumns from antarest.study.business.scenario_builder_management import Rulesets, ScenarioType @@ -147,7 +147,7 @@ def get_areas( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Get all links", - response_model=t.List[LinkInfoDTO], + response_model=t.List[LinkInfoDTOType], ) def get_links( uuid: str, @@ -184,11 +184,11 @@ def create_area( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Create a link", - response_model=LinkInfoDTO, + response_model=LinkInfoDTOType, ) def create_link( uuid: str, - link_creation_info: LinkInfoDTO, + link_creation_info: LinkInfoDTOType, current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Any: logger.info( diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 9ff97098e4..ee115ae6d6 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -24,7 +24,13 @@ from antarest.matrixstore.repository import MatrixContentRepository from antarest.matrixstore.service import SimpleMatrixService from antarest.study.business.area_management import AreaCreationDTO, AreaManager, AreaType, UpdateAreaUi -from antarest.study.business.link_management import LinkInfoDTO, LinkManager +from antarest.study.business.link_management import ( + LinkInfoDTO820, + LinkInfoDTOBase, + LinkInfoDTOType, + LinkInfoFactory, + LinkManager, +) from antarest.study.model import Patch, PatchArea, PatchCluster, RawStudy, StudyAdditionalData from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService @@ -104,6 +110,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = RawStudy( id=study_id, + version="-1", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -136,7 +143,16 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): } area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) - link_manager.create_link(study, LinkInfoDTO(area1="test", area2="test2")) + link_manager.create_link( + study, + LinkInfoFactory.create_link_info( + version=-1, + area1="test", + area2="test2", + filter_synthesis=FilteringOptions.FILTER_SYNTHESIS, + filter_year_by_year=FilteringOptions.FILTER_YEAR_BY_YEAR, + ), + ) assert empty_study.config.areas["test"].links.get("test2") is not None link_manager.delete_link(study, "test", "test2") @@ -150,6 +166,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = VariantStudy( id=variant_id, + version="900", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -218,7 +235,15 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ) area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) - link_manager.create_link(study, LinkInfoDTO(area1="test", area2="test2")) + link_manager.create_link( + study, + LinkInfoDTO820( + area1="test", + area2="test2", + filter_synthesis=FilteringOptions.FILTER_SYNTHESIS, + filter_year_by_year=FilteringOptions.FILTER_YEAR_BY_YEAR, + ), + ) variant_study_service.append_commands.assert_called_with( variant_id, [ @@ -271,7 +296,7 @@ def test_get_all_area(): ) link_manager = LinkManager(storage_service=StudyStorageService(raw_study_service, Mock())) - study = RawStudy() + study = RawStudy(version="900") config = FileStudyTreeConfig( study_path=Path("somepath"), path=Path("somepath"), @@ -475,7 +500,6 @@ def test_get_all_area(): } }, ] - links = link_manager.get_all_links(study) assert [ { From 0cf0ff5f94166fc0ee828c1cabd4e1319bd717b6 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 18 Sep 2024 16:33:39 +0200 Subject: [PATCH 004/103] add versioning --- antarest/study/business/link_management.py | 6 ++-- .../variantstudy/model/command/create_link.py | 2 +- .../storage/business/test_arealink_manager.py | 32 ++++++++++++++++++- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index c4b07b8004..38720f7765 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -37,8 +37,8 @@ class LinkUIDTO(BaseModel): color: str - width: float = 1 - style: LinkStyle = "plain" + width: float + style: LinkStyle class LinkInfoDTOBase(BaseModel): @@ -190,7 +190,7 @@ def update_links_props( self, study: RawStudy, update_links_by_ids: t.Mapping[t.Tuple[str, str], LinkOutput], - ) -> dict[tuple[str, str], BaseModel]: + ) -> dict[tuple[str, str], LinkOutput]: old_links_by_ids = self.get_all_links_props(study) new_links_by_ids = {} file_study = self.storage_service.get_storage(study).get_raw(study) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 2b7aece2d9..b1961efd5e 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -17,7 +17,7 @@ from antarest.core.model import JSON from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData -from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, TransmissionCapacity +from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, LinkStyle, TransmissionCapacity from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index ee115ae6d6..55627e799c 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -166,7 +166,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = VariantStudy( id=variant_id, - version="900", + version="-1", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -235,6 +235,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ) area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) + study.version = 880 link_manager.create_link( study, LinkInfoDTO820( @@ -267,6 +268,35 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ], RequestParameters(DEFAULT_ADMIN_USER), ) + study.version = 810 + link_manager.create_link( + study, + LinkInfoDTOBase( + area1="test", + area2="test2", + ), + ) + variant_study_service.append_commands.assert_called_with( + variant_id, + [ + CommandDTO( + action=CommandName.CREATE_LINK.value, + args={ + "area1": "test", + "area2": "test2", + "parameters": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": True, + }, + }, + ), + ], + RequestParameters(DEFAULT_ADMIN_USER), + ) link_manager.delete_link(study, "test", "test2") variant_study_service.append_commands.assert_called_with( variant_id, From d6ecfb17b3eb508bfac9f19ff1f65e85ab2f0cb3 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 18 Sep 2024 16:34:38 +0200 Subject: [PATCH 005/103] add versioning --- antarest/study/business/link_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 38720f7765..29d63d0fd8 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -154,7 +154,7 @@ def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: ) execute_or_add_commands(study, file_study, [command], self.storage_service) - def get_all_links_props(self, study: RawStudy) -> dict[tuple[Union[str, Any], Union[str, Any]], BaseModel]: + def get_all_links_props(self, study: RawStudy) -> dict[tuple[Union[str, Any], Union[str, Any]], LinkOutput]: """ Retrieves all links properties from the study. From e3e2158d9b4215258c0403cbc9ec25dcdb689980 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 18 Sep 2024 16:44:42 +0200 Subject: [PATCH 006/103] add versioning --- antarest/study/business/link_management.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 29d63d0fd8..52ac1c59e3 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -154,7 +154,7 @@ def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: ) execute_or_add_commands(study, file_study, [command], self.storage_service) - def get_all_links_props(self, study: RawStudy) -> dict[tuple[Union[str, Any], Union[str, Any]], LinkOutput]: + def get_all_links_props(self, study: RawStudy) -> t.Mapping[t.Tuple[str, str], LinkOutput]: """ Retrieves all links properties from the study. @@ -190,7 +190,7 @@ def update_links_props( self, study: RawStudy, update_links_by_ids: t.Mapping[t.Tuple[str, str], LinkOutput], - ) -> dict[tuple[str, str], LinkOutput]: + ) -> t.Mapping[t.Tuple[str, str], LinkOutput]: old_links_by_ids = self.get_all_links_props(study) new_links_by_ids = {} file_study = self.storage_service.get_storage(study).get_raw(study) From e03678a5614f1c9fc23017e01e375f99ca84ecff Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 19 Sep 2024 09:09:05 +0200 Subject: [PATCH 007/103] correct typing issue --- antarest/study/business/link_management.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 52ac1c59e3..b0030fd360 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -47,8 +47,8 @@ class LinkInfoDTOBase(BaseModel): hurdles_cost: t.Optional[bool] = False loop_flow: t.Optional[bool] = False use_phase_shifter: t.Optional[bool] = False - transmission_capacities: t.Optional[TransmissionCapacity] = "enabled" - asset_type: t.Optional[AssetType] = "ac" + transmission_capacities: t.Optional[TransmissionCapacity] = 'enabled' + asset_type: t.Optional[AssetType] = 'ac' display_comments: t.Optional[bool] = True ui: t.Optional[LinkUIDTO] = None @@ -229,7 +229,7 @@ def get_table_schema() -> JSON: return LinkOutput.schema() @staticmethod - def create_parameters(study_version: int, link_creation_info: LinkInfoDTOType) -> dict[str, str]: + def create_parameters(study_version: int, link_creation_info: LinkInfoDTOType) -> t.Dict[str, str]: parameters = { "hurdles-cost": link_creation_info.hurdles_cost, "loop-flow": link_creation_info.loop_flow, From 2f17d71c8f5d1439ff8a1ce580e852cbbe0c454f Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 19 Sep 2024 09:11:48 +0200 Subject: [PATCH 008/103] correct typing issue --- antarest/study/business/link_management.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index b0030fd360..6779f0fc0a 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -47,8 +47,8 @@ class LinkInfoDTOBase(BaseModel): hurdles_cost: t.Optional[bool] = False loop_flow: t.Optional[bool] = False use_phase_shifter: t.Optional[bool] = False - transmission_capacities: t.Optional[TransmissionCapacity] = 'enabled' - asset_type: t.Optional[AssetType] = 'ac' + transmission_capacities: t.Optional[TransmissionCapacity] = "enabled" + asset_type: t.Optional[AssetType] = "ac" display_comments: t.Optional[bool] = True ui: t.Optional[LinkUIDTO] = None From 26cdb3ada0a7d53a645b4dc86cb7d76590925639 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 19 Sep 2024 10:22:44 +0200 Subject: [PATCH 009/103] correct typing issue --- antarest/study/business/link_management.py | 29 +++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 6779f0fc0a..0f738f16f3 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -13,7 +13,7 @@ import typing as t from typing import Any, Dict, Tuple, Union -from pydantic import BaseModel +from pydantic import BaseModel, Field from antarest.core.exceptions import ConfigFileNotFound, InvalidFieldForVersionError from antarest.core.model import JSON @@ -21,10 +21,8 @@ from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy from antarest.study.storage.rawstudy.model.filesystem.config.links import ( - AssetType, LinkProperties, LinkStyle, - TransmissionCapacity, ) from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions @@ -47,8 +45,8 @@ class LinkInfoDTOBase(BaseModel): hurdles_cost: t.Optional[bool] = False loop_flow: t.Optional[bool] = False use_phase_shifter: t.Optional[bool] = False - transmission_capacities: t.Optional[TransmissionCapacity] = "enabled" - asset_type: t.Optional[AssetType] = "ac" + transmission_capacities: t.Optional[str] = "enabled" + asset_type: t.Optional[str] = "ac" display_comments: t.Optional[bool] = True ui: t.Optional[LinkUIDTO] = None @@ -64,10 +62,15 @@ class LinkInfoDTO820(LinkInfoDTOBase): class LinkInfoFactory: @staticmethod def create_link_info(version: int, **kwargs) -> LinkInfoDTOType: - if version >= 820 and ("filter_synthesis" in kwargs or "filter_year_by_year" in kwargs): - return LinkInfoDTO820(**kwargs) + filters_provided = kwargs.get("filter_synthesis") is not None or kwargs.get("filter_year_by_year") is not None + + if version >= 820: + link_info = LinkInfoDTO820(**kwargs) else: - return LinkInfoDTOBase(**kwargs) + link_info = LinkInfoDTOBase(**kwargs) + + link_info._filters_provided = filters_provided + return link_info @all_optional_model @@ -217,12 +220,10 @@ def update_links_props( @staticmethod def check_version_coherence(study_version: int, link_creation_info: LinkInfoDTOType) -> None: if study_version < 820: - if isinstance(link_creation_info, LinkInfoDTO820): - if link_creation_info.filter_synthesis or link_creation_info.filter_year_by_year: - raise InvalidFieldForVersionError( - f"You cannot specify a filter synthesis or filter year by year as your study version is earlier than v8.2: " - f"{link_creation_info.filter_synthesis, link_creation_info.filter_year_by_year}" - ) + if getattr(link_creation_info, "_filters_provided", True): + raise InvalidFieldForVersionError( + f"You cannot specify a filter synthesis or filter year by year as your study version is earlier than v8.2" + ) @staticmethod def get_table_schema() -> JSON: From 76188de22f8cc68341da840f4d4c012b3cce3c1b Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 19 Sep 2024 11:49:04 +0200 Subject: [PATCH 010/103] fix tests --- antarest/study/business/link_management.py | 24 +++++++++---------- .../storage/business/test_arealink_manager.py | 2 -- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 0f738f16f3..65c6325400 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -11,19 +11,15 @@ # This file is part of the Antares project. import typing as t -from typing import Any, Dict, Tuple, Union -from pydantic import BaseModel, Field +from pydantic import BaseModel from antarest.core.exceptions import ConfigFileNotFound, InvalidFieldForVersionError from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy -from antarest.study.storage.rawstudy.model.filesystem.config.links import ( - LinkProperties, - LinkStyle, -) +from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties, LinkStyle from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_link import CreateLink @@ -52,8 +48,8 @@ class LinkInfoDTOBase(BaseModel): class LinkInfoDTO820(LinkInfoDTOBase): - filter_synthesis: t.Optional[str] = FilteringOptions.FILTER_SYNTHESIS - filter_year_by_year: t.Optional[str] = FilteringOptions.FILTER_YEAR_BY_YEAR + filter_synthesis: t.Optional[str] = None + filter_year_by_year: t.Optional[str] = None LinkInfoDTOType = t.Union[LinkInfoDTO820, LinkInfoDTOBase] @@ -66,6 +62,10 @@ def create_link_info(version: int, **kwargs) -> LinkInfoDTOType: if version >= 820: link_info = LinkInfoDTO820(**kwargs) + if link_info.filter_synthesis is None: + link_info.filter_synthesis = FilteringOptions.FILTER_SYNTHESIS + if link_info.filter_year_by_year is None: + link_info.filter_year_by_year = FilteringOptions.FILTER_YEAR_BY_YEAR else: link_info = LinkInfoDTOBase(**kwargs) @@ -110,10 +110,8 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn asset_type=links_config[link].get("asset-type"), display_comments=links_config[link].get("display-comments"), ui=ui_info, - filter_synthesis=links_config[link].get("filter-synthesis") if int(study.version) >= 820 else None, - filter_year_by_year=links_config[link].get("filter-year-by-year") - if int(study.version) >= 820 - else None, + filter_synthesis=links_config[link].get("filter-synthesis"), + filter_year_by_year=links_config[link].get("filter-year-by-year"), ) result.append(link_info_dto) return result @@ -140,7 +138,7 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L command = CreateLink( area1=link_creation_info.area1, area2=link_creation_info.area2, - parameters=self.create_parameters(int(study.version), link_creation_info), + parameters=self.create_parameters(int(study.version), link_info_dto), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 55627e799c..b0d3262a34 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -149,8 +149,6 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): version=-1, area1="test", area2="test2", - filter_synthesis=FilteringOptions.FILTER_SYNTHESIS, - filter_year_by_year=FilteringOptions.FILTER_YEAR_BY_YEAR, ), ) assert empty_study.config.areas["test"].links.get("test2") is not None From 9dff527cec714e040fd8b1caf68d53031aebb8c4 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 19 Sep 2024 11:49:41 +0200 Subject: [PATCH 011/103] fix tests --- antarest/study/business/link_management.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 65c6325400..4d2524969d 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -117,6 +117,7 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn return result def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: + study_version = int(study.version) link_info_dto = LinkInfoFactory.create_link_info( version=int(study.version), area1=link_creation_info.area1, @@ -127,10 +128,10 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L transmission_capacities=link_creation_info.transmission_capacities, asset_type=link_creation_info.asset_type, display_comments=link_creation_info.display_comments, - filter_synthesis=link_creation_info.filter_synthesis if int(study.version) >= 820 else None, - filter_year_by_year=link_creation_info.filter_year_by_year if int(study.version) >= 820 else None, + filter_synthesis=link_creation_info.filter_synthesis if study_version >= 820 else None, + filter_year_by_year=link_creation_info.filter_year_by_year if study_version >= 820 else None, ) - self.check_version_coherence(int(study.version), link_info_dto) + self.check_version_coherence(study_version, link_info_dto) storage_service = self.storage_service.get_storage(study) file_study = storage_service.get_raw(study) From cac1349234f9bbe45d23a966cb02dbd69ed822b8 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 19 Sep 2024 15:47:15 +0200 Subject: [PATCH 012/103] fix tests --- antarest/study/business/link_management.py | 84 ++++++++++++------- tests/integration/test_integration.py | 8 +- .../storage/business/test_arealink_manager.py | 48 ++++++++--- 3 files changed, 94 insertions(+), 46 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 4d2524969d..560c8a7273 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -19,7 +19,12 @@ from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy -from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties, LinkStyle +from antarest.study.storage.rawstudy.model.filesystem.config.links import ( + AssetType, + LinkProperties, + LinkStyle, + TransmissionCapacity, +) from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_link import CreateLink @@ -27,12 +32,7 @@ from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig _ALL_LINKS_PATH = "input/links" - - -class LinkUIDTO(BaseModel): - color: str - width: float - style: LinkStyle +DEFAULT_COLOR = "112" class LinkInfoDTOBase(BaseModel): @@ -41,10 +41,14 @@ class LinkInfoDTOBase(BaseModel): hurdles_cost: t.Optional[bool] = False loop_flow: t.Optional[bool] = False use_phase_shifter: t.Optional[bool] = False - transmission_capacities: t.Optional[str] = "enabled" - asset_type: t.Optional[str] = "ac" + transmission_capacities: t.Optional[str] = TransmissionCapacity.ENABLED + asset_type: t.Optional[str] = AssetType.AC display_comments: t.Optional[bool] = True - ui: t.Optional[LinkUIDTO] = None + colorr: t.Optional[str] = DEFAULT_COLOR + colorb: t.Optional[str] = DEFAULT_COLOR + colorg: t.Optional[str] = DEFAULT_COLOR + link_width: t.Optional[float] = 1 + link_style: t.Optional[str] = LinkStyle.PLAIN class LinkInfoDTO820(LinkInfoDTOBase): @@ -57,10 +61,10 @@ class LinkInfoDTO820(LinkInfoDTOBase): class LinkInfoFactory: @staticmethod - def create_link_info(version: int, **kwargs) -> LinkInfoDTOType: + def create_link_info(**kwargs) -> LinkInfoDTOType: filters_provided = kwargs.get("filter_synthesis") is not None or kwargs.get("filter_year_by_year") is not None - if version >= 820: + if kwargs.get("version") >= 820: link_info = LinkInfoDTO820(**kwargs) if link_info.filter_synthesis is None: link_info.filter_synthesis = FilteringOptions.FILTER_SYNTHESIS @@ -91,28 +95,36 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn for area_id, area in file_study.config.areas.items(): links_config = file_study.tree.get(["input", "links", area_id, "properties"]) for link in area.links: - ui_info: t.Optional[LinkUIDTO] = None + link_properties = links_config[link] + link_info_args = { + "version": int(study.version), + "area1": area_id, + "area2": link, + "hurdles_cost": link_properties.get("hurdles-cost"), + "loop_flow": link_properties.get("loop-flow"), + "use_phase_shifter": link_properties.get("use-phase-shifter"), + "transmission_capacities": link_properties.get("transmission-capacities"), + "asset_type": link_properties.get("asset-type"), + "display_comments": link_properties.get("display-comments"), + "filter_synthesis": link_properties.get("filter-synthesis"), + "filter_year_by_year": link_properties.get("filter-year-by-year"), + } if with_ui and links_config and link in links_config: - ui_info = LinkUIDTO( - color=f"{links_config[link].get('colorr', '163')},{links_config[link].get('colorg', '163')},{links_config[link].get('colorb', '163')}", - width=links_config[link].get("link-width", 1), - style=links_config[link].get("link-style", "plain"), + link_info_args.update( + { + "colorr": str(link_properties.get("colorr", "112")), + "colorb": str(link_properties.get("colorb", "112")), + "colorg": str(link_properties.get("colorg", "112")), + "link_width": link_properties.get("link-width", 1.0), + "link_style": link_properties.get("link-style", "plain"), + } ) + else: + link_info_args.update( + {"colorr": None, "colorb": None, "colorg": None, "link_width": None, "link_style": None} + ) + link_info_dto = LinkInfoFactory.create_link_info(**link_info_args) - link_info_dto = LinkInfoFactory.create_link_info( - version=int(study.version), - area1=area_id, - area2=link, - hurdles_cost=links_config[link].get("hurdles-cost"), - loop_flow=links_config[link].get("loop-flow"), - use_phase_shifter=links_config[link].get("use-phase-shifter"), - transmission_capacities=links_config[link].get("transmission-capacities"), - asset_type=links_config[link].get("asset-type"), - display_comments=links_config[link].get("display-comments"), - ui=ui_info, - filter_synthesis=links_config[link].get("filter-synthesis"), - filter_year_by_year=links_config[link].get("filter-year-by-year"), - ) result.append(link_info_dto) return result @@ -128,6 +140,11 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L transmission_capacities=link_creation_info.transmission_capacities, asset_type=link_creation_info.asset_type, display_comments=link_creation_info.display_comments, + colorr=link_creation_info.colorr, + colorb=link_creation_info.colorb, + colorg=link_creation_info.colorg, + link_width=link_creation_info.link_width, + link_style=link_creation_info.link_style, filter_synthesis=link_creation_info.filter_synthesis if study_version >= 820 else None, filter_year_by_year=link_creation_info.filter_year_by_year if study_version >= 820 else None, ) @@ -237,6 +254,11 @@ def create_parameters(study_version: int, link_creation_info: LinkInfoDTOType) - "transmission-capacities": link_creation_info.transmission_capacities, "asset-type": link_creation_info.asset_type, "display-comments": link_creation_info.display_comments, + "colorr": link_creation_info.colorr, + "colorb": link_creation_info.colorb, + "colorg": link_creation_info.colorg, + "link-width": link_creation_info.link_width, + "link-style": link_creation_info.link_style, } if study_version >= 820 and isinstance(link_creation_info, LinkInfoDTO820): diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 0eab2ddd81..b247d06cd7 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -621,14 +621,18 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: "area1": "area 1", "area2": "area 2", "asset_type": "ac", + "colorb": "112", + "colorg": "112", + "colorr": "112", "display_comments": True, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", "hurdles_cost": False, + "link_style": "plain", + "link_width": 1.0, "loop_flow": False, "transmission_capacities": "enabled", - "use_phase_shifter": False, - "ui": {"color": "112,112,112", "style": "plain", "width": 1.0}, + "use_phase_shifter": False } ] diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index b0d3262a34..ac2ac275c5 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -255,9 +255,14 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "hurdles-cost": False, "loop-flow": False, "use-phase-shifter": False, - "transmission-capacities": TransmissionCapacity.ENABLED, - "asset-type": AssetType.AC, + "transmission-capacities": "enabled", + "asset-type": "ac", "display-comments": True, + "colorr": "112", + "colorg": "112", + "colorb": "112", + "link-width": 1.0, + "link-style": "plain", "filter-synthesis": "hourly, daily, weekly, monthly, annual", "filter-year-by-year": "hourly, daily, weekly, monthly, annual", }, @@ -286,9 +291,14 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "hurdles-cost": False, "loop-flow": False, "use-phase-shifter": False, - "transmission-capacities": TransmissionCapacity.ENABLED, - "asset-type": AssetType.AC, + "transmission-capacities": "enabled", + "asset-type": "ac", "display-comments": True, + "colorr": "112", + "colorg": "112", + "colorb": "112", + "link-width": 1.0, + "link-style": "plain", }, }, ), @@ -533,40 +543,52 @@ def test_get_all_area(): { "area1": "a1", "area2": "a2", - "asset_type": AssetType.DC, + "asset_type": "dc", + "colorb": None, + "colorg": None, + "colorr": None, "display_comments": False, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", "hurdles_cost": True, + "link_style": None, + "link_width": None, "loop_flow": True, - "transmission_capacities": TransmissionCapacity.ENABLED, - "ui": None, + "transmission_capacities": "enabled", "use_phase_shifter": False, }, { "area1": "a1", "area2": "a3", - "asset_type": AssetType.AC, + "asset_type": "ac", + "colorb": None, + "colorg": None, + "colorr": None, "display_comments": True, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", "hurdles_cost": True, + "link_style": None, + "link_width": None, "loop_flow": False, - "transmission_capacities": TransmissionCapacity.ENABLED, - "ui": None, + "transmission_capacities": "enabled", "use_phase_shifter": True, }, { "area1": "a2", "area2": "a3", - "asset_type": AssetType.AC, + "asset_type": "ac", + "colorb": None, + "colorg": None, + "colorr": None, "display_comments": True, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", "hurdles_cost": True, + "link_style": None, + "link_width": None, "loop_flow": False, - "transmission_capacities": TransmissionCapacity.ENABLED, - "ui": None, + "transmission_capacities": "enabled", "use_phase_shifter": True, }, ] == [link.model_dump() for link in links] From d9716e3d377aa7013ce43ddf270175d27dd128e4 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 19 Sep 2024 16:10:18 +0200 Subject: [PATCH 013/103] refactor code --- antarest/study/business/link_management.py | 157 ++++++++++++++------- 1 file changed, 106 insertions(+), 51 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 560c8a7273..dc4136d258 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -61,20 +61,104 @@ class LinkInfoDTO820(LinkInfoDTOBase): class LinkInfoFactory: @staticmethod - def create_link_info(**kwargs) -> LinkInfoDTOType: - filters_provided = kwargs.get("filter_synthesis") is not None or kwargs.get("filter_year_by_year") is not None + def create_link_info(version: int, **kwargs) -> LinkInfoDTOType: + """ + Creates a LinkInfoDTO object corresponding to the specified version. + + Args: + version (int): The study version. + kwargs: The arguments passed for the DTO creation. - if kwargs.get("version") >= 820: - link_info = LinkInfoDTO820(**kwargs) + Returns: + LinkInfoDTOType: An object of type LinkInfoDTOBase or LinkInfoDTO820 depending on the version. + """ + LinkInfoFactory._check_version_coherence(version, **kwargs) + link_info = LinkInfoFactory._initialize_link_info(version, **kwargs) + LinkInfoFactory._set_default_filters(version, link_info) + + return link_info + + @staticmethod + def _initialize_link_info(version: int, **kwargs) -> LinkInfoDTOType: + """ + Initializes the LinkInfoDTO object based on the study version. + + Args: + version (int): The study version. + kwargs: The arguments passed for the DTO creation. + + Returns: + LinkInfoDTOType: An object of type LinkInfoDTOBase or LinkInfoDTO820 depending on the version. + """ + if version >= 820: + return LinkInfoDTO820(**kwargs) + return LinkInfoDTOBase(**kwargs) + + @staticmethod + def _set_default_filters(version: int, link_info: LinkInfoDTOType) -> None: + """ + Sets default filters if the study version is 820 or higher. + + Args: + version (int): The study version. + link_info (LinkInfoDTOType): The created DTO object. + """ + if version >= 820 and isinstance(link_info, LinkInfoDTO820): if link_info.filter_synthesis is None: link_info.filter_synthesis = FilteringOptions.FILTER_SYNTHESIS if link_info.filter_year_by_year is None: link_info.filter_year_by_year = FilteringOptions.FILTER_YEAR_BY_YEAR - else: - link_info = LinkInfoDTOBase(**kwargs) - link_info._filters_provided = filters_provided - return link_info + @staticmethod + def _check_version_coherence(version: int, **kwargs) -> None: + """ + Checks if filters are provided for a study version lower than 820. + + Args: + version (int): The study version. + kwargs: The arguments passed for the DTO creation. + + Raises: + InvalidFieldForVersionError: If filters are provided for a version lower than 820. + """ + filters_provided = kwargs.get("filter_synthesis") is not None or kwargs.get("filter_year_by_year") is not None + if version < 820 and filters_provided: + raise InvalidFieldForVersionError( + "Filters filter_synthesis and filter_year_by_year cannot be used for study versions lower than 820." + ) + + @staticmethod + def create_parameters(study_version: int, link_creation_info: LinkInfoDTOType) -> t.Dict[str, t.Union[str, bool, float]]: + """ + Creates the parameters for the link creation command, handling version differences. + + Args: + study_version (int): The study version. + link_creation_info (LinkInfoDTOType): The link information for creation. + + Returns: + t.Dict[str, t.Union[str, bool, float]: A dictionary containing the parameters for the command. + """ + parameters = { + "hurdles-cost": link_creation_info.hurdles_cost, + "loop-flow": link_creation_info.loop_flow, + "use-phase-shifter": link_creation_info.use_phase_shifter, + "transmission-capacities": link_creation_info.transmission_capacities, + "asset-type": link_creation_info.asset_type, + "display-comments": link_creation_info.display_comments, + "colorr": link_creation_info.colorr, + "colorb": link_creation_info.colorb, + "colorg": link_creation_info.colorg, + "link-width": link_creation_info.link_width, + "link-style": link_creation_info.link_style, + } + + if study_version >= 820 and isinstance(link_creation_info, LinkInfoDTO820): + parameters["filter-synthesis"] = link_creation_info.filter_synthesis + parameters["filter-year-by-year"] = link_creation_info.filter_year_by_year + + return parameters + @all_optional_model @@ -96,7 +180,7 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn links_config = file_study.tree.get(["input", "links", area_id, "properties"]) for link in area.links: link_properties = links_config[link] - link_info_args = { + link_creation_data = { "version": int(study.version), "area1": area_id, "area2": link, @@ -109,21 +193,23 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn "filter_synthesis": link_properties.get("filter-synthesis"), "filter_year_by_year": link_properties.get("filter-year-by-year"), } + ui_parameters = {} if with_ui and links_config and link in links_config: - link_info_args.update( + ui_parameters.update( { - "colorr": str(link_properties.get("colorr", "112")), - "colorb": str(link_properties.get("colorb", "112")), - "colorg": str(link_properties.get("colorg", "112")), + "colorr": str(link_properties.get("colorr", DEFAULT_COLOR)), + "colorb": str(link_properties.get("colorb", DEFAULT_COLOR)), + "colorg": str(link_properties.get("colorg", DEFAULT_COLOR)), "link_width": link_properties.get("link-width", 1.0), - "link_style": link_properties.get("link-style", "plain"), + "link_style": link_properties.get("link-style", LinkStyle.PLAIN), } ) + link_creation_data.update(ui_parameters) else: - link_info_args.update( + link_creation_data.update( {"colorr": None, "colorb": None, "colorg": None, "link_width": None, "link_style": None} ) - link_info_dto = LinkInfoFactory.create_link_info(**link_info_args) + link_info_dto = LinkInfoFactory.create_link_info(**link_creation_data) result.append(link_info_dto) return result @@ -148,7 +234,6 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L filter_synthesis=link_creation_info.filter_synthesis if study_version >= 820 else None, filter_year_by_year=link_creation_info.filter_year_by_year if study_version >= 820 else None, ) - self.check_version_coherence(study_version, link_info_dto) storage_service = self.storage_service.get_storage(study) file_study = storage_service.get_raw(study) @@ -156,7 +241,7 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L command = CreateLink( area1=link_creation_info.area1, area2=link_creation_info.area2, - parameters=self.create_parameters(int(study.version), link_info_dto), + parameters=LinkInfoFactory.create_parameters(int(study.version), link_info_dto), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) @@ -217,11 +302,11 @@ def update_links_props( for (area1, area2), update_link_dto in update_links_by_ids.items(): # Update the link properties. old_link_dto = old_links_by_ids[(area1, area2)] - new_link_dto = old_link_dto.copy(update=update_link_dto.model_dump(by_alias=False, exclude_none=True)) - new_links_by_ids[(area1, area2)] = new_link_dto + updated_link_properties = old_link_dto.copy(update=update_link_dto.model_dump(by_alias=False, exclude_none=True)) + new_links_by_ids[(area1, area2)] = updated_link_properties # Convert the DTO to a configuration object and update the configuration file. - properties = LinkProperties(**new_link_dto.model_dump(by_alias=False)) + properties = LinkProperties(**updated_link_properties.model_dump(by_alias=False)) path = f"{_ALL_LINKS_PATH}/{area1}/properties/{area2}" cmd = UpdateConfig( target=path, @@ -233,36 +318,6 @@ def update_links_props( execute_or_add_commands(study, file_study, commands, self.storage_service) return new_links_by_ids - @staticmethod - def check_version_coherence(study_version: int, link_creation_info: LinkInfoDTOType) -> None: - if study_version < 820: - if getattr(link_creation_info, "_filters_provided", True): - raise InvalidFieldForVersionError( - f"You cannot specify a filter synthesis or filter year by year as your study version is earlier than v8.2" - ) - @staticmethod def get_table_schema() -> JSON: return LinkOutput.schema() - - @staticmethod - def create_parameters(study_version: int, link_creation_info: LinkInfoDTOType) -> t.Dict[str, str]: - parameters = { - "hurdles-cost": link_creation_info.hurdles_cost, - "loop-flow": link_creation_info.loop_flow, - "use-phase-shifter": link_creation_info.use_phase_shifter, - "transmission-capacities": link_creation_info.transmission_capacities, - "asset-type": link_creation_info.asset_type, - "display-comments": link_creation_info.display_comments, - "colorr": link_creation_info.colorr, - "colorb": link_creation_info.colorb, - "colorg": link_creation_info.colorg, - "link-width": link_creation_info.link_width, - "link-style": link_creation_info.link_style, - } - - if study_version >= 820 and isinstance(link_creation_info, LinkInfoDTO820): - parameters["filter-synthesis"] = link_creation_info.filter_synthesis - parameters["filter-year-by-year"] = link_creation_info.filter_year_by_year - - return parameters From 0a540b1f29542a340e5de6651e5a4f8a22b8a7fd Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 19 Sep 2024 16:10:59 +0200 Subject: [PATCH 014/103] refactor code --- antarest/study/business/link_management.py | 11 +++++++---- tests/integration/test_integration.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index dc4136d258..a6ab65ca40 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -128,7 +128,9 @@ def _check_version_coherence(version: int, **kwargs) -> None: ) @staticmethod - def create_parameters(study_version: int, link_creation_info: LinkInfoDTOType) -> t.Dict[str, t.Union[str, bool, float]]: + def create_parameters( + study_version: int, link_creation_info: LinkInfoDTOType + ) -> t.Dict[str, t.Union[str, bool, float]]: """ Creates the parameters for the link creation command, handling version differences. @@ -160,7 +162,6 @@ def create_parameters(study_version: int, link_creation_info: LinkInfoDTOType) - return parameters - @all_optional_model @camel_case_model class LinkOutput(LinkProperties): @@ -206,7 +207,7 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn ) link_creation_data.update(ui_parameters) else: - link_creation_data.update( + link_creation_data.update( {"colorr": None, "colorb": None, "colorg": None, "link_width": None, "link_style": None} ) link_info_dto = LinkInfoFactory.create_link_info(**link_creation_data) @@ -302,7 +303,9 @@ def update_links_props( for (area1, area2), update_link_dto in update_links_by_ids.items(): # Update the link properties. old_link_dto = old_links_by_ids[(area1, area2)] - updated_link_properties = old_link_dto.copy(update=update_link_dto.model_dump(by_alias=False, exclude_none=True)) + updated_link_properties = old_link_dto.copy( + update=update_link_dto.model_dump(by_alias=False, exclude_none=True) + ) new_links_by_ids[(area1, area2)] = updated_link_properties # Convert the DTO to a configuration object and update the configuration file. diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index b247d06cd7..271db351e8 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -632,7 +632,7 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: "link_width": 1.0, "loop_flow": False, "transmission_capacities": "enabled", - "use_phase_shifter": False + "use_phase_shifter": False, } ] From 40cb85b84d115fcd220f42a3299179159a873747 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Fri, 20 Sep 2024 10:18:46 +0200 Subject: [PATCH 015/103] fix mypy --- antarest/study/business/link_management.py | 34 +++++++++++-------- .../variantstudy/model/command/create_link.py | 8 ++--- .../storage/business/test_arealink_manager.py | 2 -- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index a6ab65ca40..137120121a 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -20,10 +20,8 @@ from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy from antarest.study.storage.rawstudy.model.filesystem.config.links import ( - AssetType, LinkProperties, - LinkStyle, - TransmissionCapacity, + LinkStyle, TransmissionCapacity, AssetType, ) from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions @@ -41,14 +39,14 @@ class LinkInfoDTOBase(BaseModel): hurdles_cost: t.Optional[bool] = False loop_flow: t.Optional[bool] = False use_phase_shifter: t.Optional[bool] = False - transmission_capacities: t.Optional[str] = TransmissionCapacity.ENABLED - asset_type: t.Optional[str] = AssetType.AC + transmission_capacities: t.Optional[str] = TransmissionCapacity.ENABLED.value + asset_type: t.Optional[str] = AssetType.AC.value display_comments: t.Optional[bool] = True colorr: t.Optional[str] = DEFAULT_COLOR colorb: t.Optional[str] = DEFAULT_COLOR colorg: t.Optional[str] = DEFAULT_COLOR link_width: t.Optional[float] = 1 - link_style: t.Optional[str] = LinkStyle.PLAIN + link_style: t.Optional[str] = LinkStyle.PLAIN.value class LinkInfoDTO820(LinkInfoDTOBase): @@ -61,7 +59,7 @@ class LinkInfoDTO820(LinkInfoDTOBase): class LinkInfoFactory: @staticmethod - def create_link_info(version: int, **kwargs) -> LinkInfoDTOType: + def create_link_info(version: int, **kwargs: t.Any) -> LinkInfoDTOType: """ Creates a LinkInfoDTO object corresponding to the specified version. @@ -79,7 +77,7 @@ def create_link_info(version: int, **kwargs) -> LinkInfoDTOType: return link_info @staticmethod - def _initialize_link_info(version: int, **kwargs) -> LinkInfoDTOType: + def _initialize_link_info(version: int, **kwargs: t.Any) -> LinkInfoDTOType: """ Initializes the LinkInfoDTO object based on the study version. @@ -110,7 +108,7 @@ def _set_default_filters(version: int, link_info: LinkInfoDTOType) -> None: link_info.filter_year_by_year = FilteringOptions.FILTER_YEAR_BY_YEAR @staticmethod - def _check_version_coherence(version: int, **kwargs) -> None: + def _check_version_coherence(version: int, **kwargs: t.Any) -> None: """ Checks if filters are provided for a study version lower than 820. @@ -130,7 +128,7 @@ def _check_version_coherence(version: int, **kwargs) -> None: @staticmethod def create_parameters( study_version: int, link_creation_info: LinkInfoDTOType - ) -> t.Dict[str, t.Union[str, bool, float]]: + ) -> t.Dict[str, t.Union[str, bool, float, None]]: """ Creates the parameters for the link creation command, handling version differences. @@ -139,7 +137,7 @@ def create_parameters( link_creation_info (LinkInfoDTOType): The link information for creation. Returns: - t.Dict[str, t.Union[str, bool, float]: A dictionary containing the parameters for the command. + t.Dict[str, t.Union[str, bool, float, None]: A dictionary containing the parameters for the command. """ parameters = { "hurdles-cost": link_creation_info.hurdles_cost, @@ -217,6 +215,14 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: study_version = int(study.version) + + if isinstance(link_creation_info, LinkInfoDTO820): + filter_synthesis = link_creation_info.filter_synthesis if study_version >= 820 else None + filter_year_by_year = link_creation_info.filter_year_by_year if study_version >= 820 else None + else: + filter_synthesis = None + filter_year_by_year = None + link_info_dto = LinkInfoFactory.create_link_info( version=int(study.version), area1=link_creation_info.area1, @@ -232,8 +238,8 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L colorg=link_creation_info.colorg, link_width=link_creation_info.link_width, link_style=link_creation_info.link_style, - filter_synthesis=link_creation_info.filter_synthesis if study_version >= 820 else None, - filter_year_by_year=link_creation_info.filter_year_by_year if study_version >= 820 else None, + filter_synthesis=filter_synthesis, + filter_year_by_year=filter_year_by_year, ) storage_service = self.storage_service.get_storage(study) @@ -242,7 +248,7 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L command = CreateLink( area1=link_creation_info.area1, area2=link_creation_info.area2, - parameters=LinkInfoFactory.create_parameters(int(study.version), link_info_dto), + parameters=LinkInfoFactory.create_parameters(study_version, link_info_dto), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index b1961efd5e..aea162b952 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -17,7 +17,7 @@ from antarest.core.model import JSON from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData -from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, LinkStyle, TransmissionCapacity +from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, TransmissionCapacity, LinkStyle from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix @@ -31,9 +31,9 @@ class LinkProperties: LOOP_FLOW: bool = False USE_PHASE_SHIFTER: bool = False DISPLAY_COMMENTS: bool = True - TRANSMISSION_CAPACITIES: TransmissionCapacity = "enabled" - ASSET_TYPE: AssetType = "ac" - LINK_STYLE: str = "plain" + TRANSMISSION_CAPACITIES: str = TransmissionCapacity.ENABLED.value + ASSET_TYPE: str = AssetType.AC.value + LINK_STYLE: str = LinkStyle.PLAIN.value LINK_WIDTH: int = 1 COLORR: int = 112 COLORG: int = 112 diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index ac2ac275c5..e7bf272220 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -27,7 +27,6 @@ from antarest.study.business.link_management import ( LinkInfoDTO820, LinkInfoDTOBase, - LinkInfoDTOType, LinkInfoFactory, LinkManager, ) @@ -48,7 +47,6 @@ from antarest.study.storage.variantstudy.model.dbmodel import VariantStudy from antarest.study.storage.variantstudy.model.model import CommandDTO from antarest.study.storage.variantstudy.variant_study_service import VariantStudyService -from tests.conftest_services import study_storage_service_fixture from tests.helpers import with_db_context from tests.storage.business.assets import ASSETS_DIR From 0d17337223b6bc2fb3ce97e0c3e09023053e6884 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Fri, 20 Sep 2024 15:35:27 +0200 Subject: [PATCH 016/103] fix mypy --- antarest/study/business/link_management.py | 108 ++++++++--------- .../variantstudy/model/command/create_link.py | 40 +++---- .../storage/business/test_arealink_manager.py | 111 +++++++++--------- .../model/command/test_create_link.py | 60 +++++----- 4 files changed, 148 insertions(+), 171 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 137120121a..847bbb74b2 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -20,8 +20,10 @@ from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy from antarest.study.storage.rawstudy.model.filesystem.config.links import ( + AssetType, LinkProperties, - LinkStyle, TransmissionCapacity, AssetType, + LinkStyle, + TransmissionCapacity, ) from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions @@ -119,8 +121,7 @@ def _check_version_coherence(version: int, **kwargs: t.Any) -> None: Raises: InvalidFieldForVersionError: If filters are provided for a version lower than 820. """ - filters_provided = kwargs.get("filter_synthesis") is not None or kwargs.get("filter_year_by_year") is not None - if version < 820 and filters_provided: + if version < 820 and kwargs.get("_filters_provided"): raise InvalidFieldForVersionError( "Filters filter_synthesis and filter_year_by_year cannot be used for study versions lower than 820." ) @@ -139,24 +140,7 @@ def create_parameters( Returns: t.Dict[str, t.Union[str, bool, float, None]: A dictionary containing the parameters for the command. """ - parameters = { - "hurdles-cost": link_creation_info.hurdles_cost, - "loop-flow": link_creation_info.loop_flow, - "use-phase-shifter": link_creation_info.use_phase_shifter, - "transmission-capacities": link_creation_info.transmission_capacities, - "asset-type": link_creation_info.asset_type, - "display-comments": link_creation_info.display_comments, - "colorr": link_creation_info.colorr, - "colorb": link_creation_info.colorb, - "colorg": link_creation_info.colorg, - "link-width": link_creation_info.link_width, - "link-style": link_creation_info.link_style, - } - - if study_version >= 820 and isinstance(link_creation_info, LinkInfoDTO820): - parameters["filter-synthesis"] = link_creation_info.filter_synthesis - parameters["filter-year-by-year"] = link_creation_info.filter_year_by_year - + parameters = link_creation_info.model_dump(exclude={"area1", "area2"}, exclude_none=True) return parameters @@ -175,23 +159,26 @@ def __init__(self, storage_service: StudyStorageService) -> None: def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkInfoDTOType]: file_study = self.storage_service.get_storage(study).get_raw(study) result = [] + for area_id, area in file_study.config.areas.items(): links_config = file_study.tree.get(["input", "links", area_id, "properties"]) + for link in area.links: link_properties = links_config[link] + link_creation_data = { - "version": int(study.version), "area1": area_id, "area2": link, - "hurdles_cost": link_properties.get("hurdles-cost"), - "loop_flow": link_properties.get("loop-flow"), - "use_phase_shifter": link_properties.get("use-phase-shifter"), - "transmission_capacities": link_properties.get("transmission-capacities"), - "asset_type": link_properties.get("asset-type"), - "display_comments": link_properties.get("display-comments"), - "filter_synthesis": link_properties.get("filter-synthesis"), - "filter_year_by_year": link_properties.get("filter-year-by-year"), + "hurdles_cost": link_properties.get("hurdles_cost"), + "loop_flow": link_properties.get("loop_flow"), + "use_phase_shifter": link_properties.get("use_phase_shifter"), + "transmission_capacities": link_properties.get("transmission_capacities"), + "asset_type": link_properties.get("asset_type"), + "display_comments": link_properties.get("display_comments"), + "filter_synthesis": link_properties.get("filter_synthesis"), + "filter_year_by_year": link_properties.get("filter_year_by_year"), } + ui_parameters = {} if with_ui and links_config and link in links_config: ui_parameters.update( @@ -199,48 +186,47 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn "colorr": str(link_properties.get("colorr", DEFAULT_COLOR)), "colorb": str(link_properties.get("colorb", DEFAULT_COLOR)), "colorg": str(link_properties.get("colorg", DEFAULT_COLOR)), - "link_width": link_properties.get("link-width", 1.0), - "link_style": link_properties.get("link-style", LinkStyle.PLAIN), + "link_width": link_properties.get("link_width", 1.0), + "link_style": link_properties.get("link_style", LinkStyle.PLAIN), } ) - link_creation_data.update(ui_parameters) - else: - link_creation_data.update( - {"colorr": None, "colorb": None, "colorg": None, "link_width": None, "link_style": None} - ) - link_info_dto = LinkInfoFactory.create_link_info(**link_creation_data) + link_creation_data.update(ui_parameters) + link_info_dto = LinkInfoFactory.create_link_info(int(study.version), **link_creation_data) result.append(link_info_dto) + return result def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: study_version = int(study.version) - if isinstance(link_creation_info, LinkInfoDTO820): - filter_synthesis = link_creation_info.filter_synthesis if study_version >= 820 else None - filter_year_by_year = link_creation_info.filter_year_by_year if study_version >= 820 else None + link_info_dto_data = { + "study_version": int(study.version), + "area1": link_creation_info.area1, + "area2": link_creation_info.area2, + "hurdles_cost": link_creation_info.hurdles_cost, + "loop_flow": link_creation_info.loop_flow, + "use_phase_shifter": link_creation_info.use_phase_shifter, + "transmission_capacities": link_creation_info.transmission_capacities, + "asset_type": link_creation_info.asset_type, + "display_comments": link_creation_info.display_comments, + "colorr": link_creation_info.colorr, + "colorb": link_creation_info.colorb, + "colorg": link_creation_info.colorg, + "link_width": link_creation_info.link_width, + "link_style": link_creation_info.link_style, + } + + if study_version >= 820 and isinstance(link_creation_info, LinkInfoDTO820): + link_info_dto_data["filter_synthesis"] = link_creation_info.filter_synthesis + link_info_dto_data["filter_year_by_year"] = link_creation_info.filter_year_by_year else: - filter_synthesis = None - filter_year_by_year = None + if isinstance(link_creation_info, LinkInfoDTO820) and ( + link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None + ): + link_info_dto_data["_filters_provided"] = True - link_info_dto = LinkInfoFactory.create_link_info( - version=int(study.version), - area1=link_creation_info.area1, - area2=link_creation_info.area2, - hurdles_cost=link_creation_info.hurdles_cost, - loop_flow=link_creation_info.loop_flow, - use_phase_shifter=link_creation_info.use_phase_shifter, - transmission_capacities=link_creation_info.transmission_capacities, - asset_type=link_creation_info.asset_type, - display_comments=link_creation_info.display_comments, - colorr=link_creation_info.colorr, - colorb=link_creation_info.colorb, - colorg=link_creation_info.colorg, - link_width=link_creation_info.link_width, - link_style=link_creation_info.link_style, - filter_synthesis=filter_synthesis, - filter_year_by_year=filter_year_by_year, - ) + link_info_dto = LinkInfoFactory.create_link_info(study_version, **link_info_dto_data) storage_service = self.storage_service.get_storage(study) file_study = storage_service.get_raw(study) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index aea162b952..3316b9ebd0 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -17,7 +17,7 @@ from antarest.core.model import JSON from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData -from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, TransmissionCapacity, LinkStyle +from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, LinkStyle, TransmissionCapacity from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix @@ -100,44 +100,44 @@ def _create_link_in_config(self, area_from: str, area_to: str, study_data: FileS @staticmethod def generate_link_properties(parameters: JSON) -> JSON: return { - "hurdles-cost": parameters.get( - "hurdles-cost", + "hurdles_cost": parameters.get( + "hurdles_cost", LinkProperties.HURDLES_COST, ), - "loop-flow": parameters.get("loop-flow", LinkProperties.LOOP_FLOW), - "use-phase-shifter": parameters.get( - "use-phase-shifter", + "loop_flow": parameters.get("loop_flow", LinkProperties.LOOP_FLOW), + "use_phase_shifter": parameters.get( + "use_phase_shifter", LinkProperties.USE_PHASE_SHIFTER, ), - "transmission-capacities": parameters.get( - "transmission-capacities", + "transmission_capacities": parameters.get( + "transmission_capacities", LinkProperties.TRANSMISSION_CAPACITIES, ), - "asset-type": parameters.get( - "asset-type", + "asset_type": parameters.get( + "asset_type", LinkProperties.ASSET_TYPE, ), - "link-style": parameters.get( - "link-style", + "link_style": parameters.get( + "link_style", LinkProperties.LINK_STYLE, ), - "link-width": parameters.get( - "link-width", + "link_width": parameters.get( + "link_width", LinkProperties.LINK_WIDTH, ), "colorr": parameters.get("colorr", LinkProperties.COLORR), "colorg": parameters.get("colorg", LinkProperties.COLORG), "colorb": parameters.get("colorb", LinkProperties.COLORB), - "display-comments": parameters.get( - "display-comments", + "display_comments": parameters.get( + "display_comments", LinkProperties.DISPLAY_COMMENTS, ), - "filter-synthesis": parameters.get( - "filter-synthesis", + "filter_synthesis": parameters.get( + "filter_synthesis", FilteringOptions.FILTER_SYNTHESIS, ), - "filter-year-by-year": parameters.get( - "filter-year-by-year", + "filter_year_by_year": parameters.get( + "filter_year_by_year", FilteringOptions.FILTER_YEAR_BY_YEAR, ), } diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index e7bf272220..26744b9224 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -24,12 +24,7 @@ from antarest.matrixstore.repository import MatrixContentRepository from antarest.matrixstore.service import SimpleMatrixService from antarest.study.business.area_management import AreaCreationDTO, AreaManager, AreaType, UpdateAreaUi -from antarest.study.business.link_management import ( - LinkInfoDTO820, - LinkInfoDTOBase, - LinkInfoFactory, - LinkManager, -) +from antarest.study.business.link_management import LinkInfoDTO820, LinkInfoDTOBase, LinkInfoFactory, LinkManager from antarest.study.model import Patch, PatchArea, PatchCluster, RawStudy, StudyAdditionalData from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService @@ -250,19 +245,19 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "area1": "test", "area2": "test2", "parameters": { - "hurdles-cost": False, - "loop-flow": False, - "use-phase-shifter": False, - "transmission-capacities": "enabled", - "asset-type": "ac", - "display-comments": True, + "hurdles_cost": False, + "loop_flow": False, + "use_phase_shifter": False, + "transmission_capacities": "enabled", + "asset_type": "ac", + "display_comments": True, "colorr": "112", "colorg": "112", "colorb": "112", - "link-width": 1.0, - "link-style": "plain", - "filter-synthesis": "hourly, daily, weekly, monthly, annual", - "filter-year-by-year": "hourly, daily, weekly, monthly, annual", + "link_width": 1.0, + "link_style": "plain", + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", }, }, ), @@ -286,17 +281,17 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "area1": "test", "area2": "test2", "parameters": { - "hurdles-cost": False, - "loop-flow": False, - "use-phase-shifter": False, - "transmission-capacities": "enabled", - "asset-type": "ac", - "display-comments": True, + "hurdles_cost": False, + "loop_flow": False, + "use_phase_shifter": False, + "transmission_capacities": "enabled", + "asset_type": "ac", + "display_comments": True, "colorr": "112", "colorg": "112", "colorb": "112", - "link-width": 1.0, - "link-style": "plain", + "link_width": 1.0, + "link_style": "plain", }, }, ), @@ -536,58 +531,58 @@ def test_get_all_area(): } }, ] - links = link_manager.get_all_links(study) + links = link_manager.get_all_links(study, with_ui=True) assert [ { "area1": "a1", "area2": "a2", - "asset_type": "dc", - "colorb": None, - "colorg": None, - "colorr": None, - "display_comments": False, + "asset_type": None, + "colorb": "112", + "colorg": "112", + "colorr": "112", + "display_comments": None, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", - "hurdles_cost": True, - "link_style": None, - "link_width": None, - "loop_flow": True, - "transmission_capacities": "enabled", - "use_phase_shifter": False, + "hurdles_cost": None, + "link_style": "plain", + "link_width": 1.0, + "loop_flow": None, + "transmission_capacities": None, + "use_phase_shifter": None, }, { "area1": "a1", "area2": "a3", - "asset_type": "ac", - "colorb": None, - "colorg": None, - "colorr": None, - "display_comments": True, + "asset_type": None, + "colorb": "112", + "colorg": "112", + "colorr": "112", + "display_comments": None, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", - "hurdles_cost": True, - "link_style": None, - "link_width": None, - "loop_flow": False, - "transmission_capacities": "enabled", - "use_phase_shifter": True, + "hurdles_cost": None, + "link_style": "plain", + "link_width": 1.0, + "loop_flow": None, + "transmission_capacities": None, + "use_phase_shifter": None, }, { "area1": "a2", "area2": "a3", - "asset_type": "ac", - "colorb": None, - "colorg": None, - "colorr": None, - "display_comments": True, + "asset_type": None, + "colorb": "112", + "colorg": "112", + "colorr": "112", + "display_comments": None, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", - "hurdles_cost": True, - "link_style": None, - "link_width": None, - "loop_flow": False, - "transmission_capacities": "enabled", - "use_phase_shifter": True, + "hurdles_cost": None, + "link_style": "plain", + "link_width": 1.0, + "loop_flow": None, + "transmission_capacities": None, + "use_phase_shifter": None, }, ] == [link.model_dump() for link in links] diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index 9d847f0f24..bf114f236d 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -110,19 +110,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): link = IniReader() link_data = link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert link_data[area2_id]["hurdles-cost"] == LinkProperties.HURDLES_COST - assert link_data[area2_id]["loop-flow"] == LinkProperties.LOOP_FLOW - assert link_data[area2_id]["use-phase-shifter"] == LinkProperties.USE_PHASE_SHIFTER - assert str(link_data[area2_id]["transmission-capacities"]) == LinkProperties.TRANSMISSION_CAPACITIES - assert str(link_data[area2_id]["asset-type"]) == LinkProperties.ASSET_TYPE - assert str(link_data[area2_id]["link-style"]) == LinkProperties.LINK_STYLE - assert int(link_data[area2_id]["link-width"]) == LinkProperties.LINK_WIDTH + assert link_data[area2_id]["hurdles_cost"] == LinkProperties.HURDLES_COST + assert link_data[area2_id]["loop_flow"] == LinkProperties.LOOP_FLOW + assert link_data[area2_id]["use_phase_shifter"] == LinkProperties.USE_PHASE_SHIFTER + assert str(link_data[area2_id]["transmission_capacities"]) == LinkProperties.TRANSMISSION_CAPACITIES + assert str(link_data[area2_id]["asset_type"]) == LinkProperties.ASSET_TYPE + assert str(link_data[area2_id]["link_style"]) == LinkProperties.LINK_STYLE + assert int(link_data[area2_id]["link_width"]) == LinkProperties.LINK_WIDTH assert int(link_data[area2_id]["colorr"]) == LinkProperties.COLORR assert int(link_data[area2_id]["colorg"]) == LinkProperties.COLORG assert int(link_data[area2_id]["colorb"]) == LinkProperties.COLORB - assert link_data[area2_id]["display-comments"] == LinkProperties.DISPLAY_COMMENTS - assert str(link_data[area2_id]["filter-synthesis"]) == FilteringOptions.FILTER_SYNTHESIS - assert str(link_data[area2_id]["filter-year-by-year"]) == FilteringOptions.FILTER_YEAR_BY_YEAR + assert link_data[area2_id]["display_comments"] == LinkProperties.DISPLAY_COMMENTS empty_study.config.version = 820 create_link_command: ICommand = CreateLink( @@ -157,19 +155,19 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): assert not output.status parameters = { - "hurdles-cost": "true", - "loop-flow": "true", - "use-phase-shifter": "true", - "transmission-capacities": "disabled", - "asset-type": "dc", - "link-style": "other", - "link-width": 12, + "hurdles_cost": "true", + "loop_flow": "true", + "use_phase_shifter": "true", + "transmission_capacities": "disabled", + "asset_type": "dc", + "link_style": "other", + "link_width": 12, "colorr": 120, "colorg": 120, "colorb": 120, - "display-comments": "true", - "filter-synthesis": "hourly", - "filter-year-by-year": "hourly", + "display_comments": "true", + "filter_synthesis": "hourly", + "filter_year_by_year": "hourly", } create_link_command: ICommand = CreateLink.model_validate( @@ -191,19 +189,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): link = configparser.ConfigParser() link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert str(link[area3_id]["hurdles-cost"]) == parameters["hurdles-cost"] - assert str(link[area3_id]["loop-flow"]) == parameters["loop-flow"] - assert str(link[area3_id]["use-phase-shifter"]) == parameters["use-phase-shifter"] - assert str(link[area3_id]["transmission-capacities"]) == parameters["transmission-capacities"] - assert str(link[area3_id]["asset-type"]) == parameters["asset-type"] - assert str(link[area3_id]["link-style"]) == parameters["link-style"] - assert int(link[area3_id]["link-width"]) == parameters["link-width"] + assert str(link[area3_id]["hurdles_cost"]) == parameters["hurdles_cost"] + assert str(link[area3_id]["loop_flow"]) == parameters["loop_flow"] + assert str(link[area3_id]["use_phase_shifter"]) == parameters["use_phase_shifter"] + assert str(link[area3_id]["transmission_capacities"]) == parameters["transmission_capacities"] + assert str(link[area3_id]["asset_type"]) == parameters["asset_type"] + assert str(link[area3_id]["link_style"]) == parameters["link_style"] + assert int(link[area3_id]["link_width"]) == parameters["link_width"] assert int(link[area3_id]["colorr"]) == parameters["colorr"] assert int(link[area3_id]["colorg"]) == parameters["colorg"] assert int(link[area3_id]["colorb"]) == parameters["colorb"] - assert str(link[area3_id]["display-comments"]) == parameters["display-comments"] - assert str(link[area3_id]["filter-synthesis"]) == parameters["filter-synthesis"] - assert str(link[area3_id]["filter-year-by-year"]) == parameters["filter-year-by-year"] + assert str(link[area3_id]["display_comments"]) == parameters["display_comments"] output = create_link_command.apply( study_data=empty_study, @@ -258,7 +254,7 @@ def test_create_diff(command_context: CommandContext): other_match = CreateLink( area1="foo", area2="bar", - parameters={"hurdles-cost": "true"}, + parameters={"hurdles_cost": "true"}, series=series_b, command_context=command_context, ) @@ -266,7 +262,7 @@ def test_create_diff(command_context: CommandContext): assert base.create_diff(other_match) == [ UpdateConfig( target="input/links/bar/properties/foo", - data=CreateLink.generate_link_properties({"hurdles-cost": "true"}), + data=CreateLink.generate_link_properties(parameters={"hurdles_cost": "true"}), command_context=command_context, ), ReplaceMatrix( From 827fef27237ea584c53001bd9edf0622d4329e75 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Fri, 20 Sep 2024 15:46:54 +0200 Subject: [PATCH 017/103] fix mypy --- antarest/study/business/link_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 847bbb74b2..919dcb338c 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -222,7 +222,7 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L link_info_dto_data["filter_year_by_year"] = link_creation_info.filter_year_by_year else: if isinstance(link_creation_info, LinkInfoDTO820) and ( - link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None + link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None ): link_info_dto_data["_filters_provided"] = True From 3eb93dd2b4db5938797effd54deb3ed96ec430e7 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 23 Sep 2024 11:33:14 +0200 Subject: [PATCH 018/103] change ui handling --- antarest/study/business/link_management.py | 28 ++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 919dcb338c..e7731c476a 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -179,18 +179,22 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn "filter_year_by_year": link_properties.get("filter_year_by_year"), } - ui_parameters = {} - if with_ui and links_config and link in links_config: - ui_parameters.update( - { - "colorr": str(link_properties.get("colorr", DEFAULT_COLOR)), - "colorb": str(link_properties.get("colorb", DEFAULT_COLOR)), - "colorg": str(link_properties.get("colorg", DEFAULT_COLOR)), - "link_width": link_properties.get("link_width", 1.0), - "link_style": link_properties.get("link_style", LinkStyle.PLAIN), - } - ) - link_creation_data.update(ui_parameters) + if with_ui: + link_creation_data.update({ + "colorr": str(link_properties.get("colorr", DEFAULT_COLOR)), + "colorb": str(link_properties.get("colorb", DEFAULT_COLOR)), + "colorg": str(link_properties.get("colorg", DEFAULT_COLOR)), + "link_width": link_properties.get("link_width", 1.0), + "link_style": link_properties.get("link_style", LinkStyle.PLAIN), + }) + else: + link_creation_data.update({ + "colorr": None, + "colorb": None, + "colorg": None, + "link_width": None, + "link_style": None, + }) link_info_dto = LinkInfoFactory.create_link_info(int(study.version), **link_creation_data) result.append(link_info_dto) From a04fbe27a89667b7742d909f9be7594daa45fdcb Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 23 Sep 2024 14:57:45 +0200 Subject: [PATCH 019/103] black fixing --- antarest/study/business/link_management.py | 32 ++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index e7731c476a..5de515dac9 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -180,21 +180,25 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn } if with_ui: - link_creation_data.update({ - "colorr": str(link_properties.get("colorr", DEFAULT_COLOR)), - "colorb": str(link_properties.get("colorb", DEFAULT_COLOR)), - "colorg": str(link_properties.get("colorg", DEFAULT_COLOR)), - "link_width": link_properties.get("link_width", 1.0), - "link_style": link_properties.get("link_style", LinkStyle.PLAIN), - }) + link_creation_data.update( + { + "colorr": str(link_properties.get("colorr", DEFAULT_COLOR)), + "colorb": str(link_properties.get("colorb", DEFAULT_COLOR)), + "colorg": str(link_properties.get("colorg", DEFAULT_COLOR)), + "link_width": link_properties.get("link_width", 1.0), + "link_style": link_properties.get("link_style", LinkStyle.PLAIN), + } + ) else: - link_creation_data.update({ - "colorr": None, - "colorb": None, - "colorg": None, - "link_width": None, - "link_style": None, - }) + link_creation_data.update( + { + "colorr": None, + "colorb": None, + "colorg": None, + "link_width": None, + "link_style": None, + } + ) link_info_dto = LinkInfoFactory.create_link_info(int(study.version), **link_creation_data) result.append(link_info_dto) From ca0898f5a64077a617850f67db147ed67fc1ecf4 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 24 Sep 2024 11:10:59 +0200 Subject: [PATCH 020/103] fix errors --- antarest/study/business/link_management.py | 39 ++++----- .../variantstudy/model/command/create_link.py | 55 ++++++++++--- tests/integration/test_integration.py | 6 +- .../storage/business/test_arealink_manager.py | 80 +++++++++---------- .../model/command/test_create_link.py | 32 ++++---- 5 files changed, 124 insertions(+), 88 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 5de515dac9..df3ae28867 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,7 +12,8 @@ import typing as t -from pydantic import BaseModel +from pydantic import BaseModel, model_validator +from starlette.exceptions import HTTPException from antarest.core.exceptions import ConfigFileNotFound, InvalidFieldForVersionError from antarest.core.model import JSON @@ -32,7 +33,7 @@ from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig _ALL_LINKS_PATH = "input/links" -DEFAULT_COLOR = "112" +DEFAULT_COLOR = 112 class LinkInfoDTOBase(BaseModel): @@ -44,18 +45,18 @@ class LinkInfoDTOBase(BaseModel): transmission_capacities: t.Optional[str] = TransmissionCapacity.ENABLED.value asset_type: t.Optional[str] = AssetType.AC.value display_comments: t.Optional[bool] = True - colorr: t.Optional[str] = DEFAULT_COLOR - colorb: t.Optional[str] = DEFAULT_COLOR - colorg: t.Optional[str] = DEFAULT_COLOR + colorr: t.Optional[int] = DEFAULT_COLOR + colorb: t.Optional[int] = DEFAULT_COLOR + colorg: t.Optional[int] = DEFAULT_COLOR link_width: t.Optional[float] = 1 link_style: t.Optional[str] = LinkStyle.PLAIN.value + class LinkInfoDTO820(LinkInfoDTOBase): filter_synthesis: t.Optional[str] = None filter_year_by_year: t.Optional[str] = None - LinkInfoDTOType = t.Union[LinkInfoDTO820, LinkInfoDTOBase] @@ -169,24 +170,24 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn link_creation_data = { "area1": area_id, "area2": link, - "hurdles_cost": link_properties.get("hurdles_cost"), - "loop_flow": link_properties.get("loop_flow"), - "use_phase_shifter": link_properties.get("use_phase_shifter"), - "transmission_capacities": link_properties.get("transmission_capacities"), - "asset_type": link_properties.get("asset_type"), - "display_comments": link_properties.get("display_comments"), - "filter_synthesis": link_properties.get("filter_synthesis"), - "filter_year_by_year": link_properties.get("filter_year_by_year"), + "hurdles_cost": link_properties.get("hurdles-cost"), + "loop_flow": link_properties.get("loop-flow"), + "use_phase_shifter": link_properties.get("use-phase-shifter"), + "transmission_capacities": link_properties.get("transmission-capacities"), + "asset_type": link_properties.get("asset-type"), + "display_comments": link_properties.get("display-comments"), + "filter_synthesis": link_properties.get("filter-synthesis"), + "filter_year_by_year": link_properties.get("filter-year-by-year"), } if with_ui: link_creation_data.update( { - "colorr": str(link_properties.get("colorr", DEFAULT_COLOR)), - "colorb": str(link_properties.get("colorb", DEFAULT_COLOR)), - "colorg": str(link_properties.get("colorg", DEFAULT_COLOR)), - "link_width": link_properties.get("link_width", 1.0), - "link_style": link_properties.get("link_style", LinkStyle.PLAIN), + "colorr": link_properties.get("colorr", DEFAULT_COLOR), + "colorb": link_properties.get("colorb", DEFAULT_COLOR), + "colorg": link_properties.get("colorg", DEFAULT_COLOR), + "link_width": link_properties.get("link-width", 1.0), + "link_style": link_properties.get("link-style", LinkStyle.PLAIN), } ) else: diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 3316b9ebd0..6185d75dce 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -72,12 +72,47 @@ def validate_series( new_values = values if isinstance(values, dict) else values.data return validate_matrix(v, new_values) if v is not None else v + @model_validator(mode="after") + def validate_colors(self) -> "CreateLink": + parameters = self.parameters or {} + + colors = { + "colorr": parameters.get("colorr"), + "colorb": parameters.get("colorb"), + "colorg": parameters.get("colorg"), + } + for color_name, color_value in colors.items(): + if color_value is not None and (color_value < 0 or color_value > 255): + raise ValueError(f"Invalid value for {color_name}. Must be between 0 and 255.") + + return self + + @model_validator(mode="after") + def validate_filters(self) -> "CreateLink": + parameters = self.parameters or {} + filter_options = ["hourly", "daily", "weekly", "monthly", "annual"] + + filters = { + "filter_synthesis": parameters.get("filter_synthesis"), + "filter_year_by_year": parameters.get("filter_year_by_year"), + } + for filter_name, val in filters.items(): + if val is not None: + options = val.replace(" ", "").split(",") + invalid_options = [opt for opt in options if opt not in filter_options] + if invalid_options: + raise ValueError(f"Invalid value(s) in '{filter_name}': {', '.join(invalid_options)}. " + f"Allowed values are: {', '.join(filter_options)}.") + + return self + @model_validator(mode="after") def validate_areas(self) -> "CreateLink": if self.area1 == self.area2: raise ValueError("Cannot create link on same node") return self + def _create_link_in_config(self, area_from: str, area_to: str, study_data: FileStudyTreeConfig) -> None: self.parameters = self.parameters or {} study_data.areas[area_from].links[area_to] = Link( @@ -100,43 +135,43 @@ def _create_link_in_config(self, area_from: str, area_to: str, study_data: FileS @staticmethod def generate_link_properties(parameters: JSON) -> JSON: return { - "hurdles_cost": parameters.get( + "hurdles-cost": parameters.get( "hurdles_cost", LinkProperties.HURDLES_COST, ), - "loop_flow": parameters.get("loop_flow", LinkProperties.LOOP_FLOW), - "use_phase_shifter": parameters.get( + "loop-flow": parameters.get("loop_flow", LinkProperties.LOOP_FLOW), + "use-phase-shifter": parameters.get( "use_phase_shifter", LinkProperties.USE_PHASE_SHIFTER, ), - "transmission_capacities": parameters.get( + "transmission-capacities": parameters.get( "transmission_capacities", LinkProperties.TRANSMISSION_CAPACITIES, ), - "asset_type": parameters.get( + "asset-type": parameters.get( "asset_type", LinkProperties.ASSET_TYPE, ), - "link_style": parameters.get( + "link-style": parameters.get( "link_style", LinkProperties.LINK_STYLE, ), - "link_width": parameters.get( + "link-width": parameters.get( "link_width", LinkProperties.LINK_WIDTH, ), "colorr": parameters.get("colorr", LinkProperties.COLORR), "colorg": parameters.get("colorg", LinkProperties.COLORG), "colorb": parameters.get("colorb", LinkProperties.COLORB), - "display_comments": parameters.get( + "display-comments": parameters.get( "display_comments", LinkProperties.DISPLAY_COMMENTS, ), - "filter_synthesis": parameters.get( + "filter-synthesis": parameters.get( "filter_synthesis", FilteringOptions.FILTER_SYNTHESIS, ), - "filter_year_by_year": parameters.get( + "filter-year-by-year": parameters.get( "filter_year_by_year", FilteringOptions.FILTER_YEAR_BY_YEAR, ), diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 271db351e8..47e3910b5c 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -621,9 +621,9 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: "area1": "area 1", "area2": "area 2", "asset_type": "ac", - "colorb": "112", - "colorg": "112", - "colorr": "112", + "colorb": 112, + "colorg": 112, + "colorr": 112, "display_comments": True, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 26744b9224..8dadcae767 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -251,9 +251,9 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "transmission_capacities": "enabled", "asset_type": "ac", "display_comments": True, - "colorr": "112", - "colorg": "112", - "colorb": "112", + "colorr": 112, + "colorg": 112, + "colorb": 112, "link_width": 1.0, "link_style": "plain", "filter_synthesis": "hourly, daily, weekly, monthly, annual", @@ -287,9 +287,9 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "transmission_capacities": "enabled", "asset_type": "ac", "display_comments": True, - "colorr": "112", - "colorg": "112", - "colorb": "112", + "colorr": 112, + "colorg": 112, + "colorb": 112, "link_width": 1.0, "link_style": "plain", }, @@ -486,17 +486,17 @@ def test_get_all_area(): file_tree_mock.get.side_effect = [ { "a2": { - "hurdles-cost": True, - "loop-flow": True, - "use-phase-shifter": False, + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": True, "transmission-capacities": TransmissionCapacity.ENABLED, - "asset-type": AssetType.DC, - "display-comments": False, + "asset-type": AssetType.AC, + "display-comments": True, "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, }, "a3": { - "hurdles-cost": True, + "hurdles-cost": False, "loop-flow": False, "use-phase-shifter": True, "transmission-capacities": TransmissionCapacity.ENABLED, @@ -508,7 +508,7 @@ def test_get_all_area(): }, { "a3": { - "hurdles-cost": True, + "hurdles-cost": False, "loop-flow": False, "use-phase-shifter": True, "transmission-capacities": TransmissionCapacity.ENABLED, @@ -536,53 +536,53 @@ def test_get_all_area(): { "area1": "a1", "area2": "a2", - "asset_type": None, - "colorb": "112", - "colorg": "112", - "colorr": "112", - "display_comments": None, + "asset_type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "display_comments": True, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", - "hurdles_cost": None, + "hurdles_cost": False, "link_style": "plain", "link_width": 1.0, - "loop_flow": None, - "transmission_capacities": None, - "use_phase_shifter": None, + "loop_flow": False, + "transmission_capacities": "enabled", + "use_phase_shifter": True, }, { "area1": "a1", "area2": "a3", - "asset_type": None, - "colorb": "112", - "colorg": "112", - "colorr": "112", - "display_comments": None, + "asset_type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "display_comments": True, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", - "hurdles_cost": None, + "hurdles_cost": False, "link_style": "plain", "link_width": 1.0, - "loop_flow": None, - "transmission_capacities": None, - "use_phase_shifter": None, + "loop_flow": False, + "transmission_capacities": "enabled", + "use_phase_shifter": True, }, { "area1": "a2", "area2": "a3", - "asset_type": None, - "colorb": "112", - "colorg": "112", - "colorr": "112", - "display_comments": None, + "asset_type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "display_comments": True, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", - "hurdles_cost": None, + "hurdles_cost": False, "link_style": "plain", "link_width": 1.0, - "loop_flow": None, - "transmission_capacities": None, - "use_phase_shifter": None, + "loop_flow": False, + "transmission_capacities": "enabled", + "use_phase_shifter": True, }, ] == [link.model_dump() for link in links] diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index bf114f236d..67094e67c4 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -110,17 +110,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): link = IniReader() link_data = link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert link_data[area2_id]["hurdles_cost"] == LinkProperties.HURDLES_COST - assert link_data[area2_id]["loop_flow"] == LinkProperties.LOOP_FLOW - assert link_data[area2_id]["use_phase_shifter"] == LinkProperties.USE_PHASE_SHIFTER - assert str(link_data[area2_id]["transmission_capacities"]) == LinkProperties.TRANSMISSION_CAPACITIES - assert str(link_data[area2_id]["asset_type"]) == LinkProperties.ASSET_TYPE - assert str(link_data[area2_id]["link_style"]) == LinkProperties.LINK_STYLE - assert int(link_data[area2_id]["link_width"]) == LinkProperties.LINK_WIDTH + assert link_data[area2_id]["hurdles-cost"] == LinkProperties.HURDLES_COST + assert link_data[area2_id]["loop-flow"] == LinkProperties.LOOP_FLOW + assert link_data[area2_id]["use-phase-shifter"] == LinkProperties.USE_PHASE_SHIFTER + assert str(link_data[area2_id]["transmission-capacities"]) == LinkProperties.TRANSMISSION_CAPACITIES + assert str(link_data[area2_id]["asset-type"]) == LinkProperties.ASSET_TYPE + assert str(link_data[area2_id]["link-style"]) == LinkProperties.LINK_STYLE + assert int(link_data[area2_id]["link-width"]) == LinkProperties.LINK_WIDTH assert int(link_data[area2_id]["colorr"]) == LinkProperties.COLORR assert int(link_data[area2_id]["colorg"]) == LinkProperties.COLORG assert int(link_data[area2_id]["colorb"]) == LinkProperties.COLORB - assert link_data[area2_id]["display_comments"] == LinkProperties.DISPLAY_COMMENTS + assert link_data[area2_id]["display-comments"] == LinkProperties.DISPLAY_COMMENTS empty_study.config.version = 820 create_link_command: ICommand = CreateLink( @@ -189,17 +189,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): link = configparser.ConfigParser() link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert str(link[area3_id]["hurdles_cost"]) == parameters["hurdles_cost"] - assert str(link[area3_id]["loop_flow"]) == parameters["loop_flow"] - assert str(link[area3_id]["use_phase_shifter"]) == parameters["use_phase_shifter"] - assert str(link[area3_id]["transmission_capacities"]) == parameters["transmission_capacities"] - assert str(link[area3_id]["asset_type"]) == parameters["asset_type"] - assert str(link[area3_id]["link_style"]) == parameters["link_style"] - assert int(link[area3_id]["link_width"]) == parameters["link_width"] + assert str(link[area3_id]["hurdles-cost"]) == parameters["hurdles_cost"] + assert str(link[area3_id]["loop-flow"]) == parameters["loop_flow"] + assert str(link[area3_id]["use-phase-shifter"]) == parameters["use_phase_shifter"] + assert str(link[area3_id]["transmission-capacities"]) == parameters["transmission_capacities"] + assert str(link[area3_id]["asset-type"]) == parameters["asset_type"] + assert str(link[area3_id]["link-style"]) == parameters["link_style"] + assert int(link[area3_id]["link-width"]) == parameters["link_width"] assert int(link[area3_id]["colorr"]) == parameters["colorr"] assert int(link[area3_id]["colorg"]) == parameters["colorg"] assert int(link[area3_id]["colorb"]) == parameters["colorb"] - assert str(link[area3_id]["display_comments"]) == parameters["display_comments"] + assert str(link[area3_id]["display-comments"]) == parameters["display_comments"] output = create_link_command.apply( study_data=empty_study, From 6a79ad60d826c7eb650528cd82752a889287274f Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 24 Sep 2024 11:12:26 +0200 Subject: [PATCH 021/103] fix errors --- antarest/study/business/link_management.py | 2 +- .../storage/variantstudy/model/command/create_link.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index df3ae28867..584bc69432 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -52,11 +52,11 @@ class LinkInfoDTOBase(BaseModel): link_style: t.Optional[str] = LinkStyle.PLAIN.value - class LinkInfoDTO820(LinkInfoDTOBase): filter_synthesis: t.Optional[str] = None filter_year_by_year: t.Optional[str] = None + LinkInfoDTOType = t.Union[LinkInfoDTO820, LinkInfoDTOBase] diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 6185d75dce..67932dfd46 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -101,8 +101,10 @@ def validate_filters(self) -> "CreateLink": options = val.replace(" ", "").split(",") invalid_options = [opt for opt in options if opt not in filter_options] if invalid_options: - raise ValueError(f"Invalid value(s) in '{filter_name}': {', '.join(invalid_options)}. " - f"Allowed values are: {', '.join(filter_options)}.") + raise ValueError( + f"Invalid value(s) in '{filter_name}': {', '.join(invalid_options)}. " + f"Allowed values are: {', '.join(filter_options)}." + ) return self @@ -112,7 +114,6 @@ def validate_areas(self) -> "CreateLink": raise ValueError("Cannot create link on same node") return self - def _create_link_in_config(self, area_from: str, area_to: str, study_data: FileStudyTreeConfig) -> None: self.parameters = self.parameters or {} study_data.areas[area_from].links[area_to] = Link( From b6c0053d8934b39fb0b8e061a05b719cc65c656a Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 24 Sep 2024 13:24:38 +0200 Subject: [PATCH 022/103] fix errors --- antarest/study/business/link_management.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 584bc69432..2d934897a7 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,8 +12,7 @@ import typing as t -from pydantic import BaseModel, model_validator -from starlette.exceptions import HTTPException +from pydantic import BaseModel from antarest.core.exceptions import ConfigFileNotFound, InvalidFieldForVersionError from antarest.core.model import JSON From a047c307811a21f96b0e6b8b46710c8f5f6f6f10 Mon Sep 17 00:00:00 2001 From: belthlemar Date: Tue, 24 Sep 2024 14:09:52 +0200 Subject: [PATCH 023/103] refactor code --- antarest/core/utils/string.py | 4 + antarest/study/business/link_management.py | 72 ++++---------- .../study/business/table_mode_management.py | 6 +- .../rawstudy/model/filesystem/config/links.py | 6 +- .../variantstudy/model/command/create_link.py | 95 +++++++------------ .../study_data_blueprint/test_table_mode.py | 6 ++ .../storage/business/test_arealink_manager.py | 40 ++++---- .../model/command/test_create_link.py | 63 ++++++------ 8 files changed, 118 insertions(+), 174 deletions(-) diff --git a/antarest/core/utils/string.py b/antarest/core/utils/string.py index 35c5e75541..ab45405753 100644 --- a/antarest/core/utils/string.py +++ b/antarest/core/utils/string.py @@ -18,3 +18,7 @@ def to_pascal_case(value: str) -> str: def to_camel_case(value: str) -> str: v = to_pascal_case(value) return v[0].lower() + v[1:] if len(v) > 0 else "" + + +def to_kebab_case(string: str) -> str: + return string.replace("_", "-") diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 2d934897a7..8a919977e1 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,48 +12,33 @@ import typing as t -from pydantic import BaseModel - from antarest.core.exceptions import ConfigFileNotFound, InvalidFieldForVersionError from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy -from antarest.study.storage.rawstudy.model.filesystem.config.links import ( - AssetType, - LinkProperties, - LinkStyle, - TransmissionCapacity, -) +from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkStyle, LinkProperties from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions -from antarest.study.storage.variantstudy.model.command.create_link import CreateLink +from antarest.study.storage.variantstudy.model.command.create_link import ( + DEFAULT_COLOR, + AreaInfo, + CreateLink, + LinkInfoProperties, + LinkInfoProperties820 +) from antarest.study.storage.variantstudy.model.command.remove_link import RemoveLink from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig _ALL_LINKS_PATH = "input/links" -DEFAULT_COLOR = 112 -class LinkInfoDTOBase(BaseModel): - area1: str - area2: str - hurdles_cost: t.Optional[bool] = False - loop_flow: t.Optional[bool] = False - use_phase_shifter: t.Optional[bool] = False - transmission_capacities: t.Optional[str] = TransmissionCapacity.ENABLED.value - asset_type: t.Optional[str] = AssetType.AC.value - display_comments: t.Optional[bool] = True - colorr: t.Optional[int] = DEFAULT_COLOR - colorb: t.Optional[int] = DEFAULT_COLOR - colorg: t.Optional[int] = DEFAULT_COLOR - link_width: t.Optional[float] = 1 - link_style: t.Optional[str] = LinkStyle.PLAIN.value +class LinkInfoDTOBase(AreaInfo, LinkInfoProperties): + pass -class LinkInfoDTO820(LinkInfoDTOBase): - filter_synthesis: t.Optional[str] = None - filter_year_by_year: t.Optional[str] = None +class LinkInfoDTO820(AreaInfo, LinkInfoProperties820): + pass LinkInfoDTOType = t.Union[LinkInfoDTO820, LinkInfoDTOBase] @@ -166,37 +151,16 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn for link in area.links: link_properties = links_config[link] - link_creation_data = { - "area1": area_id, - "area2": link, - "hurdles_cost": link_properties.get("hurdles-cost"), - "loop_flow": link_properties.get("loop-flow"), - "use_phase_shifter": link_properties.get("use-phase-shifter"), - "transmission_capacities": link_properties.get("transmission-capacities"), - "asset_type": link_properties.get("asset-type"), - "display_comments": link_properties.get("display-comments"), - "filter_synthesis": link_properties.get("filter-synthesis"), - "filter_year_by_year": link_properties.get("filter-year-by-year"), - } - - if with_ui: - link_creation_data.update( - { - "colorr": link_properties.get("colorr", DEFAULT_COLOR), - "colorb": link_properties.get("colorb", DEFAULT_COLOR), - "colorg": link_properties.get("colorg", DEFAULT_COLOR), - "link_width": link_properties.get("link-width", 1.0), - "link_style": link_properties.get("link-style", LinkStyle.PLAIN), - } - ) - else: + link_creation_data: t.Dict[str, t.Any] = {"area1": area_id, "area2": link} + link_creation_data.update(link_properties) + if not with_ui: link_creation_data.update( { "colorr": None, "colorb": None, "colorg": None, - "link_width": None, - "link_style": None, + "link-width": None, + "link-style": None, } ) @@ -313,7 +277,7 @@ def update_links_props( path = f"{_ALL_LINKS_PATH}/{area1}/properties/{area2}" cmd = UpdateConfig( target=path, - data=properties.to_config(), + data=properties.to_ini(int(study.version)), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) commands.append(cmd) diff --git a/antarest/study/business/table_mode_management.py b/antarest/study/business/table_mode_management.py index 342c1c5abb..67a82c89aa 100644 --- a/antarest/study/business/table_mode_management.py +++ b/antarest/study/business/table_mode_management.py @@ -98,8 +98,9 @@ def _get_table_data_unsafe(self, study: RawStudy, table_type: TableModeType) -> data = {area_id: area.model_dump(by_alias=True) for area_id, area in areas_map.items()} elif table_type == TableModeType.LINK: links_map = self._link_manager.get_all_links_props(study) + excludes = set() if int(study.version) >= 820 else {"filter_synthesis", "filter_year_by_year"} data = { - f"{area1_id} / {area2_id}": link.model_dump(by_alias=True) + f"{area1_id} / {area2_id}": link.model_dump(by_alias=True, exclude=excludes) for (area1_id, area2_id), link in links_map.items() } elif table_type == TableModeType.THERMAL: @@ -195,8 +196,9 @@ def update_table_data( elif table_type == TableModeType.LINK: links_map = {tuple(key.split(" / ")): LinkOutput(**values) for key, values in data.items()} updated_map = self._link_manager.update_links_props(study, links_map) # type: ignore + excludes = set() if int(study.version) >= 820 else {"filter_synthesis", "filter_year_by_year"} data = { - f"{area1_id} / {area2_id}": link.model_dump(by_alias=True) + f"{area1_id} / {area2_id}": link.model_dump(by_alias=True, exclude=excludes) for (area1_id, area2_id), link in updated_map.items() } return data diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/links.py b/antarest/study/storage/rawstudy/model/filesystem/config/links.py index 31c4c72f2e..40e31dd072 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/links.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/links.py @@ -81,6 +81,7 @@ class LinkStyle(EnumIgnoreCase): PLAIN = "plain" DASH = "dash" DOT_DASH = "dotdash" + OTHER = 'other' class FilterOption(EnumIgnoreCase): @@ -195,11 +196,12 @@ def _validate_colors(cls, values: t.MutableMapping[str, t.Any]) -> t.Mapping[str return validate_colors(values) # noinspection SpellCheckingInspection - def to_config(self) -> t.Dict[str, t.Any]: + def to_ini(self, version: int) -> t.Dict[str, t.Any]: """ Convert the object to a dictionary for writing to a configuration file. """ - obj = dict(super().to_config()) + excludes = set() if version >= 820 else {"filter_synthesis", "filter_year_by_year"} + obj = self.model_dump(mode='json', exclude_none=True, by_alias=True, exclude=excludes) color_rgb = obj.pop("colorRgb", "#707070") return { "colorr": int(color_rgb[1:3], 16), diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 67932dfd46..510373f0d3 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -12,9 +12,9 @@ from typing import Any, Dict, List, Optional, Tuple, Union, cast -from pydantic import ValidationInfo, field_validator, model_validator +from pydantic import BaseModel, ValidationInfo, field_validator, model_validator, AliasGenerator -from antarest.core.model import JSON +from antarest.core.utils.string import to_kebab_case from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, LinkStyle, TransmissionCapacity @@ -25,22 +25,34 @@ from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand from antarest.study.storage.variantstudy.model.model import CommandDTO +DEFAULT_COLOR = 112 -class LinkProperties: - HURDLES_COST: bool = False - LOOP_FLOW: bool = False - USE_PHASE_SHIFTER: bool = False - DISPLAY_COMMENTS: bool = True - TRANSMISSION_CAPACITIES: str = TransmissionCapacity.ENABLED.value - ASSET_TYPE: str = AssetType.AC.value - LINK_STYLE: str = LinkStyle.PLAIN.value - LINK_WIDTH: int = 1 - COLORR: int = 112 - COLORG: int = 112 - COLORB: int = 112 + +class AreaInfo(BaseModel): + area1: str + area2: str + + +class LinkInfoProperties(BaseModel): + hurdles_cost: Optional[bool] = False + loop_flow: Optional[bool] = False + use_phase_shifter: Optional[bool] = False + transmission_capacities: Optional[TransmissionCapacity] = TransmissionCapacity.ENABLED + asset_type: Optional[AssetType] = AssetType.AC + display_comments: Optional[bool] = True + colorr: Optional[int] = DEFAULT_COLOR + colorb: Optional[int] = DEFAULT_COLOR + colorg: Optional[int] = DEFAULT_COLOR + link_width: Optional[float] = 1 + link_style: Optional[LinkStyle] = LinkStyle.PLAIN -class LinkAlreadyExistError(Exception): +class LinkInfoProperties820(LinkInfoProperties): + filter_synthesis: Optional[str] = None + filter_year_by_year: Optional[str] = None + + +class LinkProperties(LinkInfoProperties820, alias_generator=AliasGenerator(serialization_alias=to_kebab_case)): pass @@ -133,51 +145,6 @@ def _create_link_in_config(self, area_from: str, area_to: str, study_data: FileS ], ) - @staticmethod - def generate_link_properties(parameters: JSON) -> JSON: - return { - "hurdles-cost": parameters.get( - "hurdles_cost", - LinkProperties.HURDLES_COST, - ), - "loop-flow": parameters.get("loop_flow", LinkProperties.LOOP_FLOW), - "use-phase-shifter": parameters.get( - "use_phase_shifter", - LinkProperties.USE_PHASE_SHIFTER, - ), - "transmission-capacities": parameters.get( - "transmission_capacities", - LinkProperties.TRANSMISSION_CAPACITIES, - ), - "asset-type": parameters.get( - "asset_type", - LinkProperties.ASSET_TYPE, - ), - "link-style": parameters.get( - "link_style", - LinkProperties.LINK_STYLE, - ), - "link-width": parameters.get( - "link_width", - LinkProperties.LINK_WIDTH, - ), - "colorr": parameters.get("colorr", LinkProperties.COLORR), - "colorg": parameters.get("colorg", LinkProperties.COLORG), - "colorb": parameters.get("colorb", LinkProperties.COLORB), - "display-comments": parameters.get( - "display_comments", - LinkProperties.DISPLAY_COMMENTS, - ), - "filter-synthesis": parameters.get( - "filter_synthesis", - FilteringOptions.FILTER_SYNTHESIS, - ), - "filter-year-by-year": parameters.get( - "filter_year_by_year", - FilteringOptions.FILTER_YEAR_BY_YEAR, - ), - } - def _apply_config(self, study_data: FileStudyTreeConfig) -> Tuple[CommandOutput, Dict[str, Any]]: if self.area1 not in study_data.areas: return ( @@ -249,8 +216,9 @@ def _apply(self, study_data: FileStudy) -> CommandOutput: area_from = data["area_from"] area_to = data["area_to"] - self.parameters = self.parameters or {} - link_property = CreateLink.generate_link_properties(self.parameters) + properties = LinkProperties.model_validate(self.parameters or {}) + excludes = set() if version >= 820 else {"filter_synthesis", "filter_year_by_year"} + link_property = properties.model_dump(mode="json", exclude=excludes, by_alias=True, exclude_none=True) study_data.tree.save(link_property, ["input", "links", area_from, "properties", area_to]) self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) @@ -339,7 +307,8 @@ def _create_diff(self, other: "ICommand") -> List["ICommand"]: commands: List[ICommand] = [] area_from, area_to = sorted([self.area1, self.area2]) if self.parameters != other.parameters: - link_property = CreateLink.generate_link_properties(other.parameters or {}) + properties = LinkProperties.model_validate(other.parameters or {}) + link_property = properties.model_dump(mode="json", by_alias=True, exclude_none=True) commands.append( UpdateConfig( target=f"input/links/{area_from}/properties/{area_to}", diff --git a/tests/integration/study_data_blueprint/test_table_mode.py b/tests/integration/study_data_blueprint/test_table_mode.py index 98d7f03393..a7b1878020 100644 --- a/tests/integration/study_data_blueprint/test_table_mode.py +++ b/tests/integration/study_data_blueprint/test_table_mode.py @@ -293,6 +293,12 @@ def test_lifecycle__nominal( "usePhaseShifter": False, }, } + # removes filter fields for study version prior to v8.2 + if study_version < 820: + for key in expected_links: + del expected_links[key]["filterSynthesis"] + del expected_links[key]["filterYearByYear"] + # asserts actual equals expected without the non-updated link. actual = res.json() expected_result = copy.deepcopy(expected_links) diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 8dadcae767..f3336865f7 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -29,7 +29,7 @@ from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService from antarest.study.storage.rawstudy.model.filesystem.config.files import build -from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, TransmissionCapacity +from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, TransmissionCapacity, LinkStyle from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, DistrictSet, FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.config.thermal import ThermalConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy @@ -248,14 +248,14 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "hurdles_cost": False, "loop_flow": False, "use_phase_shifter": False, - "transmission_capacities": "enabled", - "asset_type": "ac", + "transmission_capacities": TransmissionCapacity.ENABLED, + "asset_type": AssetType.AC, "display_comments": True, "colorr": 112, "colorg": 112, "colorb": 112, "link_width": 1.0, - "link_style": "plain", + "link_style": LinkStyle.PLAIN, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", }, @@ -284,14 +284,14 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "hurdles_cost": False, "loop_flow": False, "use_phase_shifter": False, - "transmission_capacities": "enabled", - "asset_type": "ac", + "transmission_capacities": TransmissionCapacity.ENABLED, + "asset_type": AssetType.AC, "display_comments": True, "colorr": 112, "colorg": 112, "colorb": 112, "link_width": 1.0, - "link_style": "plain", + "link_style": LinkStyle.PLAIN, }, }, ), @@ -486,17 +486,17 @@ def test_get_all_area(): file_tree_mock.get.side_effect = [ { "a2": { - "hurdles-cost": False, - "loop-flow": False, - "use-phase-shifter": True, + "hurdles-cost": True, + "loop-flow": True, + "use-phase-shifter": False, "transmission-capacities": TransmissionCapacity.ENABLED, - "asset-type": AssetType.AC, - "display-comments": True, + "asset-type": AssetType.DC, + "display-comments": False, "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, }, "a3": { - "hurdles-cost": False, + "hurdles-cost": True, "loop-flow": False, "use-phase-shifter": True, "transmission-capacities": TransmissionCapacity.ENABLED, @@ -508,7 +508,7 @@ def test_get_all_area(): }, { "a3": { - "hurdles-cost": False, + "hurdles-cost": True, "loop-flow": False, "use-phase-shifter": True, "transmission-capacities": TransmissionCapacity.ENABLED, @@ -548,12 +548,12 @@ def test_get_all_area(): "link_width": 1.0, "loop_flow": False, "transmission_capacities": "enabled", - "use_phase_shifter": True, + "use_phase_shifter": False, }, { "area1": "a1", "area2": "a3", - "asset_type": "ac", + "asset_type": 'ac', "colorb": 112, "colorg": 112, "colorr": 112, @@ -565,12 +565,12 @@ def test_get_all_area(): "link_width": 1.0, "loop_flow": False, "transmission_capacities": "enabled", - "use_phase_shifter": True, + "use_phase_shifter": False, }, { "area1": "a2", "area2": "a3", - "asset_type": "ac", + "asset_type": 'ac', "colorb": 112, "colorg": 112, "colorr": 112, @@ -582,9 +582,9 @@ def test_get_all_area(): "link_width": 1.0, "loop_flow": False, "transmission_capacities": "enabled", - "use_phase_shifter": True, + "use_phase_shifter": False, }, - ] == [link.model_dump() for link in links] + ] == [link.model_dump(mode='json') for link in links] def test_update_area(): diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index 67094e67c4..24986e1490 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -10,8 +10,6 @@ # # This file is part of the Antares project. -import configparser - import numpy as np import pytest from pydantic import ValidationError @@ -20,7 +18,6 @@ from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter -from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_area import CreateArea from antarest.study.storage.variantstudy.model.command.create_link import CreateLink, LinkProperties from antarest.study.storage.variantstudy.model.command.icommand import ICommand @@ -110,17 +107,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): link = IniReader() link_data = link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert link_data[area2_id]["hurdles-cost"] == LinkProperties.HURDLES_COST - assert link_data[area2_id]["loop-flow"] == LinkProperties.LOOP_FLOW - assert link_data[area2_id]["use-phase-shifter"] == LinkProperties.USE_PHASE_SHIFTER - assert str(link_data[area2_id]["transmission-capacities"]) == LinkProperties.TRANSMISSION_CAPACITIES - assert str(link_data[area2_id]["asset-type"]) == LinkProperties.ASSET_TYPE - assert str(link_data[area2_id]["link-style"]) == LinkProperties.LINK_STYLE - assert int(link_data[area2_id]["link-width"]) == LinkProperties.LINK_WIDTH - assert int(link_data[area2_id]["colorr"]) == LinkProperties.COLORR - assert int(link_data[area2_id]["colorg"]) == LinkProperties.COLORG - assert int(link_data[area2_id]["colorb"]) == LinkProperties.COLORB - assert link_data[area2_id]["display-comments"] == LinkProperties.DISPLAY_COMMENTS + assert link_data[area2_id]["hurdles-cost"] is False + assert link_data[area2_id]["loop-flow"] is False + assert link_data[area2_id]["use-phase-shifter"] is False + assert link_data[area2_id]["transmission-capacities"] == "enabled" + assert link_data[area2_id]["asset-type"] == "ac" + assert link_data[area2_id]["link-style"] == "plain" + assert int(link_data[area2_id]["link-width"]) == 1 + assert int(link_data[area2_id]["colorr"]) == 112 + assert int(link_data[area2_id]["colorg"]) == 112 + assert int(link_data[area2_id]["colorb"]) == 112 + assert link_data[area2_id]["display-comments"] is True empty_study.config.version = 820 create_link_command: ICommand = CreateLink( @@ -155,17 +152,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): assert not output.status parameters = { - "hurdles_cost": "true", - "loop_flow": "true", - "use_phase_shifter": "true", - "transmission_capacities": "disabled", + "hurdles_cost": True, + "loop_flow": True, + "use_phase_shifter": True, + "transmission_capacities": "ignore", "asset_type": "dc", "link_style": "other", "link_width": 12, "colorr": 120, "colorg": 120, "colorb": 120, - "display_comments": "true", + "display_comments": True, "filter_synthesis": "hourly", "filter_year_by_year": "hourly", } @@ -187,19 +184,19 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): assert (study_path / "input" / "links" / area1_id / f"{area3_id}.txt.link").exists() - link = configparser.ConfigParser() - link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert str(link[area3_id]["hurdles-cost"]) == parameters["hurdles_cost"] - assert str(link[area3_id]["loop-flow"]) == parameters["loop_flow"] - assert str(link[area3_id]["use-phase-shifter"]) == parameters["use_phase_shifter"] - assert str(link[area3_id]["transmission-capacities"]) == parameters["transmission_capacities"] - assert str(link[area3_id]["asset-type"]) == parameters["asset_type"] - assert str(link[area3_id]["link-style"]) == parameters["link_style"] - assert int(link[area3_id]["link-width"]) == parameters["link_width"] - assert int(link[area3_id]["colorr"]) == parameters["colorr"] - assert int(link[area3_id]["colorg"]) == parameters["colorg"] - assert int(link[area3_id]["colorb"]) == parameters["colorb"] - assert str(link[area3_id]["display-comments"]) == parameters["display_comments"] + link = IniReader() + link_data = link.read(study_path / "input" / "links" / area1_id / "properties.ini") + assert link_data[area3_id]["hurdles-cost"] == parameters["hurdles_cost"] + assert link_data[area3_id]["loop-flow"] == parameters["loop_flow"] + assert link_data[area3_id]["use-phase-shifter"] == parameters["use_phase_shifter"] + assert link_data[area3_id]["transmission-capacities"] == parameters["transmission_capacities"] + assert link_data[area3_id]["asset-type"] == parameters["asset_type"] + assert link_data[area3_id]["link-style"] == parameters["link_style"] + assert int(link_data[area3_id]["link-width"]) == parameters["link_width"] + assert int(link_data[area3_id]["colorr"]) == parameters["colorr"] + assert int(link_data[area3_id]["colorg"]) == parameters["colorg"] + assert int(link_data[area3_id]["colorb"]) == parameters["colorb"] + assert link_data[area3_id]["display-comments"] == parameters["display_comments"] output = create_link_command.apply( study_data=empty_study, @@ -262,7 +259,7 @@ def test_create_diff(command_context: CommandContext): assert base.create_diff(other_match) == [ UpdateConfig( target="input/links/bar/properties/foo", - data=CreateLink.generate_link_properties(parameters={"hurdles_cost": "true"}), + data=LinkProperties.model_validate({"hurdles_cost": "true"}).model_dump(by_alias=True, exclude_none=True), command_context=command_context, ), ReplaceMatrix( From 60c0c52459d4a84d9ff45449b5d61016eeb35992 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 24 Sep 2024 14:14:56 +0200 Subject: [PATCH 024/103] isort and black --- antarest/study/business/link_management.py | 5 ++--- .../storage/rawstudy/model/filesystem/config/links.py | 4 ++-- .../storage/variantstudy/model/command/create_link.py | 2 +- tests/storage/business/test_arealink_manager.py | 8 ++++---- tests/variantstudy/model/command/test_create_link.py | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 8a919977e1..a82c574837 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -17,15 +17,14 @@ from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy -from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkStyle, LinkProperties +from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties, LinkStyle from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_link import ( - DEFAULT_COLOR, AreaInfo, CreateLink, LinkInfoProperties, - LinkInfoProperties820 + LinkInfoProperties820, ) from antarest.study.storage.variantstudy.model.command.remove_link import RemoveLink from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/links.py b/antarest/study/storage/rawstudy/model/filesystem/config/links.py index 40e31dd072..624f639784 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/links.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/links.py @@ -81,7 +81,7 @@ class LinkStyle(EnumIgnoreCase): PLAIN = "plain" DASH = "dash" DOT_DASH = "dotdash" - OTHER = 'other' + OTHER = "other" class FilterOption(EnumIgnoreCase): @@ -201,7 +201,7 @@ def to_ini(self, version: int) -> t.Dict[str, t.Any]: Convert the object to a dictionary for writing to a configuration file. """ excludes = set() if version >= 820 else {"filter_synthesis", "filter_year_by_year"} - obj = self.model_dump(mode='json', exclude_none=True, by_alias=True, exclude=excludes) + obj = self.model_dump(mode="json", exclude_none=True, by_alias=True, exclude=excludes) color_rgb = obj.pop("colorRgb", "#707070") return { "colorr": int(color_rgb[1:3], 16), diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 510373f0d3..e2830bf988 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -12,7 +12,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union, cast -from pydantic import BaseModel, ValidationInfo, field_validator, model_validator, AliasGenerator +from pydantic import AliasGenerator, BaseModel, ValidationInfo, field_validator, model_validator from antarest.core.utils.string import to_kebab_case from antarest.core.utils.utils import assert_this diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index f3336865f7..5138f42649 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -29,7 +29,7 @@ from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService from antarest.study.storage.rawstudy.model.filesystem.config.files import build -from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, TransmissionCapacity, LinkStyle +from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, LinkStyle, TransmissionCapacity from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, DistrictSet, FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.config.thermal import ThermalConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy @@ -553,7 +553,7 @@ def test_get_all_area(): { "area1": "a1", "area2": "a3", - "asset_type": 'ac', + "asset_type": "ac", "colorb": 112, "colorg": 112, "colorr": 112, @@ -570,7 +570,7 @@ def test_get_all_area(): { "area1": "a2", "area2": "a3", - "asset_type": 'ac', + "asset_type": "ac", "colorb": 112, "colorg": 112, "colorr": 112, @@ -584,7 +584,7 @@ def test_get_all_area(): "transmission_capacities": "enabled", "use_phase_shifter": False, }, - ] == [link.model_dump(mode='json') for link in links] + ] == [link.model_dump(mode="json") for link in links] def test_update_area(): diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index 24986e1490..ad97bc6ddf 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -109,7 +109,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): link_data = link.read(study_path / "input" / "links" / area1_id / "properties.ini") assert link_data[area2_id]["hurdles-cost"] is False assert link_data[area2_id]["loop-flow"] is False - assert link_data[area2_id]["use-phase-shifter"] is False + assert link_data[area2_id]["use-phase-shifter"] is False assert link_data[area2_id]["transmission-capacities"] == "enabled" assert link_data[area2_id]["asset-type"] == "ac" assert link_data[area2_id]["link-style"] == "plain" From d520b83d7308fcb5094041c0be9a3217f240ec17 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 24 Sep 2024 17:46:19 +0200 Subject: [PATCH 025/103] refactor classes --- antarest/study/business/link_management.py | 146 ++++-------------- .../variantstudy/model/command/create_link.py | 103 ++++++------ tests/integration/test_integration.py | 20 +-- .../storage/business/test_arealink_manager.py | 68 ++++---- .../model/command/test_create_link.py | 47 +++--- 5 files changed, 139 insertions(+), 245 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index a82c574837..0298f269c1 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,12 +12,12 @@ import typing as t -from antarest.core.exceptions import ConfigFileNotFound, InvalidFieldForVersionError +from antarest.core.exceptions import ConfigFileNotFound from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy -from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties, LinkStyle +from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_link import ( @@ -43,91 +43,6 @@ class LinkInfoDTO820(AreaInfo, LinkInfoProperties820): LinkInfoDTOType = t.Union[LinkInfoDTO820, LinkInfoDTOBase] -class LinkInfoFactory: - @staticmethod - def create_link_info(version: int, **kwargs: t.Any) -> LinkInfoDTOType: - """ - Creates a LinkInfoDTO object corresponding to the specified version. - - Args: - version (int): The study version. - kwargs: The arguments passed for the DTO creation. - - Returns: - LinkInfoDTOType: An object of type LinkInfoDTOBase or LinkInfoDTO820 depending on the version. - """ - LinkInfoFactory._check_version_coherence(version, **kwargs) - link_info = LinkInfoFactory._initialize_link_info(version, **kwargs) - LinkInfoFactory._set_default_filters(version, link_info) - - return link_info - - @staticmethod - def _initialize_link_info(version: int, **kwargs: t.Any) -> LinkInfoDTOType: - """ - Initializes the LinkInfoDTO object based on the study version. - - Args: - version (int): The study version. - kwargs: The arguments passed for the DTO creation. - - Returns: - LinkInfoDTOType: An object of type LinkInfoDTOBase or LinkInfoDTO820 depending on the version. - """ - if version >= 820: - return LinkInfoDTO820(**kwargs) - return LinkInfoDTOBase(**kwargs) - - @staticmethod - def _set_default_filters(version: int, link_info: LinkInfoDTOType) -> None: - """ - Sets default filters if the study version is 820 or higher. - - Args: - version (int): The study version. - link_info (LinkInfoDTOType): The created DTO object. - """ - if version >= 820 and isinstance(link_info, LinkInfoDTO820): - if link_info.filter_synthesis is None: - link_info.filter_synthesis = FilteringOptions.FILTER_SYNTHESIS - if link_info.filter_year_by_year is None: - link_info.filter_year_by_year = FilteringOptions.FILTER_YEAR_BY_YEAR - - @staticmethod - def _check_version_coherence(version: int, **kwargs: t.Any) -> None: - """ - Checks if filters are provided for a study version lower than 820. - - Args: - version (int): The study version. - kwargs: The arguments passed for the DTO creation. - - Raises: - InvalidFieldForVersionError: If filters are provided for a version lower than 820. - """ - if version < 820 and kwargs.get("_filters_provided"): - raise InvalidFieldForVersionError( - "Filters filter_synthesis and filter_year_by_year cannot be used for study versions lower than 820." - ) - - @staticmethod - def create_parameters( - study_version: int, link_creation_info: LinkInfoDTOType - ) -> t.Dict[str, t.Union[str, bool, float, None]]: - """ - Creates the parameters for the link creation command, handling version differences. - - Args: - study_version (int): The study version. - link_creation_info (LinkInfoDTOType): The link information for creation. - - Returns: - t.Dict[str, t.Union[str, bool, float, None]: A dictionary containing the parameters for the command. - """ - parameters = link_creation_info.model_dump(exclude={"area1", "area2"}, exclude_none=True) - return parameters - - @all_optional_model @camel_case_model class LinkOutput(LinkProperties): @@ -142,7 +57,7 @@ def __init__(self, storage_service: StudyStorageService) -> None: def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkInfoDTOType]: file_study = self.storage_service.get_storage(study).get_raw(study) - result = [] + result: t.List[LinkInfoDTOType] = [] for area_id, area in file_study.config.areas.items(): links_config = file_study.tree.get(["input", "links", area_id, "properties"]) @@ -163,41 +78,36 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn } ) - link_info_dto = LinkInfoFactory.create_link_info(int(study.version), **link_creation_data) - result.append(link_info_dto) + link_data: LinkInfoDTOType + if int(study.version) < 820: + link_data = LinkInfoDTOBase(**link_creation_data) + else: + link_data = LinkInfoDTO820(**link_creation_data) + + result.append(link_data) return result def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: + if link_creation_info.area1 == link_creation_info.area2: + raise ValueError("Cannot create link on same node") + study_version = int(study.version) + if study_version < 820 and isinstance(link_creation_info, LinkInfoDTO820): + if link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None: + raise ValueError("Cannot specify a filter value for study's version earlier than v8.2") - link_info_dto_data = { - "study_version": int(study.version), - "area1": link_creation_info.area1, - "area2": link_creation_info.area2, - "hurdles_cost": link_creation_info.hurdles_cost, - "loop_flow": link_creation_info.loop_flow, - "use_phase_shifter": link_creation_info.use_phase_shifter, - "transmission_capacities": link_creation_info.transmission_capacities, - "asset_type": link_creation_info.asset_type, - "display_comments": link_creation_info.display_comments, - "colorr": link_creation_info.colorr, - "colorb": link_creation_info.colorb, - "colorg": link_creation_info.colorg, - "link_width": link_creation_info.link_width, - "link_style": link_creation_info.link_style, - } - - if study_version >= 820 and isinstance(link_creation_info, LinkInfoDTO820): - link_info_dto_data["filter_synthesis"] = link_creation_info.filter_synthesis - link_info_dto_data["filter_year_by_year"] = link_creation_info.filter_year_by_year - else: - if isinstance(link_creation_info, LinkInfoDTO820) and ( - link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None - ): - link_info_dto_data["_filters_provided"] = True + link_info = link_creation_info.model_dump(exclude_none=True, by_alias=True) - link_info_dto = LinkInfoFactory.create_link_info(study_version, **link_info_dto_data) + link_data: LinkInfoDTOType + if study_version < 820: + link_data = LinkInfoDTOBase(**link_info) + else: + link_data = LinkInfoDTO820(**link_info) + if isinstance(link_data, LinkInfoDTO820) and link_data.filter_synthesis is None: + link_data.filter_synthesis = FilteringOptions.FILTER_SYNTHESIS + if isinstance(link_data, LinkInfoDTO820) and link_data.filter_year_by_year is None: + link_data.filter_year_by_year = FilteringOptions.FILTER_YEAR_BY_YEAR storage_service = self.storage_service.get_storage(study) file_study = storage_service.get_raw(study) @@ -205,13 +115,13 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L command = CreateLink( area1=link_creation_info.area1, area2=link_creation_info.area2, - parameters=LinkInfoFactory.create_parameters(study_version, link_info_dto), + parameters=link_data.model_dump(exclude={"area1", "area2"}, exclude_none=True, by_alias=True), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) execute_or_add_commands(study, file_study, [command], self.storage_service) - return link_info_dto + return link_data def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index e2830bf988..53e7cc5cc1 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -9,10 +9,10 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - +import typing as t from typing import Any, Dict, List, Optional, Tuple, Union, cast -from pydantic import AliasGenerator, BaseModel, ValidationInfo, field_validator, model_validator +from pydantic import AliasGenerator, BaseModel, Field, ValidationInfo, field_validator, model_validator from antarest.core.utils.string import to_kebab_case from antarest.core.utils.utils import assert_this @@ -34,22 +34,59 @@ class AreaInfo(BaseModel): class LinkInfoProperties(BaseModel): - hurdles_cost: Optional[bool] = False - loop_flow: Optional[bool] = False - use_phase_shifter: Optional[bool] = False - transmission_capacities: Optional[TransmissionCapacity] = TransmissionCapacity.ENABLED - asset_type: Optional[AssetType] = AssetType.AC - display_comments: Optional[bool] = True + hurdles_cost: Optional[bool] = Field(False, alias="hurdles-cost") + loop_flow: Optional[bool] = Field(False, alias="loop-flow") + use_phase_shifter: Optional[bool] = Field(False, alias="use-phase-shifter") + transmission_capacities: Optional[TransmissionCapacity] = Field( + TransmissionCapacity.ENABLED, alias="transmission-capacities" + ) + asset_type: Optional[AssetType] = Field(AssetType.AC, alias="asset-type") + display_comments: Optional[bool] = Field(True, alias="display-comments") colorr: Optional[int] = DEFAULT_COLOR colorb: Optional[int] = DEFAULT_COLOR colorg: Optional[int] = DEFAULT_COLOR - link_width: Optional[float] = 1 - link_style: Optional[LinkStyle] = LinkStyle.PLAIN + link_width: Optional[float] = Field(1, alias="link-width") + link_style: Optional[LinkStyle] = Field(LinkStyle.PLAIN, alias="link-style") + + @model_validator(mode="before") + def validate_colors(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + if type(values) is dict: + colors = { + "colorr": values.get("colorr"), + "colorb": values.get("colorb"), + "colorg": values.get("colorg"), + } + for color_name, color_value in colors.items(): + if color_value is not None and (color_value < 0 or color_value > 255): + raise ValueError(f"Invalid value for {color_name}. Must be between 0 and 255.") + + return values class LinkInfoProperties820(LinkInfoProperties): - filter_synthesis: Optional[str] = None - filter_year_by_year: Optional[str] = None + filter_synthesis: Optional[str] = Field(None, alias="filter-synthesis") + filter_year_by_year: Optional[str] = Field(None, alias="filter-year-by-year") + + @model_validator(mode="before") + def validate_filters(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + if type(values) is dict: + filter_options = ["hourly", "daily", "weekly", "monthly", "annual"] + + filters = { + "filter-synthesis": values.get("filter-synthesis"), + "filter-year-by-year": values.get("filter-year-by-year"), + } + for filter_name, val in filters.items(): + if val is not None: + options = val.replace(" ", "").split(",") + invalid_options = [opt for opt in options if opt not in filter_options] + if invalid_options: + raise ValueError( + f"Invalid value(s) in '{filter_name}': {', '.join(invalid_options)}. " + f"Allowed values are: {', '.join(filter_options)}." + ) + + return values class LinkProperties(LinkInfoProperties820, alias_generator=AliasGenerator(serialization_alias=to_kebab_case)): @@ -84,48 +121,6 @@ def validate_series( new_values = values if isinstance(values, dict) else values.data return validate_matrix(v, new_values) if v is not None else v - @model_validator(mode="after") - def validate_colors(self) -> "CreateLink": - parameters = self.parameters or {} - - colors = { - "colorr": parameters.get("colorr"), - "colorb": parameters.get("colorb"), - "colorg": parameters.get("colorg"), - } - for color_name, color_value in colors.items(): - if color_value is not None and (color_value < 0 or color_value > 255): - raise ValueError(f"Invalid value for {color_name}. Must be between 0 and 255.") - - return self - - @model_validator(mode="after") - def validate_filters(self) -> "CreateLink": - parameters = self.parameters or {} - filter_options = ["hourly", "daily", "weekly", "monthly", "annual"] - - filters = { - "filter_synthesis": parameters.get("filter_synthesis"), - "filter_year_by_year": parameters.get("filter_year_by_year"), - } - for filter_name, val in filters.items(): - if val is not None: - options = val.replace(" ", "").split(",") - invalid_options = [opt for opt in options if opt not in filter_options] - if invalid_options: - raise ValueError( - f"Invalid value(s) in '{filter_name}': {', '.join(invalid_options)}. " - f"Allowed values are: {', '.join(filter_options)}." - ) - - return self - - @model_validator(mode="after") - def validate_areas(self) -> "CreateLink": - if self.area1 == self.area2: - raise ValueError("Cannot create link on same node") - return self - def _create_link_in_config(self, area_from: str, area_to: str, study_data: FileStudyTreeConfig) -> None: self.parameters = self.parameters or {} study_data.areas[area_from].links[area_to] = Link( diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 2351648241..d962520190 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -598,19 +598,19 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: { "area1": "area 1", "area2": "area 2", - "asset_type": "ac", + "asset-type": "ac", "colorb": 112, "colorg": 112, "colorr": 112, - "display_comments": True, - "filter_synthesis": "hourly, daily, weekly, monthly, annual", - "filter_year_by_year": "hourly, daily, weekly, monthly, annual", - "hurdles_cost": False, - "link_style": "plain", - "link_width": 1.0, - "loop_flow": False, - "transmission_capacities": "enabled", - "use_phase_shifter": False, + "display-comments": True, + "filter-synthesis": "hourly, daily, weekly, monthly, annual", + "filter-year-by-year": "hourly, daily, weekly, monthly, annual", + "hurdles-cost": False, + "link-style": "plain", + "link-width": 1.0, + "loop-flow": False, + "transmission-capacities": "enabled", + "use-phase-shifter": False, } ] diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 5138f42649..7de71fdfca 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -24,7 +24,7 @@ from antarest.matrixstore.repository import MatrixContentRepository from antarest.matrixstore.service import SimpleMatrixService from antarest.study.business.area_management import AreaCreationDTO, AreaManager, AreaType, UpdateAreaUi -from antarest.study.business.link_management import LinkInfoDTO820, LinkInfoDTOBase, LinkInfoFactory, LinkManager +from antarest.study.business.link_management import LinkInfoDTO820, LinkInfoDTOBase, LinkManager from antarest.study.model import Patch, PatchArea, PatchCluster, RawStudy, StudyAdditionalData from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService @@ -138,7 +138,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) link_manager.create_link( study, - LinkInfoFactory.create_link_info( + LinkInfoDTO820( version=-1, area1="test", area2="test2", @@ -245,19 +245,19 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "area1": "test", "area2": "test2", "parameters": { - "hurdles_cost": False, - "loop_flow": False, - "use_phase_shifter": False, - "transmission_capacities": TransmissionCapacity.ENABLED, - "asset_type": AssetType.AC, - "display_comments": True, + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": True, "colorr": 112, "colorg": 112, "colorb": 112, - "link_width": 1.0, - "link_style": LinkStyle.PLAIN, - "filter_synthesis": "hourly, daily, weekly, monthly, annual", - "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "link-width": 1.0, + "link-style": LinkStyle.PLAIN, + "filter-synthesis": "hourly, daily, weekly, monthly, annual", + "filter-year-by-year": "hourly, daily, weekly, monthly, annual", }, }, ), @@ -281,17 +281,17 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "area1": "test", "area2": "test2", "parameters": { - "hurdles_cost": False, - "loop_flow": False, - "use_phase_shifter": False, - "transmission_capacities": TransmissionCapacity.ENABLED, - "asset_type": AssetType.AC, - "display_comments": True, + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": True, "colorr": 112, "colorg": 112, "colorb": 112, - "link_width": 1.0, - "link_style": LinkStyle.PLAIN, + "link-width": 1.0, + "link-style": LinkStyle.PLAIN, }, }, ), @@ -486,34 +486,34 @@ def test_get_all_area(): file_tree_mock.get.side_effect = [ { "a2": { - "hurdles-cost": True, - "loop-flow": True, + "hurdles-cost": False, + "loop-flow": False, "use-phase-shifter": False, "transmission-capacities": TransmissionCapacity.ENABLED, - "asset-type": AssetType.DC, + "asset-type": AssetType.AC, "display-comments": False, "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, }, "a3": { - "hurdles-cost": True, + "hurdles-cost": False, "loop-flow": False, - "use-phase-shifter": True, + "use-phase-shifter": False, "transmission-capacities": TransmissionCapacity.ENABLED, "asset-type": AssetType.AC, - "display-comments": True, + "display-comments": False, "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, }, }, { "a3": { - "hurdles-cost": True, + "hurdles-cost": False, "loop-flow": False, - "use-phase-shifter": True, + "use-phase-shifter": False, "transmission-capacities": TransmissionCapacity.ENABLED, "asset-type": AssetType.AC, - "display-comments": True, + "display-comments": False, "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, } @@ -522,10 +522,10 @@ def test_get_all_area(): "a3": { "hurdles-cost": False, "loop-flow": False, - "use-phase-shifter": True, + "use-phase-shifter": False, "transmission-capacities": TransmissionCapacity.ENABLED, "asset-type": AssetType.AC, - "display-comments": True, + "display-comments": False, "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, } @@ -540,7 +540,7 @@ def test_get_all_area(): "colorb": 112, "colorg": 112, "colorr": 112, - "display_comments": True, + "display_comments": False, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", "hurdles_cost": False, @@ -557,7 +557,7 @@ def test_get_all_area(): "colorb": 112, "colorg": 112, "colorr": 112, - "display_comments": True, + "display_comments": False, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", "hurdles_cost": False, @@ -574,7 +574,7 @@ def test_get_all_area(): "colorb": 112, "colorg": 112, "colorr": 112, - "display_comments": True, + "display_comments": False, "filter_synthesis": "hourly, daily, weekly, monthly, annual", "filter_year_by_year": "hourly, daily, weekly, monthly, annual", "hurdles_cost": False, diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index ad97bc6ddf..7c88fbe97a 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -31,8 +31,6 @@ class TestCreateLink: def test_validation(self, empty_study: FileStudy, command_context: CommandContext): area1 = "Area1" - area1_id = transform_name_to_id(area1) - area2 = "Area2" CreateArea.model_validate( @@ -49,15 +47,6 @@ def test_validation(self, empty_study: FileStudy, command_context: CommandContex } ).apply(empty_study) - with pytest.raises(ValidationError): - CreateLink( - area1=area1_id, - area2=area1_id, - parameters={}, - command_context=command_context, - series=[[0]], - ) - def test_apply(self, empty_study: FileStudy, command_context: CommandContext): study_path = empty_study.config.study_path area1 = "Area1" @@ -152,19 +141,19 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): assert not output.status parameters = { - "hurdles_cost": True, - "loop_flow": True, - "use_phase_shifter": True, - "transmission_capacities": "ignore", - "asset_type": "dc", - "link_style": "other", - "link_width": 12, + "hurdles-cost": True, + "loop-flow": True, + "use-phase-shifter": True, + "transmission-capacities": "ignore", + "asset-type": "dc", + "link-style": "other", + "link-width": 12, "colorr": 120, "colorg": 120, "colorb": 120, - "display_comments": True, - "filter_synthesis": "hourly", - "filter_year_by_year": "hourly", + "display-comments": True, + "filter-synthesis": "hourly", + "filter-year-by-year": "hourly", } create_link_command: ICommand = CreateLink.model_validate( @@ -186,17 +175,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): link = IniReader() link_data = link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert link_data[area3_id]["hurdles-cost"] == parameters["hurdles_cost"] - assert link_data[area3_id]["loop-flow"] == parameters["loop_flow"] - assert link_data[area3_id]["use-phase-shifter"] == parameters["use_phase_shifter"] - assert link_data[area3_id]["transmission-capacities"] == parameters["transmission_capacities"] - assert link_data[area3_id]["asset-type"] == parameters["asset_type"] - assert link_data[area3_id]["link-style"] == parameters["link_style"] - assert int(link_data[area3_id]["link-width"]) == parameters["link_width"] + assert link_data[area3_id]["hurdles-cost"] == parameters["hurdles-cost"] + assert link_data[area3_id]["loop-flow"] == parameters["loop-flow"] + assert link_data[area3_id]["use-phase-shifter"] == parameters["use-phase-shifter"] + assert link_data[area3_id]["transmission-capacities"] == parameters["transmission-capacities"] + assert link_data[area3_id]["asset-type"] == parameters["asset-type"] + assert link_data[area3_id]["link-style"] == parameters["link-style"] + assert int(link_data[area3_id]["link-width"]) == parameters["link-width"] assert int(link_data[area3_id]["colorr"]) == parameters["colorr"] assert int(link_data[area3_id]["colorg"]) == parameters["colorg"] assert int(link_data[area3_id]["colorb"]) == parameters["colorb"] - assert link_data[area3_id]["display-comments"] == parameters["display_comments"] + assert link_data[area3_id]["display-comments"] == parameters["display-comments"] output = create_link_command.apply( study_data=empty_study, From 0c002d9195c621f9be15053e8d31854123b6650b Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 25 Sep 2024 14:45:28 +0200 Subject: [PATCH 026/103] add integration tests --- .../variantstudy/model/command/create_link.py | 30 +-- .../study_data_blueprint/test_link.py | 210 ++++++++++++++++++ 2 files changed, 221 insertions(+), 19 deletions(-) create mode 100644 tests/integration/study_data_blueprint/test_link.py diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 53e7cc5cc1..9a894e6266 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -67,26 +67,18 @@ class LinkInfoProperties820(LinkInfoProperties): filter_synthesis: Optional[str] = Field(None, alias="filter-synthesis") filter_year_by_year: Optional[str] = Field(None, alias="filter-year-by-year") - @model_validator(mode="before") - def validate_filters(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: - if type(values) is dict: + @field_validator('filter_synthesis', 'filter_year_by_year', mode="before") + def validate_individual_filters(cls, value: Optional[str], field) -> Optional[str]: + if value is not None: filter_options = ["hourly", "daily", "weekly", "monthly", "annual"] - - filters = { - "filter-synthesis": values.get("filter-synthesis"), - "filter-year-by-year": values.get("filter-year-by-year"), - } - for filter_name, val in filters.items(): - if val is not None: - options = val.replace(" ", "").split(",") - invalid_options = [opt for opt in options if opt not in filter_options] - if invalid_options: - raise ValueError( - f"Invalid value(s) in '{filter_name}': {', '.join(invalid_options)}. " - f"Allowed values are: {', '.join(filter_options)}." - ) - - return values + options = value.replace(" ", "").split(",") + invalid_options = [opt for opt in options if opt not in filter_options] + if invalid_options: + raise ValueError( + f"Invalid value(s) in '{field.alias}': {', '.join(invalid_options)}. " + f"Allowed values are: {', '.join(filter_options)}." + ) + return value class LinkProperties(LinkInfoProperties820, alias_generator=AliasGenerator(serialization_alias=to_kebab_case)): diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py new file mode 100644 index 0000000000..01037f04c3 --- /dev/null +++ b/tests/integration/study_data_blueprint/test_link.py @@ -0,0 +1,210 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import pytest +from starlette.testclient import TestClient + +from tests.integration.prepare_proxy import PreparerProxy + +# check error filter and version > 820 + +@pytest.mark.unit_test +class TestLink: + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_create_link_with_default_value( + self, client: TestClient, user_access_token: str, study_type: str + ) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=880) + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) + + assert res.status_code == 200, res.json() + + expected = { + "area1": "area 1", + "area2": "area 2", + "asset-type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "display-comments": True, + "filter-synthesis": "hourly, daily, weekly, monthly, annual", + "filter-year-by-year": "hourly, daily, weekly, monthly, annual", + "hurdles-cost": False, + "link-style": "plain", + "link-width": 1.0, + "loop-flow": False, + "transmission-capacities": "enabled", + "use-phase-shifter": False, + } + assert expected == res.json() + + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_create_link_with_parameters(self, client: TestClient, user_access_token: str, study_type: str) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=880) + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + + res = client.post( + f"/v1/studies/{study_id}/links", + json={ + "area1": area1_id, + "area2": area2_id, + "asset-type": "dc", + "colorb": 160, + "colorg": 170, + "colorr": 180, + "display-comments": True, + "filter-synthesis": "hourly", + "hurdles-cost": True, + "link-style": "plain", + "link-width": 2.0, + "loop-flow": False, + "transmission-capacities": "enabled", + "use-phase-shifter": True, + }, + ) + + assert res.status_code == 200, res.json() + + expected = { + "area1": "area 1", + "area2": "area 2", + "asset-type": "dc", + "colorb": 160, + "colorg": 170, + "colorr": 180, + "display-comments": True, + "filter-synthesis": "hourly", + "filter-year-by-year": "hourly, daily, weekly, monthly, annual", + "hurdles-cost": True, + "link-style": "plain", + "link-width": 2.0, + "loop-flow": False, + "transmission-capacities": "enabled", + "use-phase-shifter": True, + } + assert expected == res.json() + + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_create_two_links_and_count_then_delete_one( + self, client: TestClient, user_access_token: str, study_type: str + ) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=880) + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + area3_id = preparer.create_area(study_id, name="Area 3")["id"] + + res1 = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) + res2 = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area3_id}) + + assert res1.status_code == 200, res1.json() + assert res2.status_code == 200, res2.json() + + res3 = client.get(f"/v1/studies/{study_id}/links") + + assert res3.status_code == 200, res3.json() + assert 2 == len(res3.json()) + + res4 = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area3_id}") + assert res4.status_code == 200, res4.json() + + res5 = client.get(f"/v1/studies/{study_id}/links") + + assert res5.status_code == 200, res5.json() + assert 1 == len(res5.json()) + + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_create_link_with_same_area( + self, client: TestClient, user_access_token: str, study_type: str + ) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=880) + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area1_id}) + + assert res.status_code == 500, res.json() + + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_create_already_existing_link( + self, client: TestClient, user_access_token: str, study_type: str + ) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=880) + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + + client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) + + assert res.status_code == 500, res.json() + + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_create_link_wrong_parameter_color( + self, client: TestClient, user_access_token: str, study_type: str + ) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=880) + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "colorr": 260}) + + assert res.status_code == 422, res.json() + + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_create_link_wrong_parameter_filter( + self, client: TestClient, user_access_token: str, study_type: str + ) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=880) + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filter-synthesis": "centurial"}) + + assert res.status_code == 500, res.json() + + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_create_link_with_wrong_parameter_for_version_810( + self, client: TestClient, user_access_token: str, study_type: str + ) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=810) + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filter-synthesis": "hourly"}) + + assert res.status_code == 500, res.json() \ No newline at end of file From c7728db5df53a89e6df84e62b9de3f3782fe30d2 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 25 Sep 2024 14:47:51 +0200 Subject: [PATCH 027/103] add integration tests --- .../variantstudy/model/command/create_link.py | 2 +- .../study_data_blueprint/test_link.py | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 9a894e6266..77105ecf72 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -67,7 +67,7 @@ class LinkInfoProperties820(LinkInfoProperties): filter_synthesis: Optional[str] = Field(None, alias="filter-synthesis") filter_year_by_year: Optional[str] = Field(None, alias="filter-year-by-year") - @field_validator('filter_synthesis', 'filter_year_by_year', mode="before") + @field_validator("filter_synthesis", "filter_year_by_year", mode="before") def validate_individual_filters(cls, value: Optional[str], field) -> Optional[str]: if value is not None: filter_options = ["hourly", "daily", "weekly", "monthly", "annual"] diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 01037f04c3..3fdb9bbb98 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -17,12 +17,11 @@ # check error filter and version > 820 + @pytest.mark.unit_test class TestLink: @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_link_with_default_value( - self, client: TestClient, user_access_token: str, study_type: str - ) -> None: + def test_create_link_with_default_value(self, client: TestClient, user_access_token: str, study_type: str) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore preparer = PreparerProxy(client, user_access_token) @@ -105,7 +104,7 @@ def test_create_link_with_parameters(self, client: TestClient, user_access_token @pytest.mark.parametrize("study_type", ["raw", "variant"]) def test_create_two_links_and_count_then_delete_one( - self, client: TestClient, user_access_token: str, study_type: str + self, client: TestClient, user_access_token: str, study_type: str ) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore @@ -135,9 +134,7 @@ def test_create_two_links_and_count_then_delete_one( assert 1 == len(res5.json()) @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_link_with_same_area( - self, client: TestClient, user_access_token: str, study_type: str - ) -> None: + def test_create_link_with_same_area(self, client: TestClient, user_access_token: str, study_type: str) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore preparer = PreparerProxy(client, user_access_token) @@ -149,9 +146,7 @@ def test_create_link_with_same_area( assert res.status_code == 500, res.json() @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_already_existing_link( - self, client: TestClient, user_access_token: str, study_type: str - ) -> None: + def test_create_already_existing_link(self, client: TestClient, user_access_token: str, study_type: str) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore preparer = PreparerProxy(client, user_access_token) @@ -166,7 +161,7 @@ def test_create_already_existing_link( @pytest.mark.parametrize("study_type", ["raw", "variant"]) def test_create_link_wrong_parameter_color( - self, client: TestClient, user_access_token: str, study_type: str + self, client: TestClient, user_access_token: str, study_type: str ) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore @@ -181,7 +176,7 @@ def test_create_link_wrong_parameter_color( @pytest.mark.parametrize("study_type", ["raw", "variant"]) def test_create_link_wrong_parameter_filter( - self, client: TestClient, user_access_token: str, study_type: str + self, client: TestClient, user_access_token: str, study_type: str ) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore @@ -190,13 +185,16 @@ def test_create_link_wrong_parameter_filter( area1_id = preparer.create_area(study_id, name="Area 1")["id"] area2_id = preparer.create_area(study_id, name="Area 2")["id"] - res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filter-synthesis": "centurial"}) + res = client.post( + f"/v1/studies/{study_id}/links", + json={"area1": area1_id, "area2": area2_id, "filter-synthesis": "centurial"}, + ) assert res.status_code == 500, res.json() @pytest.mark.parametrize("study_type", ["raw", "variant"]) def test_create_link_with_wrong_parameter_for_version_810( - self, client: TestClient, user_access_token: str, study_type: str + self, client: TestClient, user_access_token: str, study_type: str ) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore @@ -205,6 +203,8 @@ def test_create_link_with_wrong_parameter_for_version_810( area1_id = preparer.create_area(study_id, name="Area 1")["id"] area2_id = preparer.create_area(study_id, name="Area 2")["id"] - res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filter-synthesis": "hourly"}) + res = client.post( + f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filter-synthesis": "hourly"} + ) - assert res.status_code == 500, res.json() \ No newline at end of file + assert res.status_code == 500, res.json() From 779dc39245d62333ba7db2fa44d1717199739282 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 25 Sep 2024 14:51:17 +0200 Subject: [PATCH 028/103] add integration tests --- .../study/storage/variantstudy/model/command/create_link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 77105ecf72..5bf0a0e0fe 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -68,7 +68,7 @@ class LinkInfoProperties820(LinkInfoProperties): filter_year_by_year: Optional[str] = Field(None, alias="filter-year-by-year") @field_validator("filter_synthesis", "filter_year_by_year", mode="before") - def validate_individual_filters(cls, value: Optional[str], field) -> Optional[str]: + def validate_individual_filters(cls, value: Optional[str], field: Any) -> Optional[str]: if value is not None: filter_options = ["hourly", "daily", "weekly", "monthly", "annual"] options = value.replace(" ", "").split(",") From 3853e45b1bc1b8304b2e9bc9e76f70da2bd1e090 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 26 Sep 2024 14:46:24 +0200 Subject: [PATCH 029/103] add integration tests --- antarest/core/exceptions.py | 5 + antarest/study/business/link_management.py | 8 +- .../variantstudy/model/command/create_link.py | 16 +-- .../study_data_blueprint/test_link.py | 117 ++++++------------ .../storage/business/test_arealink_manager.py | 3 - 5 files changed, 54 insertions(+), 95 deletions(-) diff --git a/antarest/core/exceptions.py b/antarest/core/exceptions.py index dddcda014c..fcbd3de3a1 100644 --- a/antarest/core/exceptions.py +++ b/antarest/core/exceptions.py @@ -295,6 +295,11 @@ def __init__(self, message: str) -> None: super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) +class LinkValidationError(HTTPException): + def __init__(self, message: str) -> None: + super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) + + class VariantStudyParentNotValid(HTTPException): def __init__(self, message: str) -> None: super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 0298f269c1..7507883862 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,7 +12,9 @@ import typing as t -from antarest.core.exceptions import ConfigFileNotFound +from antares.study.version.model.exceptions import ValidationError + +from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands @@ -90,12 +92,12 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: if link_creation_info.area1 == link_creation_info.area2: - raise ValueError("Cannot create link on same node") + raise LinkValidationError("Cannot create link on same node") study_version = int(study.version) if study_version < 820 and isinstance(link_creation_info, LinkInfoDTO820): if link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None: - raise ValueError("Cannot specify a filter value for study's version earlier than v8.2") + raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") link_info = link_creation_info.model_dump(exclude_none=True, by_alias=True) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 5bf0a0e0fe..36ef70ab9b 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -14,6 +14,7 @@ from pydantic import AliasGenerator, BaseModel, Field, ValidationInfo, field_validator, model_validator +from antarest.core.exceptions import LinkValidationError from antarest.core.utils.string import to_kebab_case from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData @@ -58,7 +59,7 @@ def validate_colors(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: } for color_name, color_value in colors.items(): if color_value is not None and (color_value < 0 or color_value > 255): - raise ValueError(f"Invalid value for {color_name}. Must be between 0 and 255.") + raise LinkValidationError(f"Invalid value for {color_name}. Must be between 0 and 255.") return values @@ -74,8 +75,8 @@ def validate_individual_filters(cls, value: Optional[str], field: Any) -> Option options = value.replace(" ", "").split(",") invalid_options = [opt for opt in options if opt not in filter_options] if invalid_options: - raise ValueError( - f"Invalid value(s) in '{field.alias}': {', '.join(invalid_options)}. " + raise LinkValidationError( + f"Invalid value(s) in filters: {', '.join(invalid_options)}. " f"Allowed values are: {', '.join(filter_options)}." ) return value @@ -150,15 +151,6 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> Tuple[CommandOutput, {}, ) - if self.area1 == self.area2: - return ( - CommandOutput( - status=False, - message="Cannot create link between the same node", - ), - {}, - ) - # Link parameters between two areas are stored in only one of the two # areas in the "input/links" tree. One area acts as source (`area_from`) # and the other as target (`area_to`). diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 3fdb9bbb98..36aeb56280 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -15,20 +15,20 @@ from tests.integration.prepare_proxy import PreparerProxy -# check error filter and version > 820 - @pytest.mark.unit_test class TestLink: @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_link_with_default_value(self, client: TestClient, user_access_token: str, study_type: str) -> None: + def test_link_820(self, client: TestClient, user_access_token: str, study_type: str) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore preparer = PreparerProxy(client, user_access_token) study_id = preparer.create_study("foo", version=880) area1_id = preparer.create_area(study_id, name="Area 1")["id"] area2_id = preparer.create_area(study_id, name="Area 2")["id"] + area3_id = preparer.create_area(study_id, name="Area 3")["id"] + # Test create link with default values res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) assert res.status_code == 200, res.json() @@ -51,15 +51,9 @@ def test_create_link_with_default_value(self, client: TestClient, user_access_to "use-phase-shifter": False, } assert expected == res.json() + client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") - @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_link_with_parameters(self, client: TestClient, user_access_token: str, study_type: str) -> None: - client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore - - preparer = PreparerProxy(client, user_access_token) - study_id = preparer.create_study("foo", version=880) - area1_id = preparer.create_area(study_id, name="Area 1")["id"] - area2_id = preparer.create_area(study_id, name="Area 2")["id"] + # Test create link with parameters res = client.post( f"/v1/studies/{study_id}/links", @@ -101,18 +95,9 @@ def test_create_link_with_parameters(self, client: TestClient, user_access_token "use-phase-shifter": True, } assert expected == res.json() + res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") - @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_two_links_and_count_then_delete_one( - self, client: TestClient, user_access_token: str, study_type: str - ) -> None: - client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore - - preparer = PreparerProxy(client, user_access_token) - study_id = preparer.create_study("foo", version=880) - area1_id = preparer.create_area(study_id, name="Area 1")["id"] - area2_id = preparer.create_area(study_id, name="Area 2")["id"] - area3_id = preparer.create_area(study_id, name="Area 3")["id"] + # Create two links, count them, then delete one res1 = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) res2 = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area3_id}) @@ -120,82 +105,55 @@ def test_create_two_links_and_count_then_delete_one( assert res1.status_code == 200, res1.json() assert res2.status_code == 200, res2.json() - res3 = client.get(f"/v1/studies/{study_id}/links") - - assert res3.status_code == 200, res3.json() - assert 2 == len(res3.json()) + res = client.get(f"/v1/studies/{study_id}/links") - res4 = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area3_id}") - assert res4.status_code == 200, res4.json() + assert res.status_code == 200, res.json() + assert 2 == len(res.json()) - res5 = client.get(f"/v1/studies/{study_id}/links") + res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area3_id}") + assert res.status_code == 200, res.json() - assert res5.status_code == 200, res5.json() - assert 1 == len(res5.json()) + res = client.get(f"/v1/studies/{study_id}/links") - @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_link_with_same_area(self, client: TestClient, user_access_token: str, study_type: str) -> None: - client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + assert res.status_code == 200, res.json() + assert 1 == len(res.json()) + client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") - preparer = PreparerProxy(client, user_access_token) - study_id = preparer.create_study("foo", version=880) - area1_id = preparer.create_area(study_id, name="Area 1")["id"] + # Test create link with same area res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area1_id}) - assert res.status_code == 500, res.json() - - @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_already_existing_link(self, client: TestClient, user_access_token: str, study_type: str) -> None: - client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore - - preparer = PreparerProxy(client, user_access_token) - study_id = preparer.create_study("foo", version=880) - area1_id = preparer.create_area(study_id, name="Area 1")["id"] - area2_id = preparer.create_area(study_id, name="Area 2")["id"] - - client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) - res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) - - assert res.status_code == 500, res.json() - - @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_link_wrong_parameter_color( - self, client: TestClient, user_access_token: str, study_type: str - ) -> None: - client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + assert res.status_code == 422, res.json() + expected = {"description": "Cannot create link on same node", "exception": "LinkValidationError"} + assert expected == res.json() - preparer = PreparerProxy(client, user_access_token) - study_id = preparer.create_study("foo", version=880) - area1_id = preparer.create_area(study_id, name="Area 1")["id"] - area2_id = preparer.create_area(study_id, name="Area 2")["id"] + # Test create link with wrong color parameter res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "colorr": 260}) assert res.status_code == 422, res.json() + expected = { + "description": "Invalid value for colorr. Must be between 0 and 255.", + "exception": "LinkValidationError", + } + assert expected == res.json() - @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_link_wrong_parameter_filter( - self, client: TestClient, user_access_token: str, study_type: str - ) -> None: - client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore - - preparer = PreparerProxy(client, user_access_token) - study_id = preparer.create_study("foo", version=880) - area1_id = preparer.create_area(study_id, name="Area 1")["id"] - area2_id = preparer.create_area(study_id, name="Area 2")["id"] + # Test create link with wrong filter parameter res = client.post( f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filter-synthesis": "centurial"}, ) - assert res.status_code == 500, res.json() + assert res.status_code == 422, res.json() + expected = { + "description": "Invalid value(s) in filters: centurial. Allowed values are: hourly, daily, weekly, monthly, annual.", + "exception": "LinkValidationError", + } + assert expected == res.json() @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_link_with_wrong_parameter_for_version_810( - self, client: TestClient, user_access_token: str, study_type: str - ) -> None: + def test_create_link_810(self, client: TestClient, user_access_token: str, study_type: str) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore preparer = PreparerProxy(client, user_access_token) @@ -207,4 +165,9 @@ def test_create_link_with_wrong_parameter_for_version_810( f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filter-synthesis": "hourly"} ) - assert res.status_code == 500, res.json() + assert res.status_code == 422, res.json() + expected = { + "description": "Cannot specify a filter value for study's version earlier than v8.2", + "exception": "LinkValidationError", + } + assert expected == res.json() diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 7de71fdfca..945d09db12 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -139,7 +139,6 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): link_manager.create_link( study, LinkInfoDTO820( - version=-1, area1="test", area2="test2", ), @@ -232,8 +231,6 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): LinkInfoDTO820( area1="test", area2="test2", - filter_synthesis=FilteringOptions.FILTER_SYNTHESIS, - filter_year_by_year=FilteringOptions.FILTER_YEAR_BY_YEAR, ), ) variant_study_service.append_commands.assert_called_with( From 647dfbd0e9f224a212d36f866ab50f2dfa35763a Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Fri, 27 Sep 2024 10:09:59 +0200 Subject: [PATCH 030/103] add integration tests --- antarest/study/business/link_management.py | 1 - 1 file changed, 1 deletion(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 7507883862..88c6a62fb9 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,7 +12,6 @@ import typing as t -from antares.study.version.model.exceptions import ValidationError from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError from antarest.core.model import JSON From 39f81f688fb0532df0a86afc8c1e59128445b3f9 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Fri, 27 Sep 2024 16:41:39 +0200 Subject: [PATCH 031/103] WIP : add update link endpoint --- antarest/study/business/link_management.py | 27 ++++++++++++++++++++++ antarest/study/service.py | 19 +++++++++++++++ antarest/study/web/study_data_blueprint.py | 18 +++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 88c6a62fb9..374254eb78 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -124,6 +124,33 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L return link_data + def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: + file_study = self.storage_service.get_storage(study).get_raw(study) + existing_link = self.get_one_link(study, link_creation_info.area1, link_creation_info.area2) + + + + execute_or_add_commands(study, file_study, [command], self.storage_service) + + return existing_link + + def get_one_link(self, study: RawStudy, area1: str, area2: str) -> LinkInfoDTOType: + file_study = self.storage_service.get_storage(study).get_raw(study) + + area_from, area_to = sorted([area1, area2]) + try: + link_config = file_study.tree.get(["input", "links", area_from, "properties", area_to]) + except KeyError: + raise LinkValidationError(f"The link {area_from} -> {area_to} is not present in the study") + + link_config["area1"] = area_from + link_config["area2"] = area_to + + link_data: LinkInfoDTOType + if int(study.version) < 820: + return LinkInfoDTOBase(**link_config) + return LinkInfoDTO820(**link_config) + def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) command = RemoveLink( diff --git a/antarest/study/service.py b/antarest/study/service.py index 62af9d98aa..4539152dc0 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -1856,6 +1856,25 @@ def create_link( ) return new_link + def update_link( + self, + uuid: str, + link_update_dto: LinkInfoDTOType, + params: RequestParameters, + ) -> LinkInfoDTOType: + study = self.get_study(uuid) + assert_permission(params.user, study, StudyPermissionType.WRITE) + self._assert_study_unarchived(study) + updated_link = self.links.update_link(study, link_update_dto) + self.event_bus.push( + Event( + type=EventType.STUDY_DATA_EDITED, + payload=study.to_json_summary(), + permissions=PermissionInfo.from_study(study), + ) + ) + return updated_link + def update_area( self, uuid: str, diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 33cc4e2746..8cecd76be1 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -198,6 +198,24 @@ def create_link( params = RequestParameters(user=current_user) return study_service.create_link(uuid, link_creation_info, params) + @bp.put( + "/studies/{uuid}/links", + tags=[APITag.study_data], + summary="Update a link", + response_model=LinkInfoDTOType, + ) + def update_link( + uuid: str, + link_creation_info: LinkInfoDTOType, + current_user: JWTUser = Depends(auth.get_current_user), + ) -> t.Any: + logger.info( + f"Updating link for study {uuid}", + extra={"user": current_user.id}, + ) + params = RequestParameters(user=current_user) + return study_service.update_link(uuid, link_creation_info, params) + @bp.put( "/studies/{uuid}/areas/{area_id}/ui", tags=[APITag.study_data], From 1355def9f90797b1173065c131e36aa921a05d82 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 30 Sep 2024 10:15:35 +0200 Subject: [PATCH 032/103] fix: fix tests --- antarest/study/business/link_management.py | 14 +--- antarest/study/service.py | 3 +- .../variantstudy/model/command/create_link.py | 70 +++++++++-------- antarest/study/web/study_data_blueprint.py | 3 +- .../study_data_blueprint/test_link.py | 76 ++++++++++--------- tests/integration/test_integration.py | 2 +- .../storage/business/test_arealink_manager.py | 4 +- .../model/command/test_create_link.py | 9 +++ 8 files changed, 92 insertions(+), 89 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 88c6a62fb9..0428056a6c 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -56,7 +56,7 @@ class LinkManager: def __init__(self, storage_service: StudyStorageService) -> None: self.storage_service = storage_service - def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkInfoDTOType]: + def get_all_links(self, study: RawStudy) -> t.List[LinkInfoDTOType]: file_study = self.storage_service.get_storage(study).get_raw(study) result: t.List[LinkInfoDTOType] = [] @@ -68,16 +68,6 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn link_creation_data: t.Dict[str, t.Any] = {"area1": area_id, "area2": link} link_creation_data.update(link_properties) - if not with_ui: - link_creation_data.update( - { - "colorr": None, - "colorb": None, - "colorg": None, - "link-width": None, - "link-style": None, - } - ) link_data: LinkInfoDTOType if int(study.version) < 820: @@ -91,7 +81,7 @@ def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkIn def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: if link_creation_info.area1 == link_creation_info.area2: - raise LinkValidationError("Cannot create link on same node") + raise LinkValidationError(f"Cannot create link on same area: {link_creation_info.area1}") study_version = int(study.version) if study_version < 820 and isinstance(link_creation_info, LinkInfoDTO820): diff --git a/antarest/study/service.py b/antarest/study/service.py index 62af9d98aa..f8ff80c50f 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -1811,12 +1811,11 @@ def get_all_areas( def get_all_links( self, uuid: str, - with_ui: bool, params: RequestParameters, ) -> t.List[LinkInfoDTOType]: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.READ) - return self.links.get_all_links(study, with_ui) + return self.links.get_all_links(study) def create_area( self, diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 36ef70ab9b..a5df870251 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -9,10 +9,9 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. -import typing as t from typing import Any, Dict, List, Optional, Tuple, Union, cast -from pydantic import AliasGenerator, BaseModel, Field, ValidationInfo, field_validator, model_validator +from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator from antarest.core.exceptions import LinkValidationError from antarest.core.utils.string import to_kebab_case @@ -35,54 +34,47 @@ class AreaInfo(BaseModel): class LinkInfoProperties(BaseModel): - hurdles_cost: Optional[bool] = Field(False, alias="hurdles-cost") - loop_flow: Optional[bool] = Field(False, alias="loop-flow") - use_phase_shifter: Optional[bool] = Field(False, alias="use-phase-shifter") - transmission_capacities: Optional[TransmissionCapacity] = Field( - TransmissionCapacity.ENABLED, alias="transmission-capacities" - ) - asset_type: Optional[AssetType] = Field(AssetType.AC, alias="asset-type") - display_comments: Optional[bool] = Field(True, alias="display-comments") - colorr: Optional[int] = DEFAULT_COLOR - colorb: Optional[int] = DEFAULT_COLOR - colorg: Optional[int] = DEFAULT_COLOR - link_width: Optional[float] = Field(1, alias="link-width") - link_style: Optional[LinkStyle] = Field(LinkStyle.PLAIN, alias="link-style") - - @model_validator(mode="before") - def validate_colors(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: - if type(values) is dict: - colors = { - "colorr": values.get("colorr"), - "colorb": values.get("colorb"), - "colorg": values.get("colorg"), - } - for color_name, color_value in colors.items(): - if color_value is not None and (color_value < 0 or color_value > 255): - raise LinkValidationError(f"Invalid value for {color_name}. Must be between 0 and 255.") - - return values + hurdles_cost: Optional[bool] = False + loop_flow: Optional[bool] = False + use_phase_shifter: Optional[bool] = False + transmission_capacities: Optional[TransmissionCapacity] = TransmissionCapacity.ENABLED + asset_type: Optional[AssetType] = AssetType.AC + display_comments: Optional[bool] = True + colorr: Optional[int] = Field(default=DEFAULT_COLOR, gt=0, lt=255) + colorb: Optional[int] = Field(default=DEFAULT_COLOR, gt=0, lt=255) + colorg: Optional[int] = Field(default=DEFAULT_COLOR, gt=0, lt=255) + link_width: Optional[float] = 1 + link_style: Optional[LinkStyle] = LinkStyle.PLAIN + + class Config: + alias_generator = to_kebab_case + allow_population_by_field_name = True class LinkInfoProperties820(LinkInfoProperties): - filter_synthesis: Optional[str] = Field(None, alias="filter-synthesis") - filter_year_by_year: Optional[str] = Field(None, alias="filter-year-by-year") + filter_synthesis: Optional[str] = None + filter_year_by_year: Optional[str] = None + + class Config: + alias_generator = to_kebab_case + allow_population_by_field_name = True @field_validator("filter_synthesis", "filter_year_by_year", mode="before") - def validate_individual_filters(cls, value: Optional[str], field: Any) -> Optional[str]: + def validate_individual_filters(cls, value: Optional[str]) -> Optional[str]: if value is not None: - filter_options = ["hourly", "daily", "weekly", "monthly", "annual"] + from antarest.study.business.areas.properties_management import DEFAULT_FILTER_VALUE + options = value.replace(" ", "").split(",") - invalid_options = [opt for opt in options if opt not in filter_options] + invalid_options = [opt for opt in options if opt not in DEFAULT_FILTER_VALUE] if invalid_options: raise LinkValidationError( f"Invalid value(s) in filters: {', '.join(invalid_options)}. " - f"Allowed values are: {', '.join(filter_options)}." + f"Allowed values are: {', '.join(DEFAULT_FILTER_VALUE)}." ) return value -class LinkProperties(LinkInfoProperties820, alias_generator=AliasGenerator(serialization_alias=to_kebab_case)): +class LinkProperties(LinkInfoProperties820): pass @@ -114,6 +106,12 @@ def validate_series( new_values = values if isinstance(values, dict) else values.data return validate_matrix(v, new_values) if v is not None else v + @model_validator(mode="after") + def validate_areas(self) -> "CreateLink": + if self.area1 == self.area2: + raise ValueError("Cannot create link on same node") + return self + def _create_link_in_config(self, area_from: str, area_to: str, study_data: FileStudyTreeConfig) -> None: self.parameters = self.parameters or {} study_data.areas[area_from].links[area_to] = Link( diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 33cc4e2746..86f6a6999e 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -151,7 +151,6 @@ def get_areas( ) def get_links( uuid: str, - with_ui: bool = False, current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Any: logger.info( @@ -159,7 +158,7 @@ def get_links( extra={"user": current_user.id}, ) params = RequestParameters(user=current_user) - areas_list = study_service.get_all_links(uuid, with_ui, params) + areas_list = study_service.get_all_links(uuid, params) return areas_list @bp.post( diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 36aeb56280..12bb34fd29 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -13,6 +13,7 @@ import pytest from starlette.testclient import TestClient +from antarest.study.storage.rawstudy.model.filesystem.config.links import TransmissionCapacity from tests.integration.prepare_proxy import PreparerProxy @@ -23,7 +24,10 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore preparer = PreparerProxy(client, user_access_token) - study_id = preparer.create_study("foo", version=880) + study_id = preparer.create_study("foo", version=820) + if study_type == "variant": + study_id = preparer.create_variant(study_id, name="Variant 1") + area1_id = preparer.create_area(study_id, name="Area 1")["id"] area2_id = preparer.create_area(study_id, name="Area 2")["id"] area3_id = preparer.create_area(study_id, name="Area 3")["id"] @@ -51,42 +55,20 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: "use-phase-shifter": False, } assert expected == res.json() - client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") + res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") + res.raise_for_status() # Test create link with parameters - res = client.post( - f"/v1/studies/{study_id}/links", - json={ - "area1": area1_id, - "area2": area2_id, - "asset-type": "dc", - "colorb": 160, - "colorg": 170, - "colorr": 180, - "display-comments": True, - "filter-synthesis": "hourly", - "hurdles-cost": True, - "link-style": "plain", - "link-width": 2.0, - "loop-flow": False, - "transmission-capacities": "enabled", - "use-phase-shifter": True, - }, - ) - - assert res.status_code == 200, res.json() - - expected = { - "area1": "area 1", - "area2": "area 2", + parameters = { + "area1": area1_id, + "area2": area2_id, "asset-type": "dc", "colorb": 160, "colorg": 170, "colorr": 180, "display-comments": True, "filter-synthesis": "hourly", - "filter-year-by-year": "hourly, daily, weekly, monthly, annual", "hurdles-cost": True, "link-style": "plain", "link-width": 2.0, @@ -94,8 +76,17 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: "transmission-capacities": "enabled", "use-phase-shifter": True, } - assert expected == res.json() + res = client.post( + f"/v1/studies/{study_id}/links", + json=parameters, + ) + + assert res.status_code == 200, res.json() + parameters["filter-year-by-year"] = "hourly, daily, weekly, monthly, annual" + + assert parameters == res.json() res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") + res.raise_for_status() # Create two links, count them, then delete one @@ -111,20 +102,35 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: assert 2 == len(res.json()) res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area3_id}") - assert res.status_code == 200, res.json() + res.raise_for_status() res = client.get(f"/v1/studies/{study_id}/links") assert res.status_code == 200, res.json() assert 1 == len(res.json()) client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") + res.raise_for_status() # Test create link with same area res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area1_id}) assert res.status_code == 422, res.json() - expected = {"description": "Cannot create link on same node", "exception": "LinkValidationError"} + expected = {"description": "Cannot create link on same area: area 1", "exception": "LinkValidationError"} + assert expected == res.json() + + # Test create link with wrong value for enum + + res = client.post( + f"/v1/studies/{study_id}/links", + json={"area1": area1_id, "area2": area1_id, "asset-type": TransmissionCapacity.ENABLED}, + ) + assert res.status_code == 422, res.json() + expected = { + "body": {"area1": "area 1", "area2": "area 1", "asset-type": "enabled"}, + "description": "Input should be 'ac', 'dc', 'gaz', 'virt' or 'other'", + "exception": "RequestValidationError", + } assert expected == res.json() # Test create link with wrong color parameter @@ -133,8 +139,9 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: assert res.status_code == 422, res.json() expected = { - "description": "Invalid value for colorr. Must be between 0 and 255.", - "exception": "LinkValidationError", + "body": {"area1": "area 1", "area2": "area 2", "colorr": 260}, + "description": "Input should be less than 255", + "exception": "RequestValidationError", } assert expected == res.json() @@ -152,8 +159,7 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: } assert expected == res.json() - @pytest.mark.parametrize("study_type", ["raw", "variant"]) - def test_create_link_810(self, client: TestClient, user_access_token: str, study_type: str) -> None: + def test_create_link_810(self, client: TestClient, user_access_token: str) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore preparer = PreparerProxy(client, user_access_token) diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 4ef334d532..5e2b7c7695 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -593,7 +593,7 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: }, ) res.raise_for_status() - res_links = client.get(f"/v1/studies/{study_id}/links?with_ui=true") + res_links = client.get(f"/v1/studies/{study_id}/links") assert res_links.json() == [ { "area1": "area 1", diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 945d09db12..025de3052b 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -136,6 +136,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): } area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) + study.version = 820 link_manager.create_link( study, LinkInfoDTO820( @@ -144,6 +145,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ), ) assert empty_study.config.areas["test"].links.get("test2") is not None + study.version = -1 link_manager.delete_link(study, "test", "test2") assert empty_study.config.areas["test"].links.get("test2") is None @@ -528,7 +530,7 @@ def test_get_all_area(): } }, ] - links = link_manager.get_all_links(study, with_ui=True) + links = link_manager.get_all_links(study) assert [ { "area1": "a1", diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index 7c88fbe97a..14003dcf6c 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -47,6 +47,15 @@ def test_validation(self, empty_study: FileStudy, command_context: CommandContex } ).apply(empty_study) + with pytest.raises(ValidationError): + CreateLink( + area1=area1, + area2=area1, + parameters={}, + command_context=command_context, + series=[[0]], + ) + def test_apply(self, empty_study: FileStudy, command_context: CommandContext): study_path = empty_study.config.study_path area1 = "Area1" From 18f2d77dfb17efc9479af0e79eae15665d679b14 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:15:59 +0200 Subject: [PATCH 033/103] build(ui): fix issue with build result not working (#2163) --- webapp/package-lock.json | 6491 ++++++++--------- webapp/package.json | 144 +- webapp/src/components/App/Data/index.tsx | 13 +- .../components/App/Settings/Groups/Header.tsx | 8 - .../Groups/dialog/CreateGroupDialog.tsx | 8 - .../Groups/dialog/GroupFormDialog/index.tsx | 12 - .../components/App/Settings/Groups/index.tsx | 12 - .../components/App/Settings/Tokens/Header.tsx | 8 - .../dialog/TokenFormDialog/TokenForm.tsx | 5 - .../Tokens/dialog/TokenFormDialog/index.tsx | 12 - .../Tokens/dialog/TokenInfoDialog.tsx | 8 - .../components/App/Settings/Tokens/index.tsx | 12 - .../components/App/Settings/Users/Header.tsx | 8 - .../Users/dialog/UserFormDialog/UserForm.tsx | 4 - .../Users/dialog/UserFormDialog/index.tsx | 14 - .../components/App/Settings/Users/index.tsx | 12 - webapp/src/components/App/Settings/index.tsx | 4 - .../Notes/NodeEditorModal/index.tsx | 4 - .../dialogs/ThematicTrimmingDialog/index.tsx | 11 +- .../Modelization/Areas/AreaPropsView.tsx | 4 - .../Hydro/Allocation/AllocationField.tsx | 11 +- .../Hydro/Correlation/CorrelationField.tsx | 14 +- .../constraintviews/ConstraintElement.tsx | 8 +- .../src/components/App/Singlestudy/index.tsx | 4 - .../components/App/Tasks/LaunchJobLogView.tsx | 5 - webapp/src/components/common/Fieldset.tsx | 7 +- webapp/src/components/common/FileTable.tsx | 10 - .../common/GroupedDataTable/index.tsx | 2 +- webapp/src/components/common/LogModal.tsx | 8 - .../src/components/common/MatrixGrid/style.ts | 3 +- webapp/src/components/common/SelectMulti.tsx | 10 +- webapp/src/components/common/SelectSingle.tsx | 19 +- .../src/components/common/TextSeparator.tsx | 5 - .../components/common/TreeItemEnhanced.tsx | 4 +- .../components/common/dialogs/BasicDialog.tsx | 20 - .../common/dialogs/ConfirmationDialog.tsx | 13 - .../common/dialogs/DataViewerDialog/index.tsx | 4 - .../components/common/dialogs/OkDialog.tsx | 13 - .../common/loaders/SimpleLoader.tsx | 8 +- .../src/components/common/page/BasicPage.tsx | 13 - .../src/components/common/page/RootPage.tsx | 15 - webapp/src/hooks/useEnqueueErrorSnackbar.tsx | 8 - webapp/src/redux/ducks/auth.ts | 2 +- 43 files changed, 3085 insertions(+), 3915 deletions(-) diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 3fea1937e4..c516722650 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -8,46 +8,46 @@ "name": "antares-web", "version": "2.17.6", "dependencies": { - "@emotion/react": "11.11.1", - "@emotion/styled": "11.11.0", + "@emotion/react": "11.13.3", + "@emotion/styled": "11.13.0", "@glideapps/glide-data-grid": "6.0.3", - "@handsontable/react": "14.1.0", - "@mui/icons-material": "5.16.1", - "@mui/lab": "5.0.0-alpha.172", - "@mui/material": "5.16.1", - "@mui/x-tree-view": "7.10.0", + "@handsontable/react": "14.5.0", + "@mui/icons-material": "6.1.1", + "@mui/lab": "6.0.0-beta.10", + "@mui/material": "6.1.1", + "@mui/x-tree-view": "7.18.0", "@reduxjs/toolkit": "1.9.6", - "axios": "1.5.1", - "clsx": "2.0.0", + "axios": "1.7.7", + "clsx": "2.1.1", "d3": "5.16.0", - "debug": "4.3.4", + "debug": "4.3.7", "draft-convert": "2.1.13", "draft-js": "0.11.7", "draftjs-to-html": "0.9.1", - "handsontable": "14.1.0", + "handsontable": "14.5.0", "hoist-non-react-statics": "3.3.2", - "i18next": "23.5.1", - "i18next-browser-languagedetector": "7.1.0", - "i18next-http-backend": "2.4.2", - "immer": "10.0.3", + "i18next": "23.15.1", + "i18next-browser-languagedetector": "8.0.0", + "i18next-http-backend": "2.6.1", + "immer": "10.1.1", "js-cookie": "3.0.5", - "jsoneditor": "9.10.4", - "jwt-decode": "3.1.2", + "jsoneditor": "10.1.0", + "jwt-decode": "4.0.0", "lodash": "4.17.21", - "material-react-table": "2.0.5", - "moment": "2.29.4", + "material-react-table": "3.0.1", + "moment": "2.30.1", "notistack": "3.0.1", - "plotly.js": "2.26.1", - "ramda": "0.29.0", - "ramda-adjunct": "4.1.1", - "react": "18.2.0", + "plotly.js": "2.35.2", + "ramda": "0.30.1", + "ramda-adjunct": "5.1.0", + "react": "18.3.1", "react-beautiful-dnd": "13.1.1", "react-color": "2.19.3", "react-d3-graph": "2.6.0", - "react-dom": "18.2.0", + "react-dom": "18.3.1", "react-dropzone": "14.2.3", - "react-hook-form": "7.47.0", - "react-i18next": "13.2.2", + "react-hook-form": "7.53.0", + "react-i18next": "15.0.2", "react-json-view": "1.21.3", "react-plotly.js": "2.6.0", "react-redux": "8.1.3", @@ -55,64 +55,63 @@ "react-router-dom": "6.3.0", "react-split": "2.0.14", "react-syntax-highlighter": "15.5.0", - "react-use": "17.4.0", - "react-virtualized-auto-sizer": "1.0.20", - "react-window": "1.8.9", + "react-use": "17.5.1", + "react-virtualized-auto-sizer": "1.0.24", + "react-window": "1.8.10", "redux": "4.2.1", "redux-thunk": "2.4.2", - "swagger-ui-react": "5.9.0", + "swagger-ui-react": "5.17.14", "ts-toolbelt": "9.6.0", "use-undo": "1.1.1", - "uuid": "9.0.1", + "uuid": "10.0.0", "xml-js": "1.6.11" }, "devDependencies": { - "@testing-library/jest-dom": "6.4.6", - "@testing-library/react": "16.0.0", + "@testing-library/jest-dom": "6.5.0", + "@testing-library/react": "16.0.1", "@testing-library/user-event": "14.5.2", - "@total-typescript/ts-reset": "0.5.1", + "@total-typescript/ts-reset": "0.6.1", "@types/d3": "5.16.0", - "@types/debug": "4.1.9", - "@types/draft-convert": "2.1.5", - "@types/draft-js": "0.11.13", - "@types/draftjs-to-html": "0.8.2", - "@types/js-cookie": "3.0.4", + "@types/debug": "4.1.12", + "@types/draft-convert": "2.1.8", + "@types/draft-js": "0.11.18", + "@types/draftjs-to-html": "0.8.4", + "@types/js-cookie": "3.0.6", "@types/jsoneditor": "9.9.5", - "@types/lodash": "4.14.199", - "@types/node": "18.16.1", - "@types/ramda": "0.29.5", - "@types/react": "18.2.24", - "@types/react-beautiful-dnd": "13.1.5", - "@types/react-color": "3.0.7", + "@types/lodash": "4.17.9", + "@types/node": "22.7.3", + "@types/ramda": "0.30.2", + "@types/react": "18.3.9", + "@types/react-beautiful-dnd": "13.1.8", + "@types/react-color": "3.0.12", "@types/react-d3-graph": "2.6.5", - "@types/react-dom": "18.2.8", - "@types/react-plotly.js": "2.6.1", + "@types/react-dom": "18.3.0", + "@types/react-plotly.js": "2.6.3", "@types/react-syntax-highlighter": "15.5.13", - "@types/react-virtualized-auto-sizer": "1.0.1", - "@types/react-window": "1.8.6", - "@types/redux-logger": "3.0.10", - "@types/swagger-ui-react": "4.18.1", + "@types/react-virtualized-auto-sizer": "1.0.4", + "@types/react-window": "1.8.8", + "@types/swagger-ui-react": "4.18.3", "@types/testing-library__jest-dom": "6.0.0", - "@types/uuid": "9.0.4", - "@typescript-eslint/eslint-plugin": "6.14.0", - "@typescript-eslint/parser": "6.14.0", + "@types/uuid": "10.0.0", + "@typescript-eslint/eslint-plugin": "7.2.0", + "@typescript-eslint/parser": "7.2.0", "@vitejs/plugin-react-swc": "3.7.0", - "@vitest/coverage-v8": "1.6.0", - "@vitest/ui": "1.6.0", - "eslint": "8.55.0", - "eslint-config-prettier": "9.0.0", - "eslint-plugin-jsdoc": "48.2.0", + "@vitest/coverage-v8": "2.1.1", + "@vitest/ui": "2.1.1", + "eslint": "8.57.1", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-jsdoc": "48.10.0", "eslint-plugin-license-header": "0.6.1", - "eslint-plugin-prettier": "5.0.0", - "eslint-plugin-react": "7.33.2", - "eslint-plugin-react-hooks": "4.6.0", - "eslint-plugin-react-refresh": "0.4.5", - "husky": "8.0.3", - "jsdom": "24.1.0", - "prettier": "3.0.3", - "typescript": "5.2.2", + "eslint-plugin-prettier": "5.2.1", + "eslint-plugin-react": "7.37.0", + "eslint-plugin-react-hooks": "4.6.2", + "eslint-plugin-react-refresh": "0.4.12", + "husky": "9.1.6", + "jsdom": "25.0.1", + "prettier": "3.3.3", + "typescript": "5.4.5", "vite": "5.4.8", - "vitest": "1.6.0" + "vitest": "2.1.1" }, "engines": { "node": "18.16.1" @@ -459,9 +458,9 @@ "dev": true }, "node_modules/@braintree/sanitize-url": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", - "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==" + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.0.2.tgz", + "integrity": "sha512-NVf/1YycDMs6+FxS0Tb/W8MjJRDQdXF+tBfDtZ5UZeiRUkTmwKc4vmYCKZTyymfJk1gnMsauvZSX/HiV9jOABw==" }, "node_modules/@choojs/findup": { "version": "0.2.1", @@ -504,11 +503,6 @@ "stylis": "4.2.0" } }, - "node_modules/@emotion/cache/node_modules/@emotion/weak-memoize": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" - }, "node_modules/@emotion/hash": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", @@ -528,17 +522,17 @@ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" }, "node_modules/@emotion/react": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", - "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.2", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { @@ -568,16 +562,16 @@ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" }, "node_modules/@emotion/styled": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", - "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.1", - "@emotion/serialize": "^1.1.2", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1" + "@emotion/babel-plugin": "^11.12.0", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", @@ -608,18 +602,18 @@ "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==" }, "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz", - "integrity": "sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", + "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==", "dev": true, "dependencies": { "comment-parser": "1.4.1", - "esquery": "^1.5.0", + "esquery": "^1.6.0", "jsdoc-type-pratt-parser": "~4.0.0" }, "engines": { @@ -1079,9 +1073,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1144,21 +1138,21 @@ "integrity": "sha512-1VN6N38t5/DcjJ7y7XUYrDx1LuzvvzlrFdBdMG90Qo1xc8+LXHqbWbsTEm5Ec5gXTEbDEO53vUT35R+2COmOyg==" }, "node_modules/@handsontable/react": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@handsontable/react/-/react-14.1.0.tgz", - "integrity": "sha512-tgqeiiVIG1PBczaq5/pwtQBlhmldej4wEfqx3L7aJn1fcbArGGsCbRdNd6aneH4BP79aC/ajiwr2vVx7NeAcgg==", + "version": "14.5.0", + "resolved": "https://registry.npmjs.org/@handsontable/react/-/react-14.5.0.tgz", + "integrity": "sha512-Z6weZTELY1hqgW8TDno000xScd+I1sQ0DcswX2AdnCCwvvQkmC74xmIREalwtFE9CCi0Y/kiSvq/G0bYgl//pQ==", "peerDependencies": { "handsontable": ">=14.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -1216,25 +1210,57 @@ "react": "*" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "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==", "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { - "node": ">=8" + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.27.8" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, "node_modules/@jridgewell/gen-mapping": { @@ -1266,6 +1292,16 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "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==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -1424,72 +1460,60 @@ "node": ">=6.0.0" } }, - "node_modules/@mui/base": { - "version": "5.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", - "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.3.1.tgz", + "integrity": "sha512-5ueL4UDitzVtceQ8J4kY+Px3WK+eZTsmGwha3MBKHKqiHvKrjWWwBCIl1K8BuJSc5OFh83uI8IFNoFvQxX2uUw==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "@popperjs/core": "^2.11.8", - "clsx": "^2.1.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "sort-object": "^3.0.3", + "tinyqueue": "^3.0.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" } }, - "node_modules/@mui/base/node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "engines": { - "node": ">=6" - } + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" }, - "node_modules/@mui/core-downloads-tracker": { - "version": "5.16.7", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz", - "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - } + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" }, - "node_modules/@mui/icons-material": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.1.tgz", - "integrity": "sha512-ogQPweYba4+5XZykilwxn2/oS78uwoQ0BVBpOhhCJo0ooZsqTTsalhzP2qD/RdGqMQ8xyXPz1sYM2djTruVVVA==", + "node_modules/@mui/base": { + "version": "5.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.58.tgz", + "integrity": "sha512-P0E7ZrxOuyYqBvVv9w8k7wm+Xzx/KRu+BGgFcR2htTsGCpJNQJCSUXNUZ50MUmSU9hzqhwbQWNXhV1MBTl6F7A==", "dependencies": { - "@babel/runtime": "^7.23.9" + "@babel/runtime": "^7.25.0", + "@floating-ui/react-dom": "^2.1.1", + "@mui/types": "^7.2.15", + "@mui/utils": "6.0.0-rc.0", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.1", + "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^5.0.0", "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1497,119 +1521,84 @@ } } }, - "node_modules/@mui/lab": { - "version": "5.0.0-alpha.172", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.172.tgz", - "integrity": "sha512-stpa3WTsDE1HamFR4eeS6Bhxalm+u9FhzzNph/PrDMdWSRBHlJs2mqvZ6FEoO22O7MOCwNMqbXTkvEwsyEf0ew==", + "node_modules/@mui/base/node_modules/@mui/utils": { + "version": "6.0.0-rc.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.0.0-rc.0.tgz", + "integrity": "sha512-tBp0ILEXDL0bbDDT8PnZOjCqSm5Dfk2N0Z45uzRw+wVl6fVvloC9zw8avl+OdX1Bg3ubs/ttKn8nRNv17bpM5A==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.40", - "@mui/system": "^5.16.1", + "@babel/runtime": "^7.25.0", "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.1", - "clsx": "^2.1.0", - "prop-types": "^15.8.1" + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@mui/material": ">=5.15.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, "@types/react": { "optional": true } } }, - "node_modules/@mui/lab/node_modules/@mui/private-theming": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", - "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.16.6", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.1.tgz", + "integrity": "sha512-VdQC1tPIIcZAnf62L2M1eQif0x2vlKg3YK4kGYbtijSH4niEgI21GnstykW1vQIs+Bc6L+Hua2GATYVjilJ22A==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } } }, - "node_modules/@mui/lab/node_modules/@mui/styled-engine": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", - "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", + "node_modules/@mui/icons-material": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.1.tgz", + "integrity": "sha512-sy/YKwcLPW8VcacNP2uWMYR9xyWuwO9NN9FXuGEU90bRshBXj8pdKk+joe3TCW7oviVS3zXLHlc94wQ0jNsQRQ==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@emotion/cache": "^11.11.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" + "@babel/runtime": "^7.25.6" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" + "@mui/material": "^6.1.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { + "@types/react": { "optional": true } } }, - "node_modules/@mui/lab/node_modules/@mui/system": { - "version": "5.16.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", - "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", + "node_modules/@mui/lab": { + "version": "6.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.10.tgz", + "integrity": "sha512-eqCBz5SZS8Un9To3UcjH01AxkOOgvme/g0ZstFC8Nz1Kg5/EJMA0ByhKS5AvUMzUKrv0FXMdbuPqbBvF3bVrXg==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.16.6", - "@mui/styled-engine": "^5.16.6", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.6", - "clsx": "^2.1.0", - "csstype": "^3.1.3", + "@babel/runtime": "^7.25.6", + "@mui/base": "5.0.0-beta.58", + "@mui/system": "^6.1.1", + "@mui/types": "^7.2.17", + "@mui/utils": "^6.1.1", + "clsx": "^2.1.1", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -1618,8 +1607,11 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@mui/material": "^6.1.1", + "@mui/material-pigment-css": "^6.1.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1628,39 +1620,34 @@ "@emotion/styled": { "optional": true }, + "@mui/material-pigment-css": { + "optional": true + }, "@types/react": { "optional": true } } }, - "node_modules/@mui/lab/node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "engines": { - "node": ">=6" - } - }, "node_modules/@mui/material": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.1.tgz", - "integrity": "sha512-BGTgJRb0d/hX9tus5CEb6N/Fo8pE4tYA+s9r4/S0PCrtZ3urCLXlTH4qrAvggQbiF1cYRAbHCkVHoQ+4Pdxl+w==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.1.tgz", + "integrity": "sha512-b+eULldTqtqTCbN++2BtBWCir/1LwEYw+2mIlOt2GiEUh1EBBw4/wIukGKKNt3xrCZqRA80yLLkV6tF61Lq3cA==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.40", - "@mui/core-downloads-tracker": "^5.16.1", - "@mui/system": "^5.16.1", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.1", - "@types/react-transition-group": "^4.4.10", - "clsx": "^2.1.0", + "@babel/runtime": "^7.25.6", + "@mui/core-downloads-tracker": "^6.1.1", + "@mui/system": "^6.1.1", + "@mui/types": "^7.2.17", + "@mui/utils": "^6.1.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^18.3.1", "react-transition-group": "^4.4.5" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -1669,9 +1656,10 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@mui/material-pigment-css": "^6.1.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1680,30 +1668,33 @@ "@emotion/styled": { "optional": true }, + "@mui/material-pigment-css": { + "optional": true + }, "@types/react": { "optional": true } } }, - "node_modules/@mui/material/node_modules/@mui/private-theming": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", - "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", + "node_modules/@mui/private-theming": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.1.tgz", + "integrity": "sha512-JlrjIdhyZUtewtdAuUsvi3ZnO0YS49IW4Mfz19ZWTlQ0sDGga6LNPVwHClWr2/zJK2we2BQx9/i8M32rgKuzrg==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.16.6", + "@babel/runtime": "^7.25.6", + "@mui/utils": "^6.1.1", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1711,18 +1702,19 @@ } } }, - "node_modules/@mui/material/node_modules/@mui/styled-engine": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", - "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", + "node_modules/@mui/styled-engine": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.1.tgz", + "integrity": "sha512-HJyIoMpFb11fnHuRtUILOXgq6vj4LhIlE8maG4SwP/W+E5sa7HFexhnB3vOMT7bKys4UKNxhobC8jwWxYilGsA==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@emotion/cache": "^11.11.0", + "@babel/runtime": "^7.25.6", + "@emotion/cache": "^11.13.1", + "@emotion/sheet": "^1.4.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -1731,7 +1723,7 @@ "peerDependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1742,22 +1734,22 @@ } } }, - "node_modules/@mui/material/node_modules/@mui/system": { - "version": "5.16.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", - "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", + "node_modules/@mui/system": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.1.tgz", + "integrity": "sha512-PaYsCz2tUOcpu3T0okDEsSuP/yCDIj9JZ4Tox1JovRSKIjltHpXPsXZSGr3RiWdtM1MTQMFMCZzu0+CKbyy+Kw==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.16.6", - "@mui/styled-engine": "^5.16.6", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.6", - "clsx": "^2.1.0", + "@babel/runtime": "^7.25.6", + "@mui/private-theming": "^6.1.1", + "@mui/styled-engine": "^6.1.1", + "@mui/types": "^7.2.17", + "@mui/utils": "^6.1.1", + "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -1766,8 +1758,8 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1781,46 +1773,23 @@ } } }, - "node_modules/@mui/material/node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "engines": { - "node": ">=6" + "node_modules/@mui/types": { + "version": "7.2.17", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.17.tgz", + "integrity": "sha512-oyumoJgB6jDV8JFzRqjBo2daUuHpzDjoO/e3IrRhhHo/FxJlaVhET6mcNrKHUq2E+R+q3ql0qAtvQ4rfWHhAeQ==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@mui/private-theming": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.1.tgz", - "integrity": "sha512-JlrjIdhyZUtewtdAuUsvi3ZnO0YS49IW4Mfz19ZWTlQ0sDGga6LNPVwHClWr2/zJK2we2BQx9/i8M32rgKuzrg==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.25.6", - "@mui/utils": "^6.1.1", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/private-theming/node_modules/@mui/utils": { + "node_modules/@mui/utils": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.1.tgz", "integrity": "sha512-HlRrgdJSPbYDXPpoVMWZV8AE7WcFtAk13rWNWAEVWKSanzBBkymjz3km+Th/Srowsh4pf1fTSP1B0L116wQBYw==", - "peer": true, "dependencies": { "@babel/runtime": "^7.25.6", "@mui/types": "^7.2.17", @@ -1846,26 +1815,19 @@ } } }, - "node_modules/@mui/private-theming/node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@mui/styled-engine": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.1.tgz", - "integrity": "sha512-HJyIoMpFb11fnHuRtUILOXgq6vj4LhIlE8maG4SwP/W+E5sa7HFexhnB3vOMT7bKys4UKNxhobC8jwWxYilGsA==", + "node_modules/@mui/x-date-pickers": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.18.0.tgz", + "integrity": "sha512-12tXIoMj9vpS8fS/bS3kWPCoVrH38vNGCxgplI0vOnUrN9rJuYJz3agLPJe1S0xciTw+9W8ZSe3soaW+owoz1Q==", "peer": true, "dependencies": { "@babel/runtime": "^7.25.6", - "@emotion/cache": "^11.13.1", - "@emotion/sheet": "^1.4.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" + "@mui/utils": "^5.16.6", + "@mui/x-internals": "7.18.0", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" }, "engines": { "node": ">=14.0.0" @@ -1875,9 +1837,19 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", + "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1885,61 +1857,67 @@ }, "@emotion/styled": { "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true } } }, - "node_modules/@mui/system": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.1.tgz", - "integrity": "sha512-PaYsCz2tUOcpu3T0okDEsSuP/yCDIj9JZ4Tox1JovRSKIjltHpXPsXZSGr3RiWdtM1MTQMFMCZzu0+CKbyy+Kw==", + "node_modules/@mui/x-date-pickers/node_modules/@mui/utils": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", "peer": true, "dependencies": { - "@babel/runtime": "^7.25.6", - "@mui/private-theming": "^6.1.1", - "@mui/styled-engine": "^6.1.1", - "@mui/types": "^7.2.17", - "@mui/utils": "^6.1.1", + "@babel/runtime": "^7.23.9", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", "clsx": "^2.1.1", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" + "prop-types": "^15.8.1", + "react-is": "^18.3.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, "@types/react": { "optional": true } } }, - "node_modules/@mui/system/node_modules/@mui/utils": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.1.tgz", - "integrity": "sha512-HlRrgdJSPbYDXPpoVMWZV8AE7WcFtAk13rWNWAEVWKSanzBBkymjz3km+Th/Srowsh4pf1fTSP1B0L116wQBYw==", - "peer": true, + "node_modules/@mui/x-internals": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.18.0.tgz", + "integrity": "sha512-lzCHOWIR0cAIY1bGrWSprYerahbnH5C31ql/2OWCEjcngL2NAV1M6oKI2Vp4HheqzJ822c60UyWyapvyjSzY/A==", "dependencies": { "@babel/runtime": "^7.25.6", - "@mui/types": "^7.2.17", - "@types/prop-types": "^15.7.12", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-is": "^18.3.1" + "@mui/utils": "^5.16.6" }, "engines": { "node": ">=14.0.0" @@ -1949,38 +1927,10 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/system/node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@mui/types": { - "version": "7.2.17", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.17.tgz", - "integrity": "sha512-oyumoJgB6jDV8JFzRqjBo2daUuHpzDjoO/e3IrRhhHo/FxJlaVhET6mcNrKHUq2E+R+q3ql0qAtvQ4rfWHhAeQ==", - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "react": "^17.0.0 || ^18.0.0" } }, - "node_modules/@mui/utils": { + "node_modules/@mui/x-internals/node_modules/@mui/utils": { "version": "5.16.6", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", @@ -2009,19 +1959,10 @@ } } }, - "node_modules/@mui/utils/node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@mui/x-date-pickers": { + "node_modules/@mui/x-tree-view": { "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.18.0.tgz", - "integrity": "sha512-12tXIoMj9vpS8fS/bS3kWPCoVrH38vNGCxgplI0vOnUrN9rJuYJz3agLPJe1S0xciTw+9W8ZSe3soaW+owoz1Q==", - "peer": true, + "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.18.0.tgz", + "integrity": "sha512-3UJAYtBquc0SzKxEEdM68XlKOuuCl70ktZPqqI3z4wTZ0HK445XXc32t/s0VPIL94kRxWQcGPpgWFauScDwhug==", "dependencies": { "@babel/runtime": "^7.25.6", "@mui/utils": "^5.16.6", @@ -2043,13 +1984,6 @@ "@emotion/styled": "^11.8.1", "@mui/material": "^5.15.14 || ^6.0.0", "@mui/system": "^5.15.14 || ^6.0.0", - "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", - "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", - "dayjs": "^1.10.7", - "luxon": "^3.0.2", - "moment": "^2.29.4", - "moment-hijri": "^2.1.2", - "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }, @@ -2059,200 +1993,46 @@ }, "@emotion/styled": { "optional": true - }, - "date-fns": { - "optional": true - }, - "date-fns-jalali": { - "optional": true - }, - "dayjs": { - "optional": true - }, - "luxon": { - "optional": true - }, - "moment": { - "optional": true - }, - "moment-hijri": { - "optional": true - }, - "moment-jalaali": { - "optional": true } } }, - "node_modules/@mui/x-date-pickers/node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@mui/x-internals": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.18.0.tgz", - "integrity": "sha512-lzCHOWIR0cAIY1bGrWSprYerahbnH5C31ql/2OWCEjcngL2NAV1M6oKI2Vp4HheqzJ822c60UyWyapvyjSzY/A==", - "peer": true, + "node_modules/@mui/x-tree-view/node_modules/@mui/utils": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", "dependencies": { - "@babel/runtime": "^7.25.6", - "@mui/utils": "^5.16.6" + "@babel/runtime": "^7.23.9", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@mui/x-tree-view": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.10.0.tgz", - "integrity": "sha512-9OCAIb0wS5uuEDyjcSwSturrB4RUXBfE0UO/xpKjrMvRzCaAvxbCf2aFILP8uH9NyynYZkIGYfGnlqdAPy2OLg==", - "dependencies": { - "@babel/runtime": "^7.24.7", - "@mui/base": "^5.0.0-beta.40", - "@mui/system": "^5.16.0", - "@mui/utils": "^5.16.0", - "@types/react-transition-group": "^4.4.10", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - } - }, - "node_modules/@mui/x-tree-view/node_modules/@mui/private-theming": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", - "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.16.6", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/x-tree-view/node_modules/@mui/styled-engine": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", - "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@emotion/cache": "^11.11.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/x-tree-view/node_modules/@mui/system": { - "version": "5.16.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", - "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.16.6", - "@mui/styled-engine": "^5.16.6", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.6", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/x-tree-view/node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { "node": ">= 8" @@ -2280,6 +2060,16 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", @@ -2293,9 +2083,9 @@ } }, "node_modules/@plotly/d3": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.1.tgz", - "integrity": "sha512-x49ThEu1FRA00kTso4Jdfyf2byaCPLBGmLjAYQz5OzaPyLUhHesX3/Nfv2OHEhynhdy2UB39DLXq6thYe2L2kg==" + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.2.tgz", + "integrity": "sha512-wvsNmh1GYjyJfyEBPKJLTMzgf2c2bEbSIL50lmqVUi+o1NHaLPi1Lb4v7VxXXJn043BhNyrxUrWI85Q+zmjOVA==" }, "node_modules/@plotly/d3-sankey": { "version": "0.7.2", @@ -2318,6 +2108,38 @@ "elementary-circuits-directed-graph": "^1.0.4" } }, + "node_modules/@plotly/mapbox-gl": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@plotly/mapbox-gl/-/mapbox-gl-1.13.4.tgz", + "integrity": "sha512-sR3/Pe5LqT/fhYgp4rT4aSFf1rTsxMbGiH6Hojc7PH36ny5Bn17iVFUjpzycafETURuFbLZUfjODO8LvSI+5zQ==", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/geojson-types": "^1.0.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^1.5.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^1.1.1", + "@mapbox/unitbezier": "^0.0.0", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.2", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.2.1", + "grid-index": "^1.1.0", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.1", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^7.1.0", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.1" + }, + "engines": { + "node": ">=6.4.0" + } + }, "node_modules/@plotly/point-cluster": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/@plotly/point-cluster/-/point-cluster-3.1.9.tgz", @@ -2382,124 +2204,246 @@ "url": "https://opencollective.com/immer" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@sphinxxxx/color-conversion": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz", - "integrity": "sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==" + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@swagger-api/apidom-ast": { - "version": "1.0.0-alpha.9", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-alpha.9.tgz", - "integrity": "sha512-SAOQrFSFwgDiI4QSIPDwAIJEb4Za+8bu45sNojgV3RMtCz+n4Agw66iqGsDib5YSI/Cg1h4AKFovT3iWdfGWfw==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-error": "^1.0.0-alpha.9", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "unraw": "^3.0.0" - } + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@swagger-api/apidom-ast/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "dependencies": { - "types-ramda": "^0.30.1" - } + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@swagger-api/apidom-ast/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", + "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@swagger-api/apidom-ast/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@swagger-api/apidom-ast/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "dependencies": { - "ts-toolbelt": "^9.6.0" - } + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@swagger-api/apidom-core": { - "version": "1.0.0-alpha.9", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-alpha.9.tgz", - "integrity": "sha512-vGl8BWRf6ODl39fxElcIOjRE2QG5AJhn8tTNMqjjHB/2WppNBuxOVStYZeVJoWfK03OPK8v4Fp/TAcaP9+R7DQ==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^1.0.0-alpha.9", - "@swagger-api/apidom-error": "^1.0.0-alpha.9", - "@types/ramda": "~0.30.0", - "minim": "~0.23.8", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "short-unique-id": "^5.0.2", - "ts-mixer": "^6.0.3" - } + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@swagger-api/apidom-core/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "dependencies": { - "types-ramda": "^0.30.1" - } + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@swagger-api/apidom-core/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@swagger-api/apidom-core/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sphinxxxx/color-conversion": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz", + "integrity": "sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==" + }, + "node_modules/@swagger-api/apidom-ast": { + "version": "1.0.0-alpha.9", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-alpha.9.tgz", + "integrity": "sha512-SAOQrFSFwgDiI4QSIPDwAIJEb4Za+8bu45sNojgV3RMtCz+n4Agw66iqGsDib5YSI/Cg1h4AKFovT3iWdfGWfw==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-error": "^1.0.0-alpha.9", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "unraw": "^3.0.0" } }, - "node_modules/@swagger-api/apidom-core/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "node_modules/@swagger-api/apidom-core": { + "version": "1.0.0-alpha.9", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-alpha.9.tgz", + "integrity": "sha512-vGl8BWRf6ODl39fxElcIOjRE2QG5AJhn8tTNMqjjHB/2WppNBuxOVStYZeVJoWfK03OPK8v4Fp/TAcaP9+R7DQ==", "dependencies": { - "ts-toolbelt": "^9.6.0" + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-alpha.9", + "@swagger-api/apidom-error": "^1.0.0-alpha.9", + "@types/ramda": "~0.30.0", + "minim": "~0.23.8", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "short-unique-id": "^5.0.2", + "ts-mixer": "^6.0.3" } }, "node_modules/@swagger-api/apidom-error": { @@ -2523,46 +2467,6 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-json-pointer/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-json-pointer/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-json-pointer/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-json-pointer/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-ns-api-design-systems": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-alpha.9.tgz", @@ -2579,50 +2483,6 @@ "ts-mixer": "^6.0.3" } }, - "node_modules/@swagger-api/apidom-ns-api-design-systems/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-ns-api-design-systems/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-ns-api-design-systems/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-ns-api-design-systems/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-ns-asyncapi-2": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-alpha.9.tgz", @@ -2634,106 +2494,22 @@ "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-alpha.9", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "ts-mixer": "^6.0.3" - } - }, - "node_modules/@swagger-api/apidom-ns-asyncapi-2/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-ns-asyncapi-2/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-ns-asyncapi-2/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-ns-asyncapi-2/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { - "version": "1.0.0-alpha.9", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-alpha.9.tgz", - "integrity": "sha512-OfX4UBb08C0xD5+F80dQAM2yt5lXxcURWkVEeCwxz7i23BB3nNEbnZXNV91Qo9eaJflPh8dO9iiHQxvfw5IgSg==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^1.0.0-alpha.9", - "@swagger-api/apidom-core": "^1.0.0-alpha.9", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "ts-mixer": "^6.0.4" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-4/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-4/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-4/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" } }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-4/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { + "version": "1.0.0-alpha.9", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-alpha.9.tgz", + "integrity": "sha512-OfX4UBb08C0xD5+F80dQAM2yt5lXxcURWkVEeCwxz7i23BB3nNEbnZXNV91Qo9eaJflPh8dO9iiHQxvfw5IgSg==", "dependencies": { - "ts-toolbelt": "^9.6.0" + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-alpha.9", + "@swagger-api/apidom-core": "^1.0.0-alpha.9", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { @@ -2752,50 +2528,6 @@ "ts-mixer": "^6.0.4" } }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-6/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-6/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-6/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-6/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-alpha.9.tgz", @@ -2812,50 +2544,6 @@ "ts-mixer": "^6.0.4" } }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-7/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-7/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-7/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-7/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-ns-openapi-2": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-alpha.9.tgz", @@ -2872,50 +2560,6 @@ "ts-mixer": "^6.0.3" } }, - "node_modules/@swagger-api/apidom-ns-openapi-2/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-2/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-2/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-2/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-ns-openapi-3-0": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-alpha.9.tgz", @@ -2931,46 +2575,6 @@ "ts-mixer": "^6.0.3" } }, - "node_modules/@swagger-api/apidom-ns-openapi-3-0/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-3-0/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-3-0/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-3-0/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-ns-openapi-3-1": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-alpha.9.tgz", @@ -2987,103 +2591,19 @@ "ts-mixer": "^6.0.3" } }, - "node_modules/@swagger-api/apidom-ns-openapi-3-1/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-3-1/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-3-1/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-3-1/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-ns-workflows-1": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-workflows-1/-/apidom-ns-workflows-1-1.0.0-alpha.9.tgz", - "integrity": "sha512-yKo0p8OkQmDib93Kt1yqWmI7JsD6D9qUHxr/SCuAmNNWny1hxm7cZGoKJwJlGd0uAg84j4vmzWOlG3AsJbnT8g==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-alpha.9", - "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-alpha.9", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "ts-mixer": "^6.0.3" - } - }, - "node_modules/@swagger-api/apidom-ns-workflows-1/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-ns-workflows-1/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-ns-workflows-1/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-ns-workflows-1/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "integrity": "sha512-yKo0p8OkQmDib93Kt1yqWmI7JsD6D9qUHxr/SCuAmNNWny1hxm7cZGoKJwJlGd0uAg84j4vmzWOlG3AsJbnT8g==", "optional": true, "dependencies": { - "ts-toolbelt": "^9.6.0" + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.9", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-alpha.9", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" } }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { @@ -3101,50 +2621,6 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.0-alpha.9.tgz", @@ -3160,50 +2636,6 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.0-alpha.9.tgz", @@ -3219,50 +2651,6 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.0-alpha.9.tgz", @@ -3278,50 +2666,6 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-json": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.0-alpha.9.tgz", @@ -3340,107 +2684,19 @@ "web-tree-sitter": "=0.20.3" } }, - "node_modules/@swagger-api/apidom-parser-adapter-json/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-json/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-json/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-json/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.0-alpha.9.tgz", "integrity": "sha512-zgtsAfkplCFusX2P/saqdn10J8P3kQizCXxHLvxd2j0EhMJk2wfu4HYN5Pej/7/qf/OR1QZxqtacwebd4RfpXA==", "optional": true, "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-alpha.9", - "@swagger-api/apidom-ns-openapi-2": "^1.0.0-alpha.9", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.9", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.9", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-alpha.9", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.9", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { @@ -3458,50 +2714,6 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.0-alpha.9.tgz", @@ -3517,50 +2729,6 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.0-alpha.9.tgz", @@ -3576,50 +2744,6 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.0-alpha.9.tgz", @@ -3635,50 +2759,6 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.0-alpha.9.tgz", @@ -3694,107 +2774,19 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-workflows-json-1": { - "version": "1.0.0-alpha.9", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-json-1/-/apidom-parser-adapter-workflows-json-1-1.0.0-alpha.9.tgz", - "integrity": "sha512-LylC2cQdAmvR7bXqwMwBt6FHTMVGinwIdI8pjl4EbPT9hCVm1rdED53caTYM4gCm+CJGRw20r4gb9vn3+N6RrA==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-alpha.9", - "@swagger-api/apidom-ns-workflows-1": "^1.0.0-alpha.9", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.9", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-workflows-json-1/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-workflows-json-1/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-workflows-json-1/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-workflows-json-1/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "version": "1.0.0-alpha.9", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-json-1/-/apidom-parser-adapter-workflows-json-1-1.0.0-alpha.9.tgz", + "integrity": "sha512-LylC2cQdAmvR7bXqwMwBt6FHTMVGinwIdI8pjl4EbPT9hCVm1rdED53caTYM4gCm+CJGRw20r4gb9vn3+N6RrA==", "optional": true, "dependencies": { - "ts-toolbelt": "^9.6.0" + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.9", + "@swagger-api/apidom-ns-workflows-1": "^1.0.0-alpha.9", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.9", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-workflows-yaml-1": { @@ -3812,50 +2804,6 @@ "ramda-adjunct": "^5.0.0" } }, - "node_modules/@swagger-api/apidom-parser-adapter-workflows-yaml-1/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-workflows-yaml-1/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-workflows-yaml-1/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-workflows-yaml-1/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.0-alpha.9.tgz", @@ -3874,50 +2822,6 @@ "web-tree-sitter": "=0.20.3" } }, - "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "optional": true, - "dependencies": { - "types-ramda": "^0.30.1" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "optional": true, - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "optional": true, - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swagger-api/apidom-reference": { "version": "1.0.0-alpha.9", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-alpha.9.tgz", @@ -3956,14 +2860,6 @@ "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-alpha.1" } }, - "node_modules/@swagger-api/apidom-reference/node_modules/@types/ramda": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", - "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", - "dependencies": { - "types-ramda": "^0.30.1" - } - }, "node_modules/@swagger-api/apidom-reference/node_modules/minimatch": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", @@ -3978,42 +2874,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@swagger-api/apidom-reference/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/@swagger-api/apidom-reference/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, - "node_modules/@swagger-api/apidom-reference/node_modules/types-ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", - "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, "node_modules/@swc/core": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.28.tgz", - "integrity": "sha512-XapcMgsOS0cKh01AFEj+qXOk6KM4NZhp7a5vPicdhkRR8RzvjrCa7DTtijMxfotU8bqaEHguxmiIag2HUlT8QQ==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.26.tgz", + "integrity": "sha512-f5uYFf+TmMQyYIoxkn/evWhNGuUzC730dFwAKGwBVHHVoPyak1/GvJUm6i1SKl+2Hrj9oN0i3WSoWWZ4pgI8lw==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -4028,16 +2892,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.7.28", - "@swc/core-darwin-x64": "1.7.28", - "@swc/core-linux-arm-gnueabihf": "1.7.28", - "@swc/core-linux-arm64-gnu": "1.7.28", - "@swc/core-linux-arm64-musl": "1.7.28", - "@swc/core-linux-x64-gnu": "1.7.28", - "@swc/core-linux-x64-musl": "1.7.28", - "@swc/core-win32-arm64-msvc": "1.7.28", - "@swc/core-win32-ia32-msvc": "1.7.28", - "@swc/core-win32-x64-msvc": "1.7.28" + "@swc/core-darwin-arm64": "1.7.26", + "@swc/core-darwin-x64": "1.7.26", + "@swc/core-linux-arm-gnueabihf": "1.7.26", + "@swc/core-linux-arm64-gnu": "1.7.26", + "@swc/core-linux-arm64-musl": "1.7.26", + "@swc/core-linux-x64-gnu": "1.7.26", + "@swc/core-linux-x64-musl": "1.7.26", + "@swc/core-win32-arm64-msvc": "1.7.26", + "@swc/core-win32-ia32-msvc": "1.7.26", + "@swc/core-win32-x64-msvc": "1.7.26" }, "peerDependencies": { "@swc/helpers": "*" @@ -4049,9 +2913,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.28.tgz", - "integrity": "sha512-BNkj6enHo2pdzOpCtQGKZbXT2A/qWIr0CVtbTM4WkJ3MCK/glbFsyO6X59p1r8+gfaZG4bWYnTTu+RuUAcsL5g==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.26.tgz", + "integrity": "sha512-FF3CRYTg6a7ZVW4yT9mesxoVVZTrcSWtmZhxKCYJX9brH4CS/7PRPjAKNk6kzWgWuRoglP7hkjQcd6EpMcZEAw==", "cpu": [ "arm64" ], @@ -4065,9 +2929,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.28.tgz", - "integrity": "sha512-96zQ+X5Fd6P/RNPkOyikTJgEc2M4TzznfYvjRd2hye5h22jhxCLL/csoauDgN7lYfd7mwsZ/sVXwJTMKl+vZSA==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.26.tgz", + "integrity": "sha512-az3cibZdsay2HNKmc4bjf62QVukuiMRh5sfM5kHR/JMTrLyS6vSw7Ihs3UTkZjUxkLTT8ro54LI6sV6sUQUbLQ==", "cpu": [ "x64" ], @@ -4081,9 +2945,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.28.tgz", - "integrity": "sha512-l2100Wx6LdXMOmOW3+KoHhBhyZrGdz8ylkygcVOC0QHp6YIATfuG+rRHksfyEWCSOdL3anM9MJZJX26KT/s+XQ==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.26.tgz", + "integrity": "sha512-VYPFVJDO5zT5U3RpCdHE5v1gz4mmR8BfHecUZTmD2v1JeFY6fv9KArJUpjrHEEsjK/ucXkQFmJ0jaiWXmpOV9Q==", "cpu": [ "arm" ], @@ -4097,9 +2961,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.28.tgz", - "integrity": "sha512-03m6iQ5Bv9u2VPnNRyaBmE8eHi056eE39L0gXcqGoo46GAGuoqYHt9pDz8wS6EgoN4t85iBMUZrkCNqFKkN6ZQ==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.26.tgz", + "integrity": "sha512-YKevOV7abpjcAzXrhsl+W48Z9mZvgoVs2eP5nY+uoMAdP2b3GxC0Df1Co0I90o2lkzO4jYBpTMcZlmUXLdXn+Q==", "cpu": [ "arm64" ], @@ -4113,9 +2977,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.28.tgz", - "integrity": "sha512-vqVOpG/jc8mvTKQjaPBLhr7tnWyzuztOHsPnJqMWmg7zGcMeQC/2c5pU4uzRAfXMTp25iId6s4Y4wWfPS1EeDw==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.26.tgz", + "integrity": "sha512-3w8iZICMkQQON0uIcvz7+Q1MPOW6hJ4O5ETjA0LSP/tuKqx30hIniCGOgPDnv3UTMruLUnQbtBwVCZTBKR3Rkg==", "cpu": [ "arm64" ], @@ -4129,9 +2993,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.28.tgz", - "integrity": "sha512-HGwpWuB83Kr+V0E+zT5UwIIY9OxiS8aLd0UVMRVWuO8SrQyKm9HKJ46+zoAb8tfJrpZftfxvbn2ayZWR7gqosA==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.26.tgz", + "integrity": "sha512-c+pp9Zkk2lqb06bNGkR2Looxrs7FtGDMA4/aHjZcCqATgp348hOKH5WPvNLBl+yPrISuWjbKDVn3NgAvfvpH4w==", "cpu": [ "x64" ], @@ -4145,9 +3009,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.28.tgz", - "integrity": "sha512-q2Y2T8y8EgFtIiRyInnAXNe94aaHX74F0ha1Bl9VdRxE0u1/So+3VLbPvtp4V3Z6pj5pOePfCQJKifnllgAQ9A==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.26.tgz", + "integrity": "sha512-PgtyfHBF6xG87dUSSdTJHwZ3/8vWZfNIXQV2GlwEpslrOkGqy+WaiiyE7Of7z9AvDILfBBBcJvJ/r8u980wAfQ==", "cpu": [ "x64" ], @@ -4161,9 +3025,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.28.tgz", - "integrity": "sha512-bCqh4uBT/59h3dWK1v91In6qzz8rKoWoFRxCtNQLIK4jP55K0U231ZK9oN7neZD6bzcOUeFvOGgcyMAgDfFWfA==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.26.tgz", + "integrity": "sha512-9TNXPIJqFynlAOrRD6tUQjMq7KApSklK3R/tXgIxc7Qx+lWu8hlDQ/kVPLpU7PWvMMwC/3hKBW+p5f+Tms1hmA==", "cpu": [ "arm64" ], @@ -4177,9 +3041,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.28.tgz", - "integrity": "sha512-XTHbHrksnrqK3JSJ2sbuMWvdJ6/G0roRpgyVTmNDfhTYPOwcVaL/mSrPGLwbksYUbq7ckwoKzrobhdxvQzPsDA==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.26.tgz", + "integrity": "sha512-9YngxNcG3177GYdsTum4V98Re+TlCeJEP4kEwEg9EagT5s3YejYdKwVAkAsJszzkXuyRDdnHUpYbTrPG6FiXrQ==", "cpu": [ "ia32" ], @@ -4193,9 +3057,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.7.28", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.28.tgz", - "integrity": "sha512-jyXeoq6nX8abiCy2EpporsC5ywNENs4ocYuvxo1LSxDktWN1E2MTXq3cdJcEWB2Vydxq0rDcsGyzkRPMzFhkZw==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.26.tgz", + "integrity": "sha512-VR+hzg9XqucgLjXxA13MtV5O3C0bK0ywtLIBw/+a+O+Oc6mxFWHtdUeXDbIi5AiPbn0fjgVJMqYnyjGyyX8u0w==", "cpu": [ "x64" ], @@ -4224,26 +3088,26 @@ } }, "node_modules/@tanstack/match-sorter-utils": { - "version": "8.8.4", - "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", - "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz", + "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==", "dependencies": { - "remove-accents": "0.4.2" + "remove-accents": "0.5.0" }, "engines": { "node": ">=12" }, "funding": { "type": "github", - "url": "https://github.com/sponsors/kentcdodds" + "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/react-table": { - "version": "8.10.7", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.10.7.tgz", - "integrity": "sha512-bXhjA7xsTcsW8JPTTYlUg/FuBpn8MNjiEPhkNhIGCUR6iRQM2+WEco4OBpvDeVcR9SE+bmWLzdfiY7bCbCSVuA==", + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", + "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", "dependencies": { - "@tanstack/table-core": "8.10.7" + "@tanstack/table-core": "8.20.5" }, "engines": { "node": ">=12" @@ -4253,16 +3117,16 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" + "react": ">=16.8", + "react-dom": ">=16.8" } }, "node_modules/@tanstack/react-virtual": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.1.tgz", - "integrity": "sha512-IFOFuRUTaiM/yibty9qQ9BfycQnYXIDHGP2+cU+0LrFFGNhVxCXSQnaY6wkX8uJVteFEBjUondX0Hmpp7TNcag==", + "version": "3.10.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.6.tgz", + "integrity": "sha512-xaSy6uUxB92O8mngHZ6CvbhGuqxQ5lIZWCBy+FjhrbHmOwc6BnOnKkYm2FsB1/BpKw/+FVctlMbEtI+F6I1aJg==", "dependencies": { - "@tanstack/virtual-core": "3.0.0" + "@tanstack/virtual-core": "3.10.6" }, "funding": { "type": "github", @@ -4274,9 +3138,9 @@ } }, "node_modules/@tanstack/table-core": { - "version": "8.10.7", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.10.7.tgz", - "integrity": "sha512-KQk5OMg5OH6rmbHZxuNROvdI+hKDIUxANaHlV+dPlNN7ED3qYQ/WkpY2qlXww1SIdeMlkIhpN/2L00rof0fXFw==", + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", "engines": { "node": ">=12" }, @@ -4286,9 +3150,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0.tgz", - "integrity": "sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==", + "version": "3.10.6", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.6.tgz", + "integrity": "sha512-1giLc4dzgEKLMx5pgKjL6HlG5fjZMgCjzlKAlpr7yoUtetVPELgER1NtephAI910nMwfPTHNyWKSFmJdHkz2Cw==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -4391,13 +3255,12 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz", - "integrity": "sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz", + "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.4.0", - "@babel/runtime": "^7.9.2", "aria-query": "^5.0.0", "chalk": "^3.0.0", "css.escape": "^1.5.1", @@ -4409,30 +3272,6 @@ "node": ">=14", "npm": ">=6", "yarn": ">=1" - }, - "peerDependencies": { - "@jest/globals": ">= 28", - "@types/bun": "latest", - "@types/jest": ">= 28", - "jest": ">= 28", - "vitest": ">= 0.32" - }, - "peerDependenciesMeta": { - "@jest/globals": { - "optional": true - }, - "@types/bun": { - "optional": true - }, - "@types/jest": { - "optional": true - }, - "jest": { - "optional": true - }, - "vitest": { - "optional": true - } } }, "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { @@ -4509,9 +3348,9 @@ } }, "node_modules/@testing-library/react": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.0.tgz", - "integrity": "sha512-guuxUKRWQ+FgNX0h0NS0FIq3Q3uLtWVpBzcLOggmfMoUpgBnzBzvLLd4fbm6yS8ydJd94cIfY4yP9qUQjM2KwQ==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.1.tgz", + "integrity": "sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5" @@ -4549,61 +3388,72 @@ } }, "node_modules/@total-typescript/ts-reset": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@total-typescript/ts-reset/-/ts-reset-0.5.1.tgz", - "integrity": "sha512-AqlrT8YA1o7Ff5wPfMOL0pvL+1X+sw60NN6CcOCqs658emD6RfiXhF7Gu9QcfKBH7ELY2nInLhKSCWVoNL70MQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@total-typescript/ts-reset/-/ts-reset-0.6.1.tgz", + "integrity": "sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==", "dev": true }, "node_modules/@turf/area": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@turf/area/-/area-6.5.0.tgz", - "integrity": "sha512-xCZdiuojokLbQ+29qR6qoMD89hv+JAgWjLrwSEWL+3JV8IXKeNFl6XkEJz9HGkVpnXvQKJoRz4/liT+8ZZ5Jyg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@turf/area/-/area-7.1.0.tgz", + "integrity": "sha512-w91FEe02/mQfMPRX2pXua48scFuKJ2dSVMF2XmJ6+BJfFiCPxp95I3+Org8+ZsYv93CDNKbf0oLNEPnuQdgs2g==", "dependencies": { - "@turf/helpers": "^6.5.0", - "@turf/meta": "^6.5.0" + "@turf/helpers": "^7.1.0", + "@turf/meta": "^7.1.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.6.2" }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/bbox": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", - "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-7.1.0.tgz", + "integrity": "sha512-PdWPz9tW86PD78vSZj2fiRaB8JhUHy6piSa/QXb83lucxPK+HTAdzlDQMTKj5okRCU8Ox/25IR2ep9T8NdopRA==", "dependencies": { - "@turf/helpers": "^6.5.0", - "@turf/meta": "^6.5.0" + "@turf/helpers": "^7.1.0", + "@turf/meta": "^7.1.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.6.2" }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/centroid": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-6.5.0.tgz", - "integrity": "sha512-MwE1oq5E3isewPprEClbfU5pXljIK/GUOMbn22UM3IFPDJX0KeoyLNwghszkdmFp/qMGL/M13MMWvU+GNLXP/A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-7.1.0.tgz", + "integrity": "sha512-1Y1b2l+ZB1CZ+ITjUCsGqC4/tSjwm/R4OUfDztVqyyCq/VvezkLmTNqvXTGXgfP0GXkpv68iCfxF5M7QdM5pJQ==", "dependencies": { - "@turf/helpers": "^6.5.0", - "@turf/meta": "^6.5.0" + "@turf/helpers": "^7.1.0", + "@turf/meta": "^7.1.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.6.2" }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/helpers": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", - "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.1.0.tgz", + "integrity": "sha512-dTeILEUVeNbaEeoZUOhxH5auv7WWlOShbx7QSd4s0T4Z0/iz90z9yaVCtZOLbU89umKotwKaJQltBNO9CzVgaQ==", + "dependencies": { + "@types/geojson": "^7946.0.10", + "tslib": "^2.6.2" + }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/meta": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", - "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-7.1.0.tgz", + "integrity": "sha512-ZgGpWWiKz797Fe8lfRj7HKCkGR+nSJ/5aKXMyofCvLSc2PuYJs/qyyifDPWjASQQCzseJ7AlF2Pc/XQ/3XkkuA==", "dependencies": { - "@turf/helpers": "^6.5.0" + "@turf/helpers": "^7.1.0", + "@types/geojson": "^7946.0.10" }, "funding": { "url": "https://opencollective.com/turf" @@ -4883,18 +3733,18 @@ } }, "node_modules/@types/debug": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.9.tgz", - "integrity": "sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "dev": true, "dependencies": { "@types/ms": "*" } }, "node_modules/@types/draft-convert": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/draft-convert/-/draft-convert-2.1.5.tgz", - "integrity": "sha512-gRJphK0P4yl/Px5iVZXJFB5ojq4aF91QL77vR0lBth9w66dnceOFn5lj3WoKdr5fXIaPKQoA4JRxRtdNoAr1TQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@types/draft-convert/-/draft-convert-2.1.8.tgz", + "integrity": "sha512-gzHXLnOhDqdDv3ieMCZjqbmP992MBBn1u9HrhrCQ4+sip2pFz7d+fXL4GqaBgFjM/A5+iSNRhkr4ZP4tMR3jWw==", "dev": true, "dependencies": { "@types/draft-js": "*", @@ -4902,9 +3752,9 @@ } }, "node_modules/@types/draft-js": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@types/draft-js/-/draft-js-0.11.13.tgz", - "integrity": "sha512-4cMlNWoKwcRwkA+GtCD53u5Dj78n9Z8hScB5rZYKw4Q3UZxh0h1Fm4ezLgGjAHkhsdrviWQm3MPuUi/jLOuq8A==", + "version": "0.11.18", + "resolved": "https://registry.npmjs.org/@types/draft-js/-/draft-js-0.11.18.tgz", + "integrity": "sha512-lP6yJ+EKv5tcG1dflWgDKeezdwBa8wJ7KkiNrrHqXuXhl/VGes1SKjEfKHDZqOz19KQbrAhFvNhDPWwnQXYZGQ==", "dev": true, "dependencies": { "@types/react": "*", @@ -4912,9 +3762,9 @@ } }, "node_modules/@types/draftjs-to-html": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@types/draftjs-to-html/-/draftjs-to-html-0.8.2.tgz", - "integrity": "sha512-bjxcYNJJ7RTvYFx+P557aHs8TIVqqzJsl1kkxt4r9r+StxIdsTcrHZ2fhGKJ+jZRLsD5o+LReDFGeqzNghrGHQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@types/draftjs-to-html/-/draftjs-to-html-0.8.4.tgz", + "integrity": "sha512-5FZcjFoJL57N/IttLCTCNI0krX+181oCl5hf76u3TqPkqBAphHrJAO9ReYesx9138kcObaYmpnWC2Yrqxoqd2Q==", "dev": true, "dependencies": { "@types/draft-js": "*" @@ -4923,14 +3773,20 @@ "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "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==", - "dev": true + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" + }, + "node_modules/@types/geojson-vt": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", + "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "dependencies": { + "@types/geojson": "*" + } }, "node_modules/@types/hast": { "version": "2.3.10", @@ -4950,16 +3806,15 @@ } }, "node_modules/@types/js-cookie": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.4.tgz", - "integrity": "sha512-vMMnFF+H5KYqdd/myCzq6wLDlPpteJK+jGFgBus3Da7lw+YsDmx2C8feGTzY2M3Fo823yON+HC2CL240j4OV+w==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", "dev": true }, "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==", - "dev": true + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/jsoneditor": { "version": "9.9.5", @@ -4972,11 +3827,26 @@ } }, "node_modules/@types/lodash": { - "version": "4.14.199", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", - "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", + "version": "4.17.9", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.9.tgz", + "integrity": "sha512-w9iWudx1XWOHW5lQRS9iKpK/XuRhnN+0T7HvdCCd802FYkT1AMTnxndJHGrNJwRoRHkslGr4S29tjm1cT7x/7w==", "dev": true }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==" + }, + "node_modules/@types/mapbox__vector-tile": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", + "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, "node_modules/@types/ms": { "version": "0.7.34", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", @@ -4984,16 +3854,23 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.16.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz", - "integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==", - "dev": true + "version": "22.7.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.3.tgz", + "integrity": "sha512-qXKfhXXqGTyBskvWEzJZPUxSslAiLaB6JGP1ic/XTH9ctGgzdgYguuLP1C601aRTSDNlLb0jbKqXjZ48GNraSA==", + "dependencies": { + "undici-types": "~6.19.2" + } }, "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==" }, + "node_modules/@types/pbf": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==" + }, "node_modules/@types/pikaday": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/@types/pikaday/-/pikaday-1.7.4.tgz", @@ -5014,37 +3891,35 @@ "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, "node_modules/@types/ramda": { - "version": "0.29.5", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.29.5.tgz", - "integrity": "sha512-oBBdRfoZoCl/aBIpBbct/uUHAbJ5i7vSOHK83SvH2Qr9ermYITRNKnEYgGJlnkagUY2cu8L2//Jq7o1355Go5A==", - "dev": true, + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", + "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", "dependencies": { - "types-ramda": "^0.29.4" + "types-ramda": "^0.30.1" } }, "node_modules/@types/react": { - "version": "18.2.24", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.24.tgz", - "integrity": "sha512-Ee0Jt4sbJxMu1iDcetZEIKQr99J1Zfb6D4F3qfUWoR1JpInkY1Wdg4WwCyBjL257D0+jGqSl1twBjV8iCaC0Aw==", + "version": "18.3.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.9.tgz", + "integrity": "sha512-+BpAVyTpJkNWWSSnaLBk6ePpHLOGJKnEQNbINNovPWzvEUyAe3e+/d494QdEh71RekM/qV7lw6jzf1HGrJyAtQ==", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-beautiful-dnd": { - "version": "13.1.5", - "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.5.tgz", - "integrity": "sha512-mzohmMtV48b0bXF2dP8188LzUv9HAGHKucOORYsd5Sqq830pJ4gseFyDDAH0TR4TeD1ceG9DxTQ0FOFbtCSy4Q==", + "version": "13.1.8", + "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz", + "integrity": "sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==", "dev": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-color": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.7.tgz", - "integrity": "sha512-IGZA7e8Oia0+Sb3/1KP0qTThGelZ9DRspfeLrFWQWv5vXHiYlJJQMC2kgQr75CtP4uL8/kvT8qBgrOVlxVoNTw==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.12.tgz", + "integrity": "sha512-pr3uKE3lSvf7GFo1Rn2K3QktiZQFFrSgSGJ/3iMvSOYWt2pPAJ97rVdVfhWxYJZ8prAEXzoP2XX//3qGSQgu7Q==", "dev": true, "dependencies": { "@types/react": "*", @@ -5061,18 +3936,18 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.8.tgz", - "integrity": "sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "devOptional": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-plotly.js": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@types/react-plotly.js/-/react-plotly.js-2.6.1.tgz", - "integrity": "sha512-vFJZRCC2Pav0NdrFm0grPMm9+67ejGZZglDBWqo+J6VFbB4CAatjoNiowfardznuujaaoDNoZ4MSCFwYyVk4aA==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@types/react-plotly.js/-/react-plotly.js-2.6.3.tgz", + "integrity": "sha512-HBQwyGuu/dGXDsWhnQrhH+xcJSsHvjkwfSRjP+YpOsCCWryIuXF78ZCBjpfgO3sCc0Jo8sYp4NOGtqT7Cn3epQ==", "dev": true, "dependencies": { "@types/plotly.js": "*", @@ -5108,18 +3983,18 @@ } }, "node_modules/@types/react-virtualized-auto-sizer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz", - "integrity": "sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.4.tgz", + "integrity": "sha512-nhYwlFiYa8M3S+O2T9QO/e1FQUYMr/wJENUdf/O0dhRi1RS/93rjrYQFYdbUqtdFySuhrtnEDX29P6eKOttY+A==", "dev": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-window": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.6.tgz", - "integrity": "sha512-AVJr3A5rIO9dQQu5TwTN0lP2c1RtuqyyZGCt7PGP8e5gUpn1PuQRMJb/u3UpdbwTHh4wbEi33UMW5NI0IXt1Mg==", + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", + "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", "dev": true, "dependencies": { "@types/react": "*" @@ -5134,30 +4009,24 @@ "@types/react": "*" } }, - "node_modules/@types/redux-logger": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/redux-logger/-/redux-logger-3.0.10.tgz", - "integrity": "sha512-wjcuptRVZQoWjbwKdLrEnit9A7TvW0HD6ZYNQGWRRCUBBux2RT8JxqwYemaS2fTQT3ebOvsszt2L/Hrylxh5PA==", - "dev": true, - "dependencies": { - "redux": "^4.0.0" - } - }, - "node_modules/@types/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==" - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/swagger-ui-react": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@types/swagger-ui-react/-/swagger-ui-react-4.18.1.tgz", - "integrity": "sha512-nYhNi+cyN78vve1/QY5PNKYzHYlDKETtXj+gQAhuoCRB+GxGT3MVJUj8WCdwYj4vF0s1j68qkLv/66DGe5ZlnA==", + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-react/-/swagger-ui-react-4.18.3.tgz", + "integrity": "sha512-Mo/R7IjDVwtiFPs84pWvh5pI9iyNGBjmfielxqbOh2Jv+8WVSDVe8Nu25kb5BOuV2xmGS3o33jr6nwDJMBcX+Q==", "dev": true, "dependencies": { "@types/react": "*" @@ -5184,22 +4053,22 @@ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, "node_modules/@types/uuid": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.4.tgz", - "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.14.0.tgz", - "integrity": "sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", + "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.14.0", - "@typescript-eslint/type-utils": "6.14.0", - "@typescript-eslint/utils": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/type-utils": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -5215,8 +4084,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5237,15 +4106,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.14.0.tgz", - "integrity": "sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.14.0", - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/typescript-estree": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4" }, "engines": { @@ -5256,7 +4125,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5265,13 +4134,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.14.0.tgz", - "integrity": "sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -5282,13 +4151,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.14.0.tgz", - "integrity": "sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", + "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.14.0", - "@typescript-eslint/utils": "6.14.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/utils": "7.2.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -5300,7 +4169,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5309,9 +4178,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.14.0.tgz", - "integrity": "sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -5322,16 +4191,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.14.0.tgz", - "integrity": "sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -5348,6 +4218,21 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -5361,17 +4246,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.14.0.tgz", - "integrity": "sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", + "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.14.0", - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/typescript-estree": "6.14.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", "semver": "^7.5.4" }, "engines": { @@ -5382,7 +4267,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/utils/node_modules/semver": { @@ -5398,12 +4283,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.14.0.tgz", - "integrity": "sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.14.0", + "@typescript-eslint/types": "7.2.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -5433,199 +4318,309 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.0.tgz", - "integrity": "sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.1.tgz", + "integrity": "sha512-md/A7A3c42oTT8JUHSqjP5uKTWJejzUW4jalpvs+rZ27gsURsMU8DEb+8Jf8C6Kj2gwfSHJqobDNBuoqlm0cFw==", "dev": true, "dependencies": { - "@ampproject/remapping": "^2.2.1", + "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.4", + "debug": "^4.3.6", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.4", - "istanbul-reports": "^3.1.6", - "magic-string": "^0.30.5", - "magicast": "^0.3.3", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0" + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.11", + "magicast": "^0.3.4", + "std-env": "^3.7.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.6.0" - } - }, - "node_modules/@vitest/expect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", - "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", - "dev": true, - "dependencies": { - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "chai": "^4.3.10" + "@vitest/browser": "2.1.1", + "vitest": "2.1.1" }, - "funding": { - "url": "https://opencollective.com/vitest" + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, - "node_modules/@vitest/runner": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", - "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", + "node_modules/@vitest/expect": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.1.tgz", + "integrity": "sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==", "dev": true, "dependencies": { - "@vitest/utils": "1.6.0", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "@vitest/spy": "2.1.1", + "@vitest/utils": "2.1.1", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", - "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "node_modules/@vitest/mocker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.1.tgz", + "integrity": "sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==", "dev": true, - "engines": { - "node": ">=12.20" + "dependencies": { + "@vitest/spy": "^2.1.0-beta.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.11" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/spy": "2.1.1", + "msw": "^2.3.5", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/@vitest/snapshot": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", - "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "node_modules/@vitest/pretty-format": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.1.tgz", + "integrity": "sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==", "dev": true, "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/@vitest/runner": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.1.tgz", + "integrity": "sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==", "dev": true, - "engines": { - "node": ">=10" + "dependencies": { + "@vitest/utils": "2.1.1", + "pathe": "^1.1.2" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "node_modules/@vitest/snapshot": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.1.tgz", + "integrity": "sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@vitest/pretty-format": "2.1.1", + "magic-string": "^0.30.11", + "pathe": "^1.1.2" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", - "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.1.tgz", + "integrity": "sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==", "dev": true, "dependencies": { - "tinyspy": "^2.2.0" + "tinyspy": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/ui": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.6.0.tgz", - "integrity": "sha512-k3Lyo+ONLOgylctiGovRKy7V4+dIN2yxstX3eY5cWFXH6WP+ooVX79YSyi0GagdTQzLmT43BF27T0s6dOIPBXA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-2.1.1.tgz", + "integrity": "sha512-IIxo2LkQDA+1TZdPLYPclzsXukBWd5dX2CKpGqH8CCt8Wh0ZuDn4+vuQ9qlppEju6/igDGzjWF/zyorfsf+nHg==", "dev": true, "dependencies": { - "@vitest/utils": "1.6.0", - "fast-glob": "^3.3.2", - "fflate": "^0.8.1", - "flatted": "^3.2.9", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "sirv": "^2.0.4" + "@vitest/utils": "2.1.1", + "fflate": "^0.8.2", + "flatted": "^3.3.1", + "pathe": "^1.1.2", + "sirv": "^2.0.4", + "tinyglobby": "^0.2.6", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.6.0" + "vitest": "2.1.1" } }, "node_modules/@vitest/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.1.tgz", + "integrity": "sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==", "dev": true, "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "2.1.1", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, - "node_modules/@vitest/utils/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "peer": true, "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" } }, "node_modules/@xobotyi/scrollbar-width": { @@ -5633,10 +4628,17 @@ "resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==" }, - "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==" + "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==", + "peer": true + }, + "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==", + "peer": true }, "node_modules/abs-svg-path": { "version": "0.1.1", @@ -5652,7 +4654,6 @@ "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -5660,6 +4661,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "peer": true, + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -5669,18 +4679,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/agent-base": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", @@ -5708,6 +4706,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peer": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, "node_modules/almost-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/almost-equal/-/almost-equal-1.1.0.tgz", @@ -5761,6 +4768,14 @@ "dequal": "^2.0.3" } }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-bounds": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-bounds/-/array-bounds-1.0.1.tgz", @@ -5837,6 +4852,26 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", @@ -5917,12 +4952,20 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "engines": { - "node": "*" + "node": ">=12" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "engines": { + "node": ">=0.10.0" } }, "node_modules/asynckit": { @@ -5930,14 +4973,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "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==", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/attr-accept": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", @@ -5970,11 +5005,11 @@ } }, "node_modules/axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -6016,6 +5051,14 @@ "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6079,6 +5122,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -6146,16 +5190,21 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/bytewise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", + "dependencies": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "node_modules/bytewise-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", + "dependencies": { + "typewise-core": "^1.2" } }, "node_modules/cac": { @@ -6171,6 +5220,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -6226,21 +5276,19 @@ "integrity": "sha512-+VsMpRr64jYgKq2IeFUNel3vCZH/IzS+iXSHxmUV3IUH5dXlC9xHz4AwtPZisDxZ5MWcuK0V+TXgPKFPiZnxzg==" }, "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/chalk": { @@ -6292,15 +5340,12 @@ } }, "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/chevrotain": { @@ -6318,18 +5363,13 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "optional": true }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "peer": true, "engines": { - "node": ">=8" + "node": ">=6.0" } }, "node_modules/clamp": { @@ -6343,9 +5383,9 @@ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "engines": { "node": ">=6" } @@ -6358,6 +5398,14 @@ "color-parse": "^1.3.8" } }, + "node_modules/color-alpha/node_modules/color-parse": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.4.3.tgz", + "integrity": "sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A==", + "dependencies": { + "color-name": "^1.0.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -6390,13 +5438,11 @@ } }, "node_modules/color-parse": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.3.8.tgz", - "integrity": "sha512-1Y79qFv0n1xair3lNMTNeoFvmc3nirMVBij24zbs1f13+7fPpQClMg5b4AuKXLt3szj7BRlHMCXHplkce6XlmA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-2.0.0.tgz", + "integrity": "sha512-g2Z+QnWsdHLppAbrpcFWo629kLOnOPtpxYV69GCqm92gqSgyXbzlfyN3MXs0412fPBkFmiuS+rXposgBgBa6Kg==", "dependencies": { - "color-name": "^1.0.0", - "defined": "^1.0.0", - "is-plain-obj": "^1.1.0" + "color-name": "^1.0.0" } }, "node_modules/color-rgba": { @@ -6409,6 +5455,14 @@ "color-space": "^1.14.6" } }, + "node_modules/color-rgba/node_modules/color-parse": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.4.3.tgz", + "integrity": "sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A==", + "dependencies": { + "color-name": "^1.0.0" + } + }, "node_modules/color-space": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/color-space/-/color-space-1.16.0.tgz", @@ -6455,7 +5509,8 @@ "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==", + "dev": true }, "node_modules/concat-stream": { "version": "1.6.2", @@ -6471,12 +5526,6 @@ "typedarray": "^0.0.6" } }, - "node_modules/confbox": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", - "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", - "dev": true - }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -6555,6 +5604,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6621,6 +5671,51 @@ "hyphenate-style-name": "^1.0.3" } }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/css-system-font-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz", @@ -6656,6 +5751,17 @@ "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==" }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cssstyle": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", @@ -7043,11 +6149,11 @@ } }, "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.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -7080,13 +6186,10 @@ } }, "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, "engines": { "node": ">=6" } @@ -7117,6 +6220,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==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -7185,15 +6289,6 @@ "node": ">=8" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7235,9 +6330,9 @@ } }, "node_modules/dompurify": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.6.tgz", - "integrity": "sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==" + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.7.tgz", + "integrity": "sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q==" }, "node_modules/draft-convert": { "version": "2.1.13", @@ -7319,6 +6414,12 @@ "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/electron-to-chromium": { "version": "1.5.29", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", @@ -7337,6 +6438,12 @@ "strongly-connected-components": "^1.0.1" } }, + "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==", + "dev": true + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -7345,6 +6452,19 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -7437,6 +6557,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -7448,6 +6569,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -7477,6 +6599,11 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==" + }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -7664,16 +6791,16 @@ } }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -7719,9 +6846,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -7731,20 +6858,21 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.0.tgz", - "integrity": "sha512-O2B1XLBJnUCRkggFzUQ+PBYJDit8iAgXdlu8ucolqGrbmOWPvttZQZX8d1sC0MbqDMSLs8SHSQxaNPRY1RQREg==", + "version": "48.10.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.10.0.tgz", + "integrity": "sha512-BEli0k8E0dzhJairAllwlkGnyYDZVKNn4WDmyKy+v6J5qGNuofjzxwNUi+55BOGmyO9mKBhqaidwGy+dxndn/Q==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.42.0", + "@es-joy/jsdoccomment": "~0.46.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", - "debug": "^4.3.4", + "debug": "^4.3.5", "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "is-builtin-module": "^3.2.1", - "semver": "^7.6.0", - "spdx-expression-parse": "^4.0.0" + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" }, "engines": { "node": ">=18" @@ -7775,23 +6903,24 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", - "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "synckit": "^0.9.1" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/prettier" + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", + "eslint-config-prettier": "*", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -7804,39 +6933,41 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", - "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "version": "7.37.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.0.tgz", + "integrity": "sha512-IHBePmfWH5lKhJnJ7WB1V+v/GolbB0rjS8XYVCSQCZKaQCAUhMoVoOEn1Ef8Z8Wf0a7l8KTJvuZg5/e4qrZ6nA==", "dev": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.12", + "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", + "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, "engines": { "node": ">=10" @@ -7846,9 +6977,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.5.tgz", - "integrity": "sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.12.tgz", + "integrity": "sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==", "dev": true, "peerDependencies": { "eslint": ">=7" @@ -8099,7 +7230,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -8149,41 +7279,6 @@ "node": ">=0.8.x" } }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -8201,6 +7296,25 @@ "type": "^2.7.2" } }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/falafel": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.2.5.tgz", @@ -8418,6 +7532,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -8445,14 +7560,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-yarn-workspace-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", - "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", - "dependencies": { - "micromatch": "^4.0.2" - } - }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -8576,6 +7683,22 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -8612,24 +7735,11 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "optional": true }, - "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==", - "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/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==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -8711,6 +7821,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -8753,6 +7864,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -8812,6 +7931,7 @@ "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", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -8839,10 +7959,17 @@ "node": ">=10.13.0" } }, + "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==", + "peer": true + }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8852,6 +7979,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8859,6 +7987,41 @@ "node": "*" } }, + "node_modules/global-prefix": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", + "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", + "dependencies": { + "ini": "^4.1.3", + "kind-of": "^6.0.3", + "which": "^4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -9096,6 +8259,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -9120,20 +8284,20 @@ "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==" }, "node_modules/handsontable": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/handsontable/-/handsontable-14.1.0.tgz", - "integrity": "sha512-7j6rvOTyNoz84u6ZjCluGhQp+VCVGi0bg2Os5cCTaStyDjyB5y/b0En8qWMyXMTcXzN+GeDn8eB+N6dqSYPnTg==", + "version": "14.5.0", + "resolved": "https://registry.npmjs.org/handsontable/-/handsontable-14.5.0.tgz", + "integrity": "sha512-fxCjDZS4z2LFwrmHXqtEKIcfrPxoD8+5AmX7r3pEYp2rjIhmtYKA45DFQ/3PP8PYvSFW8BGR58ZaKecMpGfJXg==", "dependencies": { "@handsontable/pikaday": "^1.0.0", "@types/pikaday": "1.7.4", - "core-js": "^3.31.1", + "core-js": "^3.37.0", "dompurify": "^2.1.1", - "moment": "2.29.4", + "moment": "2.30.1", "numbro": "2.1.2", "pikaday": "1.8.2" }, "optionalDependencies": { - "hyperformula": "^2.4.0" + "hyperformula": "^2.6.2" } }, "node_modules/has-bigints": { @@ -9173,6 +8337,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==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -9184,6 +8349,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -9195,6 +8361,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -9353,25 +8520,16 @@ "node": ">= 14" } }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, "node_modules/husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", "dev": true, "bin": { - "husky": "lib/bin.js" + "husky": "bin.js" }, "engines": { - "node": ">=14" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -9393,9 +8551,9 @@ "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==" }, "node_modules/i18next": { - "version": "23.5.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.5.1.tgz", - "integrity": "sha512-JelYzcaCoFDaa+Ysbfz2JsGAKkrHiMG6S61+HLBUEIPaF40WMwW9hCPymlQGrP+wWawKxKPuSuD71WZscCsWHg==", + "version": "23.15.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.15.1.tgz", + "integrity": "sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA==", "funding": [ { "type": "individual", @@ -9411,21 +8569,21 @@ } ], "dependencies": { - "@babel/runtime": "^7.22.5" + "@babel/runtime": "^7.23.2" } }, "node_modules/i18next-browser-languagedetector": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.1.0.tgz", - "integrity": "sha512-cr2k7u1XJJ4HTOjM9GyOMtbOA47RtUoWRAtt52z43r3AoMs2StYKyjS3URPhzHaf+mn10hY9dZWamga5WPQjhA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", + "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==", "dependencies": { - "@babel/runtime": "^7.19.4" + "@babel/runtime": "^7.23.2" } }, "node_modules/i18next-http-backend": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.4.2.tgz", - "integrity": "sha512-wKrgGcaFQ4EPjfzBTjzMU0rbFTYpa0S5gv9N/d8WBmWS64+IgJb7cHddMvV+tUkse7vUfco3eVs2lB+nJhPo3w==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.6.1.tgz", + "integrity": "sha512-rCilMAnlEQNeKOZY1+x8wLM5IpYOj10guGvEpeC59tNjj6MMreLIjIW8D1RclhD3ifLwn6d/Y9HEM1RUE6DSog==", "dependencies": { "cross-fetch": "4.0.0" } @@ -9449,6 +8607,17 @@ "node": ">=0.10.0" } }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -9478,9 +8647,9 @@ } }, "node_modules/immer": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", - "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -9532,6 +8701,7 @@ "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.", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -9543,10 +8713,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "optional": true + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/inline-style-prefixer": { "version": "7.0.1", @@ -9669,21 +8841,6 @@ "resolved": "https://registry.npmjs.org/is-browser/-/is-browser-2.1.0.tgz", "integrity": "sha512-F5rTJxDQ2sW81fcfOR1GnCXT6sVJC104fCyfj+mjpwNEwaPYSn5fte5jiHmBg3DHsIoL/l8Kvw5VN5SsTRcRFQ==" }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -9749,20 +8906,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", @@ -9814,6 +8957,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", @@ -9891,6 +9043,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -9995,18 +9148,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "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", @@ -10102,17 +9243,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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==", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -10121,7 +9251,8 @@ "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==", + "dev": true }, "node_modules/isobject": { "version": "3.0.1", @@ -10215,11 +9346,64 @@ "set-function-name": "^2.0.1" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/javascript-natural-sort": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/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==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -10267,31 +9451,31 @@ } }, "node_modules/jsdom": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz", - "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==", + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", "dev": true, "dependencies": { - "cssstyle": "^4.0.1", + "cssstyle": "^4.1.0", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.4", + "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.10", + "nwsapi": "^2.2.12", "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.0", + "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.4", + "tough-cookie": "^5.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", - "ws": "^8.17.0", + "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -10328,39 +9512,27 @@ "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==" }, - "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==" - }, - "node_modules/json-source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/json-source-map/-/json-source-map-0.6.1.tgz", - "integrity": "sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==" - }, - "node_modules/json-stable-stringify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", - "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", - "dependencies": { - "call-bind": "^1.0.5", - "isarray": "^2.0.5", - "jsonify": "^0.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, + "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==" + }, + "node_modules/json-source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/json-source-map/-/json-source-map-0.6.1.tgz", + "integrity": "sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -10373,38 +9545,19 @@ } }, "node_modules/jsoneditor": { - "version": "9.10.4", - "resolved": "https://registry.npmjs.org/jsoneditor/-/jsoneditor-9.10.4.tgz", - "integrity": "sha512-tr7dSARLHM65OQTE81zo5fQAjLzijLl+u/z+pcJaeaFzgkey59Gi8TDCYIejQ/plvm6RLVmuEeqgDhsQdayhiQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/jsoneditor/-/jsoneditor-10.1.0.tgz", + "integrity": "sha512-s+BP/x9Rx9ukNyTjeWCvIAGTEvXwCw6l4NX4mlxXfkK87F1ZgsesHbD9SsyZDLP27dvcFgr9H/j1jy/Bzzk07Q==", "dependencies": { - "ace-builds": "^1.31.1", + "ace-builds": "^1.35.0", "ajv": "^6.12.6", "javascript-natural-sort": "^0.7.1", "jmespath": "^0.16.0", "json-source-map": "^0.6.1", - "jsonrepair": "^3.4.0", + "jsonrepair": "^3.8.0", "mobius1-selectr": "^2.4.13", "picomodal": "^3.0.0", - "vanilla-picker": "^2.12.2" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", - "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "vanilla-picker": "^2.12.3" } }, "node_modules/jsonrepair": { @@ -10431,14 +9584,17 @@ } }, "node_modules/jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } }, "node_modules/kdbush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", - "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" }, "node_modules/keyv": { "version": "4.5.4", @@ -10449,12 +9605,12 @@ "json-buffer": "3.0.1" } }, - "node_modules/klaw-sync": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", - "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", - "dependencies": { - "graceful-fs": "^4.1.11" + "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==", + "engines": { + "node": ">=0.10.0" } }, "node_modules/levn": { @@ -10475,20 +9631,13 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, - "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", - "dev": true, - "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" - }, + "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==", + "peer": true, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "node": ">=6.11.5" } }, "node_modules/locate-path": { @@ -10547,9 +9696,9 @@ } }, "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", "dev": true, "dependencies": { "get-func-name": "^2.0.1" @@ -10650,11 +9799,12 @@ } }, "node_modules/mapbox-gl": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.10.1.tgz", - "integrity": "sha512-0aHt+lFUpYfvh0kMIqXqNXqoYMuhuAsMlw87TbhWrw78Tx2zfuPI0Lx31/YPUgJ+Ire0tzQ4JnuBL7acDNXmMg==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.13.3.tgz", + "integrity": "sha512-p8lJFEiqmEQlyv+DQxFAOG/XPWN0Wp7j/Psq93Zywz7qt9CcUKFYDBOoOEKzqe6gudHVJY8/Bhqw6VDpX2lSBg==", + "peer": true, "dependencies": { - "@mapbox/geojson-rewind": "^0.5.0", + "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/geojson-types": "^1.0.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/mapbox-gl-supported": "^1.5.0", @@ -10668,13 +9818,12 @@ "geojson-vt": "^3.2.1", "gl-matrix": "^3.2.1", "grid-index": "^1.1.0", - "minimist": "^1.2.5", "murmurhash-js": "^1.0.0", "pbf": "^3.2.1", "potpack": "^1.0.1", "quickselect": "^2.0.0", "rw": "^1.3.3", - "supercluster": "^7.0.0", + "supercluster": "^7.1.0", "tinyqueue": "^2.0.3", "vt-pbf": "^3.1.1" }, @@ -10682,6 +9831,89 @@ "node": ">=6.4.0" } }, + "node_modules/maplibre-gl": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.1.tgz", + "integrity": "sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/maplibre-gl-style-spec": "^20.3.1", + "@types/geojson": "^7946.0.14", + "@types/geojson-vt": "3.2.5", + "@types/mapbox__point-geometry": "^0.1.4", + "@types/mapbox__vector-tile": "^1.3.4", + "@types/pbf": "^3.0.5", + "@types/supercluster": "^7.1.3", + "earcut": "^3.0.0", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.3", + "global-prefix": "^4.0.0", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^3.3.0", + "potpack": "^2.0.0", + "quickselect": "^3.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0", + "vt-pbf": "^3.1.3" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "node_modules/maplibre-gl/node_modules/@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + }, + "node_modules/maplibre-gl/node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "node_modules/maplibre-gl/node_modules/earcut": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.0.tgz", + "integrity": "sha512-41Fs7Q/PLq1SDbqjsgcY7GA42T0jvaCNGXgGtsNdvg+Yv8eIu06bxv4/PoREkZ9nMDNwnUSG9OFB9+yv8eKhDg==" + }, + "node_modules/maplibre-gl/node_modules/geojson-vt": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", + "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==" + }, + "node_modules/maplibre-gl/node_modules/potpack": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", + "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==" + }, + "node_modules/maplibre-gl/node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==" + }, + "node_modules/maplibre-gl/node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "dependencies": { + "kdbush": "^4.0.2" + } + }, + "node_modules/maplibre-gl/node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" + }, "node_modules/marked": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", @@ -10700,13 +9932,13 @@ "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" }, "node_modules/material-react-table": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-2.0.5.tgz", - "integrity": "sha512-axRrqa/2QQ+AO3SiJbOtSyemlHX0S03X+IXW72z344d3LT+u/jsKiAmdWMLTN8ARScYMAN5NgrArujiLEmftSQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-3.0.1.tgz", + "integrity": "sha512-RP+bnpsOAH5j6zwP04u9HB37fyqbd6mVv9mkT4IUJC3e3gEqixZmkNdJMVM1ZVHoq7yIaM381xf22mpBVe0IaA==", "dependencies": { - "@tanstack/match-sorter-utils": "8.8.4", - "@tanstack/react-table": "8.10.7", - "@tanstack/react-virtual": "3.0.1", + "@tanstack/match-sorter-utils": "8.19.4", + "@tanstack/react-table": "8.20.5", + "@tanstack/react-virtual": "3.10.6", "highlight-words": "1.2.2" }, "engines": { @@ -10717,11 +9949,11 @@ "url": "https://github.com/sponsors/kevinvandy" }, "peerDependencies": { - "@emotion/react": ">=11.11", - "@emotion/styled": ">=11.11", - "@mui/icons-material": ">=5.11", - "@mui/material": ">=5.13", - "@mui/x-date-pickers": ">=6.15.0", + "@emotion/react": ">=11.13", + "@emotion/styled": ">=11.13", + "@mui/icons-material": ">=6", + "@mui/material": ">=6", + "@mui/x-date-pickers": ">=7.15", "react": ">=18.0", "react-dom": ">=18.0" } @@ -10748,7 +9980,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "peer": true }, "node_modules/merge2": { "version": "1.4.1", @@ -10763,6 +9995,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -10790,18 +10023,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -10856,33 +10077,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "optional": true }, - "node_modules/mlly": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", - "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", - "dev": true, - "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.1.1", - "ufo": "^1.5.3" - } - }, "node_modules/mobius1-selectr": { "version": "2.4.13", "resolved": "https://registry.npmjs.org/mobius1-selectr/-/mobius1-selectr-2.4.13.tgz", "integrity": "sha512-Mk9qDrvU44UUL0EBhbAA1phfQZ7aMZPjwtL7wkpiBzGh8dETGqfsh50mWoX9EkjDlkONlErWXArHCKfoxVg0Bw==" }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "engines": { "node": "*" } @@ -10925,9 +10143,9 @@ } }, "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==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mumath": { "version": "3.3.4", @@ -10977,7 +10195,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -11032,6 +10249,12 @@ "ms": "^2.1.1" } }, + "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==", + "peer": true + }, "node_modules/neotraverse": { "version": "0.6.18", "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", @@ -11185,33 +10408,6 @@ "node": ">=6" } }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/number-is-integer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-integer/-/number-is-integer-1.0.1.tgz", @@ -11235,9 +10431,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.12", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", - "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", "dev": true }, "node_modules/object-assign": { @@ -11264,6 +10460,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -11318,23 +10515,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.hasown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", - "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.omit": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz", @@ -11371,36 +10551,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/openapi-path-templating": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/openapi-path-templating/-/openapi-path-templating-1.6.0.tgz", @@ -11440,14 +10590,6 @@ "node": ">= 0.8.0" } }, - "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==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -11476,6 +10618,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11509,186 +10657,64 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-rect": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parse-rect/-/parse-rect-1.2.0.tgz", - "integrity": "sha512-4QZ6KYbnE6RTwg9E0HpLchUM9EZt6DnDxajFZZDSV4p/12ZJEvPO702DZpGvRYEPo00yKDys7jASi+/w7aO8LA==", - "dependencies": { - "pick-by-alias": "^1.2.0" - } - }, - "node_modules/parse-svg-path": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", - "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==" - }, - "node_modules/parse-unit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-unit/-/parse-unit-1.0.1.tgz", - "integrity": "sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg==" - }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/patch-package": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", - "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", - "dependencies": { - "@yarnpkg/lockfile": "^1.1.0", - "chalk": "^4.1.2", - "ci-info": "^3.7.0", - "cross-spawn": "^7.0.3", - "find-yarn-workspace-root": "^2.0.0", - "fs-extra": "^9.0.0", - "json-stable-stringify": "^1.0.2", - "klaw-sync": "^6.0.0", - "minimist": "^1.2.6", - "open": "^7.4.2", - "rimraf": "^2.6.3", - "semver": "^7.5.3", - "slash": "^2.0.0", - "tmp": "^0.0.33", - "yaml": "^2.2.2" - }, - "bin": { - "patch-package": "index.js" - }, - "engines": { - "node": ">=14", - "npm": ">5" - } - }, - "node_modules/patch-package/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/patch-package/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/patch-package/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==", + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, "dependencies": { - "color-name": "~1.1.4" + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" }, "engines": { - "node": ">=7.0.0" + "node": ">= 18" } }, - "node_modules/patch-package/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==" - }, - "node_modules/patch-package/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==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/patch-package/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/parse-rect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parse-rect/-/parse-rect-1.2.0.tgz", + "integrity": "sha512-4QZ6KYbnE6RTwg9E0HpLchUM9EZt6DnDxajFZZDSV4p/12ZJEvPO702DZpGvRYEPo00yKDys7jASi+/w7aO8LA==", "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "pick-by-alias": "^1.2.0" } }, - "node_modules/patch-package/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==" }, - "node_modules/patch-package/node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "engines": { - "node": ">=6" - } + "node_modules/parse-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-unit/-/parse-unit-1.0.1.tgz", + "integrity": "sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg==" }, - "node_modules/patch-package/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==", + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/patch-package/node_modules/yaml": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", - "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", - "bin": { - "yaml": "bin.mjs" + "entities": "^4.4.0" }, - "engines": { - "node": ">= 14" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/path-exists": { @@ -11703,6 +10729,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -11711,6 +10738,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "engines": { "node": ">=8" } @@ -11720,6 +10748,28 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "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==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -11735,12 +10785,12 @@ "dev": true }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/pbf": { @@ -11774,6 +10824,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -11791,34 +10842,26 @@ "resolved": "https://registry.npmjs.org/pikaday/-/pikaday-1.8.2.tgz", "integrity": "sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew==" }, - "node_modules/pkg-types": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.0.tgz", - "integrity": "sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==", - "dev": true, - "dependencies": { - "confbox": "^0.1.7", - "mlly": "^1.7.1", - "pathe": "^1.1.2" - } - }, "node_modules/plotly.js": { - "version": "2.26.1", - "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-2.26.1.tgz", - "integrity": "sha512-aZgY5NEbuwgsnbTNmMy3BXPkx/QK+wuSnnEWvEeeUnhEZK+fTKazx6zCsbImLPinOvQtTdOXH4LhqrTYcYd69g==", + "version": "2.35.2", + "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-2.35.2.tgz", + "integrity": "sha512-s0knlWzRvLQXxzf3JQ6qbm8FpwKuMjkr+6r04f8/yCEByAQ+I0jkUzY/hSGRGb+u7iljTh9hgpEiiJP90vjyeQ==", "dependencies": { - "@plotly/d3": "3.8.1", + "@plotly/d3": "3.8.2", "@plotly/d3-sankey": "0.7.2", "@plotly/d3-sankey-circular": "0.33.1", - "@turf/area": "^6.4.0", - "@turf/bbox": "^6.4.0", - "@turf/centroid": "^6.0.2", + "@plotly/mapbox-gl": "1.13.4", + "@turf/area": "^7.1.0", + "@turf/bbox": "^7.1.0", + "@turf/centroid": "^7.1.0", + "base64-arraybuffer": "^1.0.2", "canvas-fit": "^1.5.0", "color-alpha": "1.0.4", "color-normalize": "1.5.0", - "color-parse": "1.3.8", + "color-parse": "2.0.0", "color-rgba": "2.1.1", "country-regex": "^1.1.0", + "css-loader": "^7.1.2", "d3-force": "^1.2.1", "d3-format": "^1.4.5", "d3-geo": "^1.12.1", @@ -11829,26 +10872,26 @@ "d3-time-format": "^2.2.3", "fast-isnumeric": "^1.1.4", "gl-mat4": "^1.2.0", - "gl-text": "^1.3.1", - "glslify": "^7.1.1", + "gl-text": "^1.4.0", "has-hover": "^1.0.1", "has-passive-events": "^1.0.0", "is-mobile": "^4.0.0", - "mapbox-gl": "1.10.1", + "maplibre-gl": "^4.5.2", "mouse-change": "^1.4.0", "mouse-event-offset": "^3.0.2", "mouse-wheel": "^1.2.0", "native-promise-only": "^0.8.1", "parse-svg-path": "^0.1.2", "point-in-polygon": "^1.1.0", - "polybooljs": "^1.2.0", + "polybooljs": "^1.2.2", "probe-image-size": "^7.2.3", "regl": "npm:@plotly/regl@^2.1.2", "regl-error2d": "^2.0.12", - "regl-line2d": "^3.1.2", - "regl-scatter2d": "^3.2.9", + "regl-line2d": "^3.1.3", + "regl-scatter2d": "^3.3.1", "regl-splom": "^1.0.14", "strongly-connected-components": "^1.0.1", + "style-loader": "^4.0.0", "superscript-text": "^1.0.0", "svg-path-sdf": "^1.1.3", "tinycolor2": "^1.4.2", @@ -11892,7 +10935,6 @@ "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -11916,6 +10958,78 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-modules-extract-imports": { + "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==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "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==", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "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==" + }, "node_modules/potpack": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", @@ -11957,9 +11071,9 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -12094,12 +11208,6 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "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 - }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -12167,18 +11275,18 @@ "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" }, "node_modules/ramda": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", - "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", + "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/ramda" } }, "node_modules/ramda-adjunct": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-4.1.1.tgz", - "integrity": "sha512-BnCGsZybQZMDGram9y7RiryoRHS5uwx8YeGuUeDKuZuvK38XO6JJfmK85BwRWAKFA6pZ5nZBO/HBFtExVaf31w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", + "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", "engines": { "node": ">=0.10.3" }, @@ -12187,7 +11295,7 @@ "url": "https://opencollective.com/ramda-adjunct" }, "peerDependencies": { - "ramda": ">= 0.29.0" + "ramda": ">= 0.30.0" } }, "node_modules/randexp": { @@ -12225,6 +11333,12 @@ "rc": "cli.js" } }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "optional": true + }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -12235,9 +11349,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -12357,15 +11471,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-dropzone": { @@ -12397,18 +11511,18 @@ } }, "node_modules/react-hook-form": { - "version": "7.47.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.47.0.tgz", - "integrity": "sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==", + "version": "7.53.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.0.tgz", + "integrity": "sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==", "engines": { - "node": ">=12.22.0" + "node": ">=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/react-hook-form" }, "peerDependencies": { - "react": "^16.8.0 || ^17 || ^18" + "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "node_modules/react-html-attributes": { @@ -12420,11 +11534,11 @@ } }, "node_modules/react-i18next": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.2.2.tgz", - "integrity": "sha512-+nFUkbRByFwnrfDcYqvzBuaeZb+nACHx+fAWN/pZMddWOCJH5hoc21+Sa/N/Lqi6ne6/9wC/qRGOoQhJa6IkEQ==", + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", + "integrity": "sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==", "dependencies": { - "@babel/runtime": "^7.22.5", + "@babel/runtime": "^7.25.0", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { @@ -12656,9 +11770,9 @@ } }, "node_modules/react-use": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/react-use/-/react-use-17.4.0.tgz", - "integrity": "sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/react-use/-/react-use-17.5.1.tgz", + "integrity": "sha512-LG/uPEVRflLWMwi3j/sZqR00nF6JGqTTDblkXK2nzXsIvij06hXl1V/MZIlwj1OKIQUtlh1l9jK8gLsRyCQxMg==", "dependencies": { "@types/js-cookie": "^2.2.6", "@xobotyi/scrollbar-width": "^1.9.5", @@ -12666,7 +11780,7 @@ "fast-deep-equal": "^3.1.3", "fast-shallow-equal": "^1.0.0", "js-cookie": "^2.2.1", - "nano-css": "^5.3.1", + "nano-css": "^5.6.2", "react-universal-interface": "^0.6.2", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.1.0", @@ -12676,8 +11790,8 @@ "tslib": "^2.1.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "*", + "react-dom": "*" } }, "node_modules/react-use/node_modules/@types/js-cookie": { @@ -12691,18 +11805,18 @@ "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" }, "node_modules/react-virtualized-auto-sizer": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.20.tgz", - "integrity": "sha512-OdIyHwj4S4wyhbKHOKM1wLSj/UDXm839Z3Cvfg2a9j+He6yDa6i5p0qQvEiCnyQlGO/HyfSnigQwuxvYalaAXA==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz", + "integrity": "sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==", "peerDependencies": { - "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0-rc", - "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0-rc" + "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0", + "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0" } }, "node_modules/react-window": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.9.tgz", - "integrity": "sha512-+Eqx/fj1Aa5WnhRfj9dJg4VYATGwIUP2ItwItiJ6zboKWA6EX3lYDAXfGF2hyNqplEprhbtjbipiADEcwQ823Q==", + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", + "integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==", "dependencies": { "@babel/runtime": "^7.0.0", "memoize-one": ">=3.1.1 <6" @@ -12947,9 +12061,9 @@ } }, "node_modules/remove-accents": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", - "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" }, "node_modules/repeat-string": { "version": "1.6.1", @@ -13054,6 +12168,41 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", + "fsevents": "~2.3.2" + } + }, "node_modules/rrweb-cssom": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", @@ -13180,6 +12329,24 @@ "loose-envify": "^1.1.0" } }, + "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==", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/screenfull": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", @@ -13213,10 +12380,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "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==", + "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -13252,6 +12429,28 @@ "node": ">=6.9" } }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -13278,6 +12477,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -13289,6 +12489,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "engines": { "node": ">=8" } @@ -13411,6 +12612,52 @@ "node": ">=8" } }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true + }, + "node_modules/sort-asc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", + "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", + "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", + "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", + "dependencies": { + "bytewise": "^1.1.0", + "get-value": "^2.0.2", + "is-extendable": "^0.1.1", + "sort-asc": "^0.2.0", + "sort-desc": "^0.2.0", + "union-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -13423,7 +12670,25 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "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==", + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13453,11 +12718,34 @@ "spdx-license-ids": "^3.0.0" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.20", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", - "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", - "dev": true + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "dev": true + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } }, "node_modules/split.js": { "version": "1.6.5", @@ -13584,6 +12872,71 @@ "parenthesis": "^3.1.5" } }, + "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==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/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==", + "dev": true + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -13610,6 +12963,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -13671,16 +13034,17 @@ "node": ">=8" } }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "engines": { - "node": ">=12" + "dependencies": { + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, "node_modules/strip-indent": { @@ -13707,29 +13071,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", - "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", - "dev": true, - "dependencies": { - "js-tokens": "^9.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", - "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", - "dev": true - }, "node_modules/strongly-connected-components": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strongly-connected-components/-/strongly-connected-components-1.0.1.tgz", "integrity": "sha512-i0TFx4wPcO0FwX+4RkLJi1MxmcTv90jNZgxMu9XRnMXMeFUY1VJlIoXpZunPUvUUqbCT1pg5PEkFqqpcaElNaA==" }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -13743,6 +13104,11 @@ "kdbush": "^3.0.0" } }, + "node_modules/supercluster/node_modules/kdbush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + }, "node_modules/superscript-text": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/superscript-text/-/superscript-text-1.0.0.tgz", @@ -13838,48 +13204,23 @@ "node": ">=0.10.0" } }, - "node_modules/swagger-client/node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/swagger-client/node_modules/ramda-adjunct": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", - "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.30.0" - } - }, "node_modules/swagger-ui-react": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-5.9.0.tgz", - "integrity": "sha512-j45ceuGHMRmI8nhOaG71VeQwrPutFHDq6QhgrxOmf4BRMOdOQgVY1POQY9ksnXZtskbD9J2NHURs4BLEDIs8gA==", + "version": "5.17.14", + "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-5.17.14.tgz", + "integrity": "sha512-mCXerZrbcn4ftPYifUF0+iKIRTHoVCv0HcJc/sXl9nCe3oeWdsjmOWVqKabzzAkAa0NwsbKNJFv2UL/Ivnf6VQ==", "dependencies": { - "@babel/runtime-corejs3": "^7.23.1", - "@braintree/sanitize-url": "=6.0.4", + "@babel/runtime-corejs3": "^7.24.5", + "@braintree/sanitize-url": "=7.0.2", "base64-js": "^1.5.1", - "classnames": "^2.3.1", + "classnames": "^2.5.1", "css.escape": "1.5.1", "deep-extend": "0.6.0", - "dompurify": "=3.0.6", + "dompurify": "=3.1.4", "ieee754": "^1.2.1", "immutable": "^3.x.x", "js-file-download": "^0.4.12", "js-yaml": "=4.1.0", "lodash": "^4.17.21", - "patch-package": "^8.0.0", "prop-types": "^15.8.1", "randexp": "^0.5.3", "randombytes": "^2.1.0", @@ -13888,29 +13229,29 @@ "react-immutable-proptypes": "2.2.0", "react-immutable-pure-component": "^2.2.0", "react-inspector": "^6.0.1", - "react-redux": "^8.1.2", + "react-redux": "^9.1.2", "react-syntax-highlighter": "^15.5.0", - "redux": "^4.1.2", + "redux": "^5.0.1", "redux-immutable": "^4.0.0", "remarkable": "^2.0.1", - "reselect": "^4.1.8", + "reselect": "^5.1.0", "serialize-error": "^8.1.0", "sha.js": "^2.4.11", - "swagger-client": "^3.22.3", + "swagger-client": "^3.28.1", "url-parse": "^1.5.10", "xml": "=1.0.1", "xml-but-prettier": "^1.0.1", "zenscroll": "^4.0.2" }, "peerDependencies": { - "react": ">=17.0.0", - "react-dom": ">=17.0.0" + "react": ">=16.8.0 <19", + "react-dom": ">=16.8.0 <19" } }, "node_modules/swagger-ui-react/node_modules/dompurify": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.6.tgz", - "integrity": "sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w==" + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.4.tgz", + "integrity": "sha512-2gnshi6OshmuKil8rMZuQCGiUF3cUxHY3NGDzUAdUx/NPEe5DVnO8BDoAQouvgwnx0R/+a6jUn36Z0FSdq8vww==" }, "node_modules/swagger-ui-react/node_modules/immutable": { "version": "3.8.2", @@ -13920,6 +13261,33 @@ "node": ">=0.10.0" } }, + "node_modules/swagger-ui-react/node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/swagger-ui-react/node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, "node_modules/swagger-ui-react/node_modules/redux-immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", @@ -13928,6 +13296,11 @@ "immutable": "^3.8.1 || ^4.0.0-rc.1" } }, + "node_modules/swagger-ui-react/node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -13935,9 +13308,9 @@ "dev": true }, "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", "dev": true, "dependencies": { "@pkgr/core": "^0.1.0", @@ -13950,6 +13323,15 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -14003,40 +13385,90 @@ "node": ">= 6" } }, - "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==", - "dev": true, + "node_modules/terser": { + "version": "5.34.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", + "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", + "peer": true, "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "node_modules/terser-webpack-plugin": { + "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==", + "peer": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" }, "engines": { - "node": "*" + "node": ">=18" + } + }, + "node_modules/test-exclude/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, + "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/text-table": { @@ -14084,13 +13516,58 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" }, + "node_modules/tinyexec": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", + "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.6.tgz", + "integrity": "sha512-NbBoFBpqfcgd1tCiO8Lkfdk+xrA7mlLR9zgvZcZWQQwU63XAfUePyd6wZBaU93Hqw347lHnwFzttAkemHzzz4g==", + "dev": true, + "dependencies": { + "fdir": "^6.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.3.0.tgz", + "integrity": "sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/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, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", "dev": true, "engines": { - "node": ">=14.0.0" + "node": "^18.0.0 || >=20.0.0" } }, "node_modules/tinyqueue": { @@ -14098,26 +13575,42 @@ "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "engines": { "node": ">=14.0.0" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "node_modules/tldts": { + "version": "6.1.48", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.48.tgz", + "integrity": "sha512-SPbnh1zaSzi/OsmHb1vrPNnYuwJbdWjwo5TbBYYMlTtH3/1DSb41t8bcSxkwDmmbG2q6VLPVvQc7Yf23T+1EEw==", + "dev": true, "dependencies": { - "os-tmpdir": "~1.0.2" + "tldts-core": "^6.1.48" }, - "engines": { - "node": ">=0.6.0" + "bin": { + "tldts": "bin/cli.js" } }, + "node_modules/tldts-core": { + "version": "6.1.48", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.48.tgz", + "integrity": "sha512-3gD9iKn/n2UuFH1uilBviK9gvTNT6iYwdqrj1Vr5mh8FuelvpRNaYVH4pNYqUgOGU4aAdL9X35eLuuj0gRsx+A==", + "dev": true + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -14143,6 +13636,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==", + "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -14178,27 +13672,15 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", "dev": true, "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" + "node": ">=16" } }, "node_modules/tr46": { @@ -14316,15 +13798,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -14424,18 +13897,17 @@ } }, "node_modules/types-ramda": { - "version": "0.29.10", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.29.10.tgz", - "integrity": "sha512-5PJiW/eiTPyXXBYGZOYGezMl6qj7keBiZheRwfjJZY26QPHsNrjfJnz0mru6oeqqoTHOni893Jfd6zyUXfQRWg==", - "dev": true, + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", + "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", "dependencies": { "ts-toolbelt": "^9.6.0" } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -14445,6 +13917,19 @@ "node": ">=14.17" } }, + "node_modules/typewise": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", + "dependencies": { + "typewise-core": "^1.2.0" + } + }, + "node_modules/typewise-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" + }, "node_modules/ua-parser-js": { "version": "0.7.39", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz", @@ -14470,12 +13955,6 @@ "node": "*" } }, - "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", - "dev": true - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -14491,12 +13970,31 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", "engines": { - "node": ">= 10.0.0" + "node": ">=0.10.0" } }, "node_modules/unquote": { @@ -14510,9 +14008,9 @@ "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==" }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -14528,8 +14026,8 @@ } ], "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -14607,402 +14105,151 @@ }, "node_modules/use-sync-external-store": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", - "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/use-undo": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/use-undo/-/use-undo-1.1.1.tgz", - "integrity": "sha512-2NNpJv7TDQpyETgRTG5I/CsABX5nhExpx8PEhWiDsnW9C5BA6Xnn8G9u/lPeg3+JsBrBtUx8/5bMbGmpnWz1Cw==", - "peerDependencies": { - "react": ">=16.8.6", - "react-dom": ">=16.8.6" - } - }, - "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==" - }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vanilla-picker": { - "version": "2.12.3", - "resolved": "https://registry.npmjs.org/vanilla-picker/-/vanilla-picker-2.12.3.tgz", - "integrity": "sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==", - "dependencies": { - "@sphinxxxx/color-conversion": "^2.2.2" - } - }, - "node_modules/vite": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", - "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", - "dev": true, - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", - "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", - "dev": true, - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite/node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.0.tgz", - "integrity": "sha512-/IZQvg6ZR0tAkEi4tdXOraQoWeJy9gbQ/cx4I7k9dJaCk9qrXEcdouxRVz5kZXt5C2bQ9pILoAA+KB4C/d3pfw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.0.tgz", - "integrity": "sha512-ETHi4bxrYnvOtXeM7d4V4kZWixib2jddFacJjsOjwbgYSRsyXYtZHC4ht134OsslPIcnkqT+TKV4eU8rNBKyyQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.0.tgz", - "integrity": "sha512-ZWgARzhSKE+gVUX7QWaECoRQsPwaD8ZR0Oxb3aUpzdErTvlEadfQpORPXkKSdKbFci9v8MJfkTtoEHnnW9Ulng==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.0.tgz", - "integrity": "sha512-h0ZAtOfHyio8Az6cwIGS+nHUfRMWBDO5jXB8PQCARVF6Na/G6XS2SFxDl8Oem+S5ZsHQgtsI7RT4JQnI1qrlaw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.0.tgz", - "integrity": "sha512-9pxQJSPwFsVi0ttOmqLY4JJ9pg9t1gKhK0JDbV1yUEETSx55fdyCjt39eBQ54OQCzAF0nVGO6LfEH1KnCPvelA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.0.tgz", - "integrity": "sha512-YJ5Ku5BmNJZb58A4qSEo3JlIG4d3G2lWyBi13ABlXzO41SsdnUKi3HQHe83VpwBVG4jHFTW65jOQb8qyoR+qzg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.0.tgz", - "integrity": "sha512-U4G4u7f+QCqHlVg1Nlx+qapZy+QoG+NV6ux+upo/T7arNGwKvKP2kmGM4W5QTbdewWFgudQxi3kDNST9GT1/mg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.0.tgz", - "integrity": "sha512-aQpNlKmx3amwkA3a5J6nlXSahE1ijl0L9KuIjVOUhfOh7uw2S4piR3mtpxpRtbnK809SBtyPsM9q15CPTsY7HQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.0.tgz", - "integrity": "sha512-9fx6Zj/7vve/Fp4iexUFRKb5+RjLCff6YTRQl4CoDhdMfDoobWmhAxQWV3NfShMzQk1Q/iCnageFyGfqnsmeqQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.0.tgz", - "integrity": "sha512-VWQiCcN7zBgZYLjndIEh5tamtnKg5TGxyZPWcN9zBtXBwfcGSZ5cHSdQZfQH/GB4uRxk0D3VYbOEe/chJhPGLQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.0.tgz", - "integrity": "sha512-EHmPnPWvyYqncObwqrosb/CpH3GOjE76vWVs0g4hWsDRUVhg61hBmlVg5TPXqF+g+PvIbqkC7i3h8wbn4Gp2Fg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.0.tgz", - "integrity": "sha512-tsSWy3YQzmpjDKnQ1Vcpy3p9Z+kMFbSIesCdMNgLizDWFhrLZIoN21JSq01g+MZMDFF+Y1+4zxgrlqPjid5ohg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.0.tgz", - "integrity": "sha512-anr1Y11uPOQrpuU8XOikY5lH4Qu94oS6j0xrulHk3NkLDq19MlX8Ng/pVipjxBJ9a2l3+F39REZYyWQFkZ4/fw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/vite/node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.0.tgz", - "integrity": "sha512-7LB+Bh+Ut7cfmO0m244/asvtIGQr5pG5Rvjz/l1Rnz1kDzM02pSX9jPaS0p+90H5I1x4d1FkCew+B7MOnoatNw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } }, - "node_modules/vite/node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.0.tgz", - "integrity": "sha512-+3qZ4rer7t/QsC5JwMpcvCVPRcJt1cJrYS/TMJZzXIJbxWFQEVhrIc26IhB+5Z9fT9umfVc+Es2mOZgl+7jdJQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] + "node_modules/use-undo": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/use-undo/-/use-undo-1.1.1.tgz", + "integrity": "sha512-2NNpJv7TDQpyETgRTG5I/CsABX5nhExpx8PEhWiDsnW9C5BA6Xnn8G9u/lPeg3+JsBrBtUx8/5bMbGmpnWz1Cw==", + "peerDependencies": { + "react": ">=16.8.6", + "react-dom": ">=16.8.6" + } }, - "node_modules/vite/node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.0.tgz", - "integrity": "sha512-YdicNOSJONVx/vuPkgPTyRoAPx3GbknBZRCOUkK84FJ/YTfs/F0vl/YsMscrB6Y177d+yDRcj+JWMPMCgshwrA==", - "cpu": [ - "x64" + "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==" + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] + "bin": { + "uuid": "dist/bin/uuid" + } }, - "node_modules/vite/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "node_modules/vanilla-picker": { + "version": "2.12.3", + "resolved": "https://registry.npmjs.org/vanilla-picker/-/vanilla-picker-2.12.3.tgz", + "integrity": "sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==", + "dependencies": { + "@sphinxxxx/color-conversion": "^2.2.2" + } }, - "node_modules/vite/node_modules/rollup": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.0.tgz", - "integrity": "sha512-W21MUIFPZ4+O2Je/EU+GP3iz7PH4pVPUXSbEZdatQnxo29+3rsUjgrJmzuAZU24z7yRAnFN6ukxeAhZh/c7hzg==", + "node_modules/vite": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, "dependencies": { - "@types/estree": "1.0.5" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { - "rollup": "dist/bin/rollup" + "vite": "bin/vite.js" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.0", - "@rollup/rollup-android-arm64": "4.22.0", - "@rollup/rollup-darwin-arm64": "4.22.0", - "@rollup/rollup-darwin-x64": "4.22.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.0", - "@rollup/rollup-linux-arm-musleabihf": "4.22.0", - "@rollup/rollup-linux-arm64-gnu": "4.22.0", - "@rollup/rollup-linux-arm64-musl": "4.22.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.0", - "@rollup/rollup-linux-riscv64-gnu": "4.22.0", - "@rollup/rollup-linux-s390x-gnu": "4.22.0", - "@rollup/rollup-linux-x64-gnu": "4.22.0", - "@rollup/rollup-linux-x64-musl": "4.22.0", - "@rollup/rollup-win32-arm64-msvc": "4.22.0", - "@rollup/rollup-win32-ia32-msvc": "4.22.0", - "@rollup/rollup-win32-x64-msvc": "4.22.0", - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } } }, - "node_modules/vitest": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", - "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", + "node_modules/vite-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.1.tgz", + "integrity": "sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==", "dev": true, "dependencies": { - "@vitest/expect": "1.6.0", - "@vitest/runner": "1.6.0", - "@vitest/snapshot": "1.6.0", - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", + "cac": "^6.7.14", + "debug": "^4.3.6", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.1.tgz", + "integrity": "sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==", + "dev": true, + "dependencies": { + "@vitest/expect": "2.1.1", + "@vitest/mocker": "2.1.1", + "@vitest/pretty-format": "^2.1.1", + "@vitest/runner": "2.1.1", + "@vitest/snapshot": "2.1.1", + "@vitest/spy": "2.1.1", + "@vitest/utils": "2.1.1", + "chai": "^5.1.1", + "debug": "^4.3.6", + "magic-string": "^0.30.11", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "1.6.0", - "why-is-node-running": "^2.2.2" + "vite-node": "2.1.1", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" @@ -15016,8 +14263,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.0", - "@vitest/ui": "1.6.0", + "@vitest/browser": "2.1.1", + "@vitest/ui": "2.1.1", "happy-dom": "*", "jsdom": "*" }, @@ -15072,6 +14319,19 @@ "node": ">=18" } }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/weak-map": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.8.tgz", @@ -15108,6 +14368,83 @@ "node": ">=12" } }, + "node_modules/webpack": { + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "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==", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -15158,6 +14495,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -15280,6 +14618,133 @@ "object-assign": "^4.1.0" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "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==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/webapp/package.json b/webapp/package.json index 3ee3e81d4e..da78848498 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -14,46 +14,46 @@ "test:ui": "vitest --ui" }, "dependencies": { - "@emotion/react": "11.11.1", - "@emotion/styled": "11.11.0", + "@emotion/react": "11.13.3", + "@emotion/styled": "11.13.0", "@glideapps/glide-data-grid": "6.0.3", - "@handsontable/react": "14.1.0", - "@mui/icons-material": "5.16.1", - "@mui/lab": "5.0.0-alpha.172", - "@mui/material": "5.16.1", - "@mui/x-tree-view": "7.10.0", + "@handsontable/react": "14.5.0", + "@mui/icons-material": "6.1.1", + "@mui/lab": "6.0.0-beta.10", + "@mui/material": "6.1.1", + "@mui/x-tree-view": "7.18.0", "@reduxjs/toolkit": "1.9.6", - "axios": "1.5.1", - "clsx": "2.0.0", + "axios": "1.7.7", + "clsx": "2.1.1", "d3": "5.16.0", - "debug": "4.3.4", + "debug": "4.3.7", "draft-convert": "2.1.13", "draft-js": "0.11.7", "draftjs-to-html": "0.9.1", - "handsontable": "14.1.0", + "handsontable": "14.5.0", "hoist-non-react-statics": "3.3.2", - "i18next": "23.5.1", - "i18next-browser-languagedetector": "7.1.0", - "i18next-http-backend": "2.4.2", - "immer": "10.0.3", + "i18next": "23.15.1", + "i18next-browser-languagedetector": "8.0.0", + "i18next-http-backend": "2.6.1", + "immer": "10.1.1", "js-cookie": "3.0.5", - "jsoneditor": "9.10.4", - "jwt-decode": "3.1.2", + "jsoneditor": "10.1.0", + "jwt-decode": "4.0.0", "lodash": "4.17.21", - "material-react-table": "2.0.5", - "moment": "2.29.4", + "material-react-table": "3.0.1", + "moment": "2.30.1", "notistack": "3.0.1", - "plotly.js": "2.26.1", - "ramda": "0.29.0", - "ramda-adjunct": "4.1.1", - "react": "18.2.0", + "plotly.js": "2.35.2", + "ramda": "0.30.1", + "ramda-adjunct": "5.1.0", + "react": "18.3.1", "react-beautiful-dnd": "13.1.1", "react-color": "2.19.3", "react-d3-graph": "2.6.0", - "react-dom": "18.2.0", + "react-dom": "18.3.1", "react-dropzone": "14.2.3", - "react-hook-form": "7.47.0", - "react-i18next": "13.2.2", + "react-hook-form": "7.53.0", + "react-i18next": "15.0.2", "react-json-view": "1.21.3", "react-plotly.js": "2.6.0", "react-redux": "8.1.3", @@ -61,78 +61,74 @@ "react-router-dom": "6.3.0", "react-split": "2.0.14", "react-syntax-highlighter": "15.5.0", - "react-use": "17.4.0", - "react-virtualized-auto-sizer": "1.0.20", - "react-window": "1.8.9", + "react-use": "17.5.1", + "react-virtualized-auto-sizer": "1.0.24", + "react-window": "1.8.10", "redux": "4.2.1", "redux-thunk": "2.4.2", - "swagger-ui-react": "5.9.0", + "swagger-ui-react": "5.17.14", "ts-toolbelt": "9.6.0", "use-undo": "1.1.1", - "uuid": "9.0.1", + "uuid": "10.0.0", "xml-js": "1.6.11" }, "devDependencies": { - "@testing-library/jest-dom": "6.4.6", - "@testing-library/react": "16.0.0", + "@testing-library/jest-dom": "6.5.0", + "@testing-library/react": "16.0.1", "@testing-library/user-event": "14.5.2", - "@total-typescript/ts-reset": "0.5.1", + "@total-typescript/ts-reset": "0.6.1", "@types/d3": "5.16.0", - "@types/debug": "4.1.9", - "@types/draft-convert": "2.1.5", - "@types/draft-js": "0.11.13", - "@types/draftjs-to-html": "0.8.2", - "@types/js-cookie": "3.0.4", + "@types/debug": "4.1.12", + "@types/draft-convert": "2.1.8", + "@types/draft-js": "0.11.18", + "@types/draftjs-to-html": "0.8.4", + "@types/js-cookie": "3.0.6", "@types/jsoneditor": "9.9.5", - "@types/lodash": "4.14.199", - "@types/node": "18.16.1", - "@types/ramda": "0.29.5", - "@types/react": "18.2.24", - "@types/react-beautiful-dnd": "13.1.5", - "@types/react-color": "3.0.7", + "@types/lodash": "4.17.9", + "@types/node": "22.7.3", + "@types/ramda": "0.30.2", + "@types/react": "18.3.9", + "@types/react-beautiful-dnd": "13.1.8", + "@types/react-color": "3.0.12", "@types/react-d3-graph": "2.6.5", - "@types/react-dom": "18.2.8", - "@types/react-plotly.js": "2.6.1", + "@types/react-dom": "18.3.0", + "@types/react-plotly.js": "2.6.3", "@types/react-syntax-highlighter": "15.5.13", - "@types/react-virtualized-auto-sizer": "1.0.1", - "@types/react-window": "1.8.6", - "@types/redux-logger": "3.0.10", - "@types/swagger-ui-react": "4.18.1", + "@types/react-virtualized-auto-sizer": "1.0.4", + "@types/react-window": "1.8.8", + "@types/swagger-ui-react": "4.18.3", "@types/testing-library__jest-dom": "6.0.0", - "@types/uuid": "9.0.4", - "@typescript-eslint/eslint-plugin": "6.14.0", - "@typescript-eslint/parser": "6.14.0", + "@types/uuid": "10.0.0", + "@typescript-eslint/eslint-plugin": "7.2.0", + "@typescript-eslint/parser": "7.2.0", "@vitejs/plugin-react-swc": "3.7.0", - "@vitest/coverage-v8": "1.6.0", - "@vitest/ui": "1.6.0", - "eslint": "8.55.0", - "eslint-config-prettier": "9.0.0", - "eslint-plugin-jsdoc": "48.2.0", + "@vitest/coverage-v8": "2.1.1", + "@vitest/ui": "2.1.1", + "eslint": "8.57.1", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-jsdoc": "48.10.0", "eslint-plugin-license-header": "0.6.1", - "eslint-plugin-prettier": "5.0.0", - "eslint-plugin-react": "7.33.2", - "eslint-plugin-react-hooks": "4.6.0", - "eslint-plugin-react-refresh": "0.4.5", - "husky": "8.0.3", - "jsdom": "24.1.0", - "prettier": "3.0.3", - "typescript": "5.2.2", + "eslint-plugin-prettier": "5.2.1", + "eslint-plugin-react": "7.37.0", + "eslint-plugin-react-hooks": "4.6.2", + "eslint-plugin-react-refresh": "0.4.12", + "husky": "9.1.6", + "jsdom": "25.0.1", + "prettier": "3.3.3", + "typescript": "5.4.5", "vite": "5.4.8", - "vitest": "1.6.0" + "vitest": "2.1.1" }, "engines": { "node": "18.16.1" }, "overrides": { "react-d3-graph": { - "react": "18.2.0" + "react": "18.3.1" }, "react-json-view": { - "react": "18.2.0", - "react-dom": "18.2.0" - }, - "vite@5.4.8": { - "rollup": "4.22.0" + "react": "18.3.1", + "react-dom": "18.3.1" } } } diff --git a/webapp/src/components/App/Data/index.tsx b/webapp/src/components/App/Data/index.tsx index ea35d174f2..9055a3e2b4 100644 --- a/webapp/src/components/App/Data/index.tsx +++ b/webapp/src/components/App/Data/index.tsx @@ -188,9 +188,10 @@ function Data() { lineHeight: 1.334, }} > - {`Matrices - ${dataList.find( - (item) => item.id === selectedItem, - )?.name}`} + {`Matrices - ${ + dataList.find((item) => item.id === selectedItem) + ?.name + }`} @@ -237,9 +238,9 @@ function Data() { alignItems: "center", }} > - {`Matrices - ${dataList.find( - (item) => item.id === selectedItem, - )?.name}`} + {`Matrices - ${ + dataList.find((item) => item.id === selectedItem)?.name + }`} ) } diff --git a/webapp/src/components/App/Settings/Groups/Header.tsx b/webapp/src/components/App/Settings/Groups/Header.tsx index 509763f096..f73f783ef9 100644 --- a/webapp/src/components/App/Settings/Groups/Header.tsx +++ b/webapp/src/components/App/Settings/Groups/Header.tsx @@ -22,20 +22,12 @@ import { isAuthUserAdmin } from "../../../../redux/selectors"; import useAppSelector from "../../../../redux/hooks/useAppSelector"; import SearchFE from "../../../common/fieldEditors/SearchFE"; -/** - * Types - */ - interface Props { setSearchValue: (v: string) => void; addGroup: (user: GroupDetailsDTO) => void; reloadFetchGroups: () => void; } -/** - * Component - */ - function Header(props: Props) { const { setSearchValue, addGroup, reloadFetchGroups } = props; const { t } = useTranslation(); diff --git a/webapp/src/components/App/Settings/Groups/dialog/CreateGroupDialog.tsx b/webapp/src/components/App/Settings/Groups/dialog/CreateGroupDialog.tsx index c35544a668..3b3abd1485 100644 --- a/webapp/src/components/App/Settings/Groups/dialog/CreateGroupDialog.tsx +++ b/webapp/src/components/App/Settings/Groups/dialog/CreateGroupDialog.tsx @@ -27,10 +27,6 @@ import { createGroup, createRole } from "../../../../../services/api/user"; import { SubmitHandlerPlus } from "../../../../common/Form/types"; import GroupFormDialog, { GroupFormDialogProps } from "./GroupFormDialog"; -/** - * Types - */ - type InheritPropsToOmit = "title" | "titleIcon" | "onSubmit" | "onCancel"; interface Props extends Omit { @@ -39,10 +35,6 @@ interface Props extends Omit { closeDialog: VoidFunction; } -/** - * Component - */ - function CreateGroupDialog(props: Props) { const { addGroup, reloadFetchGroups, closeDialog, ...dialogProps } = props; const { enqueueSnackbar } = useSnackbar(); diff --git a/webapp/src/components/App/Settings/Groups/dialog/GroupFormDialog/index.tsx b/webapp/src/components/App/Settings/Groups/dialog/GroupFormDialog/index.tsx index 4f96b8a077..ef29ca1722 100644 --- a/webapp/src/components/App/Settings/Groups/dialog/GroupFormDialog/index.tsx +++ b/webapp/src/components/App/Settings/Groups/dialog/GroupFormDialog/index.tsx @@ -18,10 +18,6 @@ import FormDialog, { import { RoleType, UserDTO } from "../../../../../../common/types"; import GroupForm from "./GroupForm"; -/** - * Types - */ - export interface GroupFormDialogProps extends Omit { defaultValues?: { @@ -30,10 +26,6 @@ export interface GroupFormDialogProps }; } -/** - * Component - */ - function GroupFormDialog(props: GroupFormDialogProps) { const { defaultValues, ...dialogProps } = props; @@ -44,8 +36,4 @@ function GroupFormDialog(props: GroupFormDialogProps) { ); } -GroupFormDialog.defaultProps = { - defaultValues: undefined, -}; - export default GroupFormDialog; diff --git a/webapp/src/components/App/Settings/Groups/index.tsx b/webapp/src/components/App/Settings/Groups/index.tsx index dd28441273..b92e62ffe8 100644 --- a/webapp/src/components/App/Settings/Groups/index.tsx +++ b/webapp/src/components/App/Settings/Groups/index.tsx @@ -47,10 +47,6 @@ import { getAuthUser } from "../../../../redux/selectors"; import useAppSelector from "../../../../redux/hooks/useAppSelector"; import { isSearchMatching } from "../../../../utils/stringUtils"; -/** - * Types - */ - enum GroupActionKind { ADD = "ADD", EDIT = "EDIT", @@ -70,10 +66,6 @@ interface GroupAction extends Action { | GroupEdit; } -/** - * Utils - */ - const reducer = produce((draft, action) => { const { payload } = action; @@ -102,10 +94,6 @@ const reducer = produce((draft, action) => { } }); -/** - * Component - */ - function Groups() { const [groupToDelete, setGroupToDelete] = useState(); const [groupToEdit, setGroupToEdit] = useState(); diff --git a/webapp/src/components/App/Settings/Tokens/Header.tsx b/webapp/src/components/App/Settings/Tokens/Header.tsx index a64c3cc599..0cf3d3e329 100644 --- a/webapp/src/components/App/Settings/Tokens/Header.tsx +++ b/webapp/src/components/App/Settings/Tokens/Header.tsx @@ -20,20 +20,12 @@ import { BotDTO } from "../../../../common/types"; import CreateTokenDialog from "./dialog/CreateTokenDialog"; import SearchFE from "../../../common/fieldEditors/SearchFE"; -/** - * Types - */ - interface Props { setSearchValue: (v: string) => void; addToken: (user: BotDTO) => void; reloadFetchTokens: () => void; } -/** - * Component - */ - function Header(props: Props) { const { setSearchValue, addToken, reloadFetchTokens } = props; const { t } = useTranslation(); diff --git a/webapp/src/components/App/Settings/Tokens/dialog/TokenFormDialog/TokenForm.tsx b/webapp/src/components/App/Settings/Tokens/dialog/TokenFormDialog/TokenForm.tsx index 44018200dd..08b8fa928c 100644 --- a/webapp/src/components/App/Settings/Tokens/dialog/TokenFormDialog/TokenForm.tsx +++ b/webapp/src/components/App/Settings/Tokens/dialog/TokenFormDialog/TokenForm.tsx @@ -256,9 +256,4 @@ function TokenForm(props: Props) { ); } -TokenForm.defaultProps = { - onlyPermissions: false, - readOnly: false, -}; - export default TokenForm; diff --git a/webapp/src/components/App/Settings/Tokens/dialog/TokenFormDialog/index.tsx b/webapp/src/components/App/Settings/Tokens/dialog/TokenFormDialog/index.tsx index 8d30e9dd56..24516f6b7e 100644 --- a/webapp/src/components/App/Settings/Tokens/dialog/TokenFormDialog/index.tsx +++ b/webapp/src/components/App/Settings/Tokens/dialog/TokenFormDialog/index.tsx @@ -17,19 +17,11 @@ import FormDialog, { } from "../../../../../common/dialogs/FormDialog"; import TokenForm from "./TokenForm"; -/** - * Types - */ - export interface TokenFormDialogProps extends Omit { onlyPermissions?: boolean; } -/** - * Component - */ - function TokenFormDialog(props: TokenFormDialogProps) { const { onlyPermissions, ...dialogProps } = props; @@ -42,8 +34,4 @@ function TokenFormDialog(props: TokenFormDialogProps) { ); } -TokenFormDialog.defaultProps = { - onlyPermissions: false, -}; - export default TokenFormDialog; diff --git a/webapp/src/components/App/Settings/Tokens/dialog/TokenInfoDialog.tsx b/webapp/src/components/App/Settings/Tokens/dialog/TokenInfoDialog.tsx index 2929cfd18c..23f12dc088 100644 --- a/webapp/src/components/App/Settings/Tokens/dialog/TokenInfoDialog.tsx +++ b/webapp/src/components/App/Settings/Tokens/dialog/TokenInfoDialog.tsx @@ -22,18 +22,10 @@ import OkDialog, { OkDialogProps } from "../../../../common/dialogs/OkDialog"; import TokenForm from "./TokenFormDialog/TokenForm"; import Form from "../../../../common/Form"; -/** - * Types - */ - interface Props extends Omit { token: BotDetailsDTO; } -/** - * Component - */ - function TokenInfoDialog(props: Props) { const { token, ...dialogProps } = props; diff --git a/webapp/src/components/App/Settings/Tokens/index.tsx b/webapp/src/components/App/Settings/Tokens/index.tsx index ea8639c808..18aa3b4d9a 100644 --- a/webapp/src/components/App/Settings/Tokens/index.tsx +++ b/webapp/src/components/App/Settings/Tokens/index.tsx @@ -51,10 +51,6 @@ import TokenInfoDialog from "./dialog/TokenInfoDialog"; import useAppSelector from "../../../../redux/hooks/useAppSelector"; import { isSearchMatching } from "../../../../utils/stringUtils"; -/** - * Types - */ - interface BotDetailsDtoWithUser extends BotDetailsDTO { user: UserDTO; } @@ -69,10 +65,6 @@ interface TokenAction extends Action { payload?: BotDTO["id"] | BotDTO | BotDTO[]; } -/** - * Utils - */ - const reducer = produce( (draft, action) => { const { payload } = action; @@ -95,10 +87,6 @@ const reducer = produce( }, ); -/** - * Component - */ - function Tokens() { const [tokenToDisplayInfo, setTokenToDisplayInfo] = useState(); const [tokenToDelete, setTokenToDelete] = useState(); diff --git a/webapp/src/components/App/Settings/Users/Header.tsx b/webapp/src/components/App/Settings/Users/Header.tsx index fa4317eade..47969745bf 100644 --- a/webapp/src/components/App/Settings/Users/Header.tsx +++ b/webapp/src/components/App/Settings/Users/Header.tsx @@ -20,20 +20,12 @@ import CreateUserDialog from "./dialog/CreateUserDialog"; import { UserDetailsDTO } from "../../../../common/types"; import SearchFE from "../../../common/fieldEditors/SearchFE"; -/** - * Types - */ - interface Props { setSearchValue: (v: string) => void; addUser: (user: UserDetailsDTO) => void; reloadFetchUsers: () => void; } -/** - * Component - */ - function Header(props: Props) { const { setSearchValue, addUser, reloadFetchUsers } = props; const { t } = useTranslation(); diff --git a/webapp/src/components/App/Settings/Users/dialog/UserFormDialog/UserForm.tsx b/webapp/src/components/App/Settings/Users/dialog/UserFormDialog/UserForm.tsx index dbe75b017f..9066989aa5 100644 --- a/webapp/src/components/App/Settings/Users/dialog/UserFormDialog/UserForm.tsx +++ b/webapp/src/components/App/Settings/Users/dialog/UserFormDialog/UserForm.tsx @@ -266,8 +266,4 @@ function UserForm(props: Props) { ); } -UserForm.defaultProps = { - onlyPermissions: false, -}; - export default UserForm; diff --git a/webapp/src/components/App/Settings/Users/dialog/UserFormDialog/index.tsx b/webapp/src/components/App/Settings/Users/dialog/UserFormDialog/index.tsx index a5d54fd5eb..cc2dd19efd 100644 --- a/webapp/src/components/App/Settings/Users/dialog/UserFormDialog/index.tsx +++ b/webapp/src/components/App/Settings/Users/dialog/UserFormDialog/index.tsx @@ -19,10 +19,6 @@ import FormDialog, { import { GroupDTO, RoleType } from "../../../../../../common/types"; import UserForm from "./UserForm"; -/** - * Types - */ - export interface UserFormDialogProps extends Omit { defaultValues?: { username?: string; @@ -33,10 +29,6 @@ export interface UserFormDialogProps extends Omit { subtitle?: string; } -/** - * Component - */ - function UserFormDialog(props: UserFormDialogProps) { const { defaultValues, onlyPermissions, subtitle, ...dialogProps } = props; @@ -52,10 +44,4 @@ function UserFormDialog(props: UserFormDialogProps) { ); } -UserFormDialog.defaultProps = { - defaultValues: undefined, - onlyPermissions: false, - subtitle: "", -}; - export default UserFormDialog; diff --git a/webapp/src/components/App/Settings/Users/index.tsx b/webapp/src/components/App/Settings/Users/index.tsx index ebca615afc..373ca36b84 100644 --- a/webapp/src/components/App/Settings/Users/index.tsx +++ b/webapp/src/components/App/Settings/Users/index.tsx @@ -45,10 +45,6 @@ import UpdateUserDialog from "./dialog/UpdateUserDialog"; import { sortByName } from "../../../../services/utils"; import { isSearchMatching } from "../../../../utils/stringUtils"; -/** - * Types - */ - enum UserActionKind { ADD = "ADD", EDIT = "EDIT", @@ -62,10 +58,6 @@ interface UserAction extends Action { payload?: UserDetailsDTO["id"] | UserDetailsDTO | UserDetailsDTO[] | UserEdit; } -/** - * Utils - */ - const reducer = produce((draft, action) => { const { payload } = action; @@ -94,10 +86,6 @@ const reducer = produce((draft, action) => { } }); -/** - * Component - */ - function Users() { const [userToDelete, setUserToDelete] = useState(); const [userToEdit, setUserToEdit] = useState(); diff --git a/webapp/src/components/App/Settings/index.tsx b/webapp/src/components/App/Settings/index.tsx index 0f3781212c..e1479ed8b8 100644 --- a/webapp/src/components/App/Settings/index.tsx +++ b/webapp/src/components/App/Settings/index.tsx @@ -29,10 +29,6 @@ import { } from "../../../redux/selectors"; import { tuple } from "../../../utils/tsUtils"; -/** - * Component - */ - function Settings() { const [tabValue, setTabValue] = useState("1"); const [t] = useTranslation(); diff --git a/webapp/src/components/App/Singlestudy/HomeView/InformationView/Notes/NodeEditorModal/index.tsx b/webapp/src/components/App/Singlestudy/HomeView/InformationView/Notes/NodeEditorModal/index.tsx index f940943899..7dae04424a 100644 --- a/webapp/src/components/App/Singlestudy/HomeView/InformationView/Notes/NodeEditorModal/index.tsx +++ b/webapp/src/components/App/Singlestudy/HomeView/InformationView/Notes/NodeEditorModal/index.tsx @@ -154,8 +154,4 @@ function NoteEditorModal(props: Props) { ); } -NoteEditorModal.defaultProps = { - content: undefined, -}; - export default NoteEditorModal; diff --git a/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ThematicTrimmingDialog/index.tsx b/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ThematicTrimmingDialog/index.tsx index 1449479876..19cbfdf8b9 100644 --- a/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ThematicTrimmingDialog/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ThematicTrimmingDialog/index.tsx @@ -18,7 +18,7 @@ import { AccordionSummary, Button, Divider, - Unstable_Grid2 as Grid, + Grid2 as Grid, } from "@mui/material"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import * as R from "ramda"; @@ -166,7 +166,7 @@ function ThematicTrimmingDialog(props: Props) { const fields = getFieldLabelsForGroup(api.getValues(), group) .filter(([, label]) => isSearchMatching(search, label)) .map(([name, label]) => ( - + )); @@ -186,12 +186,7 @@ function ThematicTrimmingDialog(props: Props) { )} - + {fields} diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/AreaPropsView.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/AreaPropsView.tsx index 790ff7a6f6..feb39df236 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/AreaPropsView.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/AreaPropsView.tsx @@ -70,8 +70,4 @@ function AreaPropsView(props: PropsType) { ); } -AreaPropsView.defaultProps = { - currentArea: undefined, -}; - export default AreaPropsView; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/Allocation/AllocationField.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/Allocation/AllocationField.tsx index 8c43074be9..f8cb147368 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/Allocation/AllocationField.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/Allocation/AllocationField.tsx @@ -13,11 +13,11 @@ */ import { Typography, Grid } from "@mui/material"; -import { t } from "i18next"; import { FieldArrayWithId } from "react-hook-form"; import NumberFE from "../../../../../../../common/fieldEditors/NumberFE"; import { useFormContextPlus } from "../../../../../../../common/Form"; import { AllocationFormFields } from "./utils"; +import { validateNumber } from "../../../../../../../../utils/validationUtils"; interface Props { field: FieldArrayWithId; @@ -48,15 +48,10 @@ function AllocationField({ field, index, label }: Props) { diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/Correlation/CorrelationField.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/Correlation/CorrelationField.tsx index 929cca3bb9..b1cdec1ac0 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/Correlation/CorrelationField.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/Correlation/CorrelationField.tsx @@ -20,6 +20,7 @@ import { CorrelationFormFields } from "./utils"; import { useFormContextPlus } from "../../../../../../../common/Form"; import useAppSelector from "../../../../../../../../redux/hooks/useAppSelector"; import { getCurrentArea } from "../../../../../../../../redux/selectors"; +import { validateNumber } from "../../../../../../../../utils/validationUtils"; interface Props { field: FieldArrayWithId; @@ -54,19 +55,10 @@ function CorrelationField({ field, index, label }: Props) { diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/BindingConstraints/BindingConstView/constraintviews/ConstraintElement.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/BindingConstraints/BindingConstView/constraintviews/ConstraintElement.tsx index 5ae1d1c31c..757c2eb019 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/BindingConstraints/BindingConstView/constraintviews/ConstraintElement.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/BindingConstraints/BindingConstView/constraintviews/ConstraintElement.tsx @@ -25,11 +25,11 @@ interface ElementProps { onToggleType?: () => void; } -export default function ConstraintElement({ +function ConstraintElement({ isLink, left, right, - operator, + operator = "x", onToggleType, }: ElementProps) { const { t } = useTranslation(); @@ -53,6 +53,4 @@ export default function ConstraintElement({ ); } -ConstraintElement.defaultProps = { - operator: "x", -}; +export default ConstraintElement; diff --git a/webapp/src/components/App/Singlestudy/index.tsx b/webapp/src/components/App/Singlestudy/index.tsx index 7748369a48..e7eedfe7a9 100644 --- a/webapp/src/components/App/Singlestudy/index.tsx +++ b/webapp/src/components/App/Singlestudy/index.tsx @@ -209,8 +209,4 @@ function SingleStudy(props: Props) { ); } -SingleStudy.defaultProps = { - isExplorer: undefined, -}; - export default SingleStudy; diff --git a/webapp/src/components/App/Tasks/LaunchJobLogView.tsx b/webapp/src/components/App/Tasks/LaunchJobLogView.tsx index 44d2b8f643..d7c5b4c22a 100644 --- a/webapp/src/components/App/Tasks/LaunchJobLogView.tsx +++ b/webapp/src/components/App/Tasks/LaunchJobLogView.tsx @@ -132,9 +132,4 @@ function LaunchJobLogView(props: PropsType) { ); } -LaunchJobLogView.defaultProps = { - logButton: false, - logErrorButton: false, -}; - export default LaunchJobLogView; diff --git a/webapp/src/components/common/Fieldset.tsx b/webapp/src/components/common/Fieldset.tsx index 88d2540ee9..081a9accce 100644 --- a/webapp/src/components/common/Fieldset.tsx +++ b/webapp/src/components/common/Fieldset.tsx @@ -12,16 +12,17 @@ * This file is part of the Antares project. */ -import { Box, BoxProps, Divider } from "@mui/material"; +import { Box, BoxProps, Divider, SxProps, Theme } from "@mui/material"; import * as RA from "ramda-adjunct"; import { mergeSxProp } from "../../utils/muiUtils"; -interface FieldsetProps extends Omit { +interface FieldsetProps { legend?: string | React.ReactNode; children: React.ReactNode; contentProps?: BoxProps; fullFieldWidth?: boolean; fieldWidth?: number; + sx?: SxProps; } function Fieldset(props: FieldsetProps) { @@ -32,12 +33,10 @@ function Fieldset(props: FieldsetProps) { contentProps, fullFieldWidth = false, fieldWidth = 220, - ...rest } = props; return ( {RA.isFunction(deleteConfirmationMessage) ? deleteConfirmationMessage(selectedRows.length) - : deleteConfirmationMessage ?? t("dialog.message.confirmDelete")} + : (deleteConfirmationMessage ?? t("dialog.message.confirmDelete"))} )} diff --git a/webapp/src/components/common/LogModal.tsx b/webapp/src/components/common/LogModal.tsx index dc94255215..b2b918a4db 100644 --- a/webapp/src/components/common/LogModal.tsx +++ b/webapp/src/components/common/LogModal.tsx @@ -210,12 +210,4 @@ function LogModal(props: Props) { ); } -LogModal.defaultProps = { - content: undefined, - jobId: undefined, - followLogs: false, - loading: false, - style: {}, -}; - export default LogModal; diff --git a/webapp/src/components/common/MatrixGrid/style.ts b/webapp/src/components/common/MatrixGrid/style.ts index d99c3ad7d6..7711a73c8e 100644 --- a/webapp/src/components/common/MatrixGrid/style.ts +++ b/webapp/src/components/common/MatrixGrid/style.ts @@ -12,8 +12,7 @@ * This file is part of the Antares project. */ -import styled from "@emotion/styled"; -import { Box, Typography } from "@mui/material"; +import { Box, styled, Typography } from "@mui/material"; export const MatrixContainer = styled(Box)(() => ({ width: "100%", diff --git a/webapp/src/components/common/SelectMulti.tsx b/webapp/src/components/common/SelectMulti.tsx index 6512206708..29f95fc5d1 100644 --- a/webapp/src/components/common/SelectMulti.tsx +++ b/webapp/src/components/common/SelectMulti.tsx @@ -27,6 +27,7 @@ import { Theme, } from "@mui/material"; import { GenericInfo } from "../../common/types"; +import { mergeSxProp } from "../../utils/muiUtils"; interface Props { name: string; @@ -79,7 +80,7 @@ function SelectMulti(props: Props) { .join(", "); return ( - + {name} { contentProps?: DialogContentProps; } -/** - * Styled - */ - const AlertBorder = styled("span", { shouldForwardProp: (prop: string) => !prop.startsWith("$"), })<{ $type: AlertValues }>(({ theme, $type }) => ({ @@ -67,10 +59,6 @@ const AlertBorder = styled("span", { ])(Alert[$type]), })); -/** - * Component - */ - function BasicDialog(props: BasicDialogProps) { const { title, @@ -118,12 +106,4 @@ function BasicDialog(props: BasicDialogProps) { ); } -BasicDialog.defaultProps = { - title: null, - titleIcon: null, - actions: null, - alert: false, - contentProps: null, -}; - export default BasicDialog; diff --git a/webapp/src/components/common/dialogs/ConfirmationDialog.tsx b/webapp/src/components/common/dialogs/ConfirmationDialog.tsx index d237a535f0..df8141906f 100644 --- a/webapp/src/components/common/dialogs/ConfirmationDialog.tsx +++ b/webapp/src/components/common/dialogs/ConfirmationDialog.tsx @@ -16,10 +16,6 @@ import { Button } from "@mui/material"; import { useTranslation } from "react-i18next"; import BasicDialog, { BasicDialogProps } from "./BasicDialog"; -/** - * Types - */ - export interface ConfirmationDialogProps extends Omit { cancelButtonText?: string; @@ -28,10 +24,6 @@ export interface ConfirmationDialogProps onCancel: VoidFunction; } -/** - * Component - */ - function ConfirmationDialog(props: ConfirmationDialogProps) { const { cancelButtonText, @@ -78,9 +70,4 @@ function ConfirmationDialog(props: ConfirmationDialogProps) { ); } -ConfirmationDialog.defaultProps = { - cancelButtonText: null, - confirmButtonText: null, -}; - export default ConfirmationDialog; diff --git a/webapp/src/components/common/dialogs/DataViewerDialog/index.tsx b/webapp/src/components/common/dialogs/DataViewerDialog/index.tsx index fc1816d0e4..156cfbe3e2 100644 --- a/webapp/src/components/common/dialogs/DataViewerDialog/index.tsx +++ b/webapp/src/components/common/dialogs/DataViewerDialog/index.tsx @@ -140,8 +140,4 @@ function DataViewerDialog(props: Props) { ); } -DataViewerDialog.defaultProps = { - isMatrix: false, -}; - export default DataViewerDialog; diff --git a/webapp/src/components/common/dialogs/OkDialog.tsx b/webapp/src/components/common/dialogs/OkDialog.tsx index d43c147b50..c8245eb533 100644 --- a/webapp/src/components/common/dialogs/OkDialog.tsx +++ b/webapp/src/components/common/dialogs/OkDialog.tsx @@ -16,20 +16,12 @@ import { Button, ButtonProps } from "@mui/material"; import { useTranslation } from "react-i18next"; import BasicDialog, { BasicDialogProps } from "./BasicDialog"; -/** - * Types - */ - export interface OkDialogProps extends Omit { okButtonText?: string; okButtonProps?: Omit; onOk: VoidFunction; } -/** - * Component - */ - function OkDialog(props: OkDialogProps) { const { okButtonText, okButtonProps, onOk, onClose, ...basicDialogProps } = props; @@ -63,9 +55,4 @@ function OkDialog(props: OkDialogProps) { ); } -OkDialog.defaultProps = { - okButtonText: null, - okButtonProps: null, -}; - export default OkDialog; diff --git a/webapp/src/components/common/loaders/SimpleLoader.tsx b/webapp/src/components/common/loaders/SimpleLoader.tsx index 8493b99327..94efbf71c2 100644 --- a/webapp/src/components/common/loaders/SimpleLoader.tsx +++ b/webapp/src/components/common/loaders/SimpleLoader.tsx @@ -23,7 +23,7 @@ interface PropTypes { function SimpleLoader(props: PropTypes) { const [t] = useTranslation(); - const { progress, message, color } = props; + const { progress, message, color = "rgba(0,0,0,0)" } = props; return ( ) { const { header, hideHeaderDivider, children } = props; @@ -44,9 +36,4 @@ function BasicPage(props: PropsWithChildren) { ); } -BasicPage.defaultProps = { - header: null, - hideHeaderDivider: false, -}; - export default BasicPage; diff --git a/webapp/src/components/common/page/RootPage.tsx b/webapp/src/components/common/page/RootPage.tsx index fb72759b8a..7d759a6321 100644 --- a/webapp/src/components/common/page/RootPage.tsx +++ b/webapp/src/components/common/page/RootPage.tsx @@ -16,10 +16,6 @@ import { SvgIconComponent } from "@mui/icons-material"; import { Box, Typography } from "@mui/material"; import BasicPage from "./BasicPage"; -/** - * Types - */ - interface Props { title: string; titleIcon?: SvgIconComponent; @@ -29,10 +25,6 @@ interface Props { children?: React.ReactNode; } -/** - * Component - */ - function RootPage(props: Props) { const { title, @@ -96,11 +88,4 @@ function RootPage(props: Props) { ); } -RootPage.defaultProps = { - titleIcon: null, - headerTopRight: null, - headerBottom: null, - hideHeaderDivider: false, -}; - export default RootPage; diff --git a/webapp/src/hooks/useEnqueueErrorSnackbar.tsx b/webapp/src/hooks/useEnqueueErrorSnackbar.tsx index 51dcec46d7..5b04adb42b 100644 --- a/webapp/src/hooks/useEnqueueErrorSnackbar.tsx +++ b/webapp/src/hooks/useEnqueueErrorSnackbar.tsx @@ -17,10 +17,6 @@ import { useCallback } from "react"; import { L } from "ts-toolbelt"; import SnackErrorMessage from "../components/common/SnackErrorMessage"; -/** - * Types - */ - type EnqueueErrorType = ProviderContext["enqueueSnackbar"]; type EnqueueErrorSnackbarType = ( @@ -28,10 +24,6 @@ type EnqueueErrorSnackbarType = ( details: string | Error, ) => ReturnType; -/** - * Hook - */ - function useEnqueueErrorSnackbar(): EnqueueErrorSnackbarType { const { enqueueSnackbar } = useSnackbar(); diff --git a/webapp/src/redux/ducks/auth.ts b/webapp/src/redux/ducks/auth.ts index e77351b6c8..308b802827 100644 --- a/webapp/src/redux/ducks/auth.ts +++ b/webapp/src/redux/ducks/auth.ts @@ -13,7 +13,7 @@ */ import { createAsyncThunk, createReducer, isAnyOf } from "@reduxjs/toolkit"; -import jwtDecode, { JwtPayload } from "jwt-decode"; +import { jwtDecode, type JwtPayload } from "jwt-decode"; import { UserInfo } from "../../common/types"; import * as authApi from "../../services/api/auth"; import * as clientApi from "../../services/api/client"; From ede7b3e86ecc7694fdd15cb86589e9d3134c65e0 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 30 Sep 2024 13:19:28 +0200 Subject: [PATCH 034/103] fix: fix tests --- .../study/storage/variantstudy/model/command/create_link.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index a5df870251..b004d5452e 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -62,14 +62,14 @@ class Config: @field_validator("filter_synthesis", "filter_year_by_year", mode="before") def validate_individual_filters(cls, value: Optional[str]) -> Optional[str]: if value is not None: - from antarest.study.business.areas.properties_management import DEFAULT_FILTER_VALUE + filter_values = ["hourly", "daily", "weekly", "monthly", "annual"] options = value.replace(" ", "").split(",") - invalid_options = [opt for opt in options if opt not in DEFAULT_FILTER_VALUE] + invalid_options = [opt for opt in options if opt not in filter_values] if invalid_options: raise LinkValidationError( f"Invalid value(s) in filters: {', '.join(invalid_options)}. " - f"Allowed values are: {', '.join(DEFAULT_FILTER_VALUE)}." + f"Allowed values are: {', '.join(filter_values)}." ) return value From 3a6a7c8534269e4031cb669c7e8afdb281132126 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 30 Sep 2024 13:35:52 +0200 Subject: [PATCH 035/103] fix: remove optional typing in models --- antarest/study/business/link_management.py | 1 - .../variantstudy/model/command/create_link.py | 27 ++++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 0428056a6c..d7ce8345d5 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,7 +12,6 @@ import typing as t - from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index b004d5452e..14b75bc7c8 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -9,6 +9,7 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. +import typing as t from typing import Any, Dict, List, Optional, Tuple, Union, cast from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator @@ -34,17 +35,17 @@ class AreaInfo(BaseModel): class LinkInfoProperties(BaseModel): - hurdles_cost: Optional[bool] = False - loop_flow: Optional[bool] = False - use_phase_shifter: Optional[bool] = False - transmission_capacities: Optional[TransmissionCapacity] = TransmissionCapacity.ENABLED - asset_type: Optional[AssetType] = AssetType.AC - display_comments: Optional[bool] = True - colorr: Optional[int] = Field(default=DEFAULT_COLOR, gt=0, lt=255) - colorb: Optional[int] = Field(default=DEFAULT_COLOR, gt=0, lt=255) - colorg: Optional[int] = Field(default=DEFAULT_COLOR, gt=0, lt=255) - link_width: Optional[float] = 1 - link_style: Optional[LinkStyle] = LinkStyle.PLAIN + hurdles_cost: bool = False + loop_flow: bool = False + use_phase_shifter: bool = False + transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED + asset_type: AssetType= AssetType.AC + display_comments: bool = True + colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + colorg: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + link_width: float = 1 + link_style: LinkStyle = LinkStyle.PLAIN class Config: alias_generator = to_kebab_case @@ -52,8 +53,8 @@ class Config: class LinkInfoProperties820(LinkInfoProperties): - filter_synthesis: Optional[str] = None - filter_year_by_year: Optional[str] = None + filter_synthesis: t.Optional[str] = None + filter_year_by_year: t.Optional[str] = None class Config: alias_generator = to_kebab_case From fcace5016b73d4b9ba24b7f4a576842939dd3884 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 30 Sep 2024 13:51:33 +0200 Subject: [PATCH 036/103] fix: remove optional typing in models --- .../study/storage/variantstudy/model/command/create_link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 14b75bc7c8..3e2a929766 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -39,7 +39,7 @@ class LinkInfoProperties(BaseModel): loop_flow: bool = False use_phase_shifter: bool = False transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED - asset_type: AssetType= AssetType.AC + asset_type: AssetType = AssetType.AC display_comments: bool = True colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) From 0f4d6e0d16e7d799900a6ec34b9a15d5514c3e44 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 30 Sep 2024 14:44:32 +0200 Subject: [PATCH 037/103] fix: remove optional typing in models --- .../study/storage/variantstudy/model/command/create_link.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 3e2a929766..9403a63990 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -49,7 +49,7 @@ class LinkInfoProperties(BaseModel): class Config: alias_generator = to_kebab_case - allow_population_by_field_name = True + populate_by_name = True class LinkInfoProperties820(LinkInfoProperties): @@ -58,7 +58,7 @@ class LinkInfoProperties820(LinkInfoProperties): class Config: alias_generator = to_kebab_case - allow_population_by_field_name = True + populate_by_name = True @field_validator("filter_synthesis", "filter_year_by_year", mode="before") def validate_individual_filters(cls, value: Optional[str]) -> Optional[str]: From ece5f0e97aec274165d36d1ce74daa50afe3c357 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 7 Oct 2024 16:34:33 +0200 Subject: [PATCH 038/103] WIP --- antarest/study/business/link_management.py | 26 +++++-- .../storage/variantstudy/command_factory.py | 2 + .../variantstudy/model/command/common.py | 1 + .../variantstudy/model/command/update_link.py | 74 +++++++++++++++++++ .../study_data_blueprint/test_link.py | 19 +++++ 5 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 antarest/study/storage/variantstudy/model/command/update_link.py diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 9ece09b073..22ac0e7d1a 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -28,6 +28,7 @@ ) from antarest.study.storage.variantstudy.model.command.remove_link import RemoveLink from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig +from antarest.study.storage.variantstudy.model.command.update_link import UpdateLink _ALL_LINKS_PATH = "input/links" @@ -114,19 +115,34 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L return link_data def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: + self.check_attributes_coherence(int(study.version), link_creation_info) + file_study = self.storage_service.get_storage(study).get_raw(study) - existing_link = self.get_one_link(study, link_creation_info.area1, link_creation_info.area2) + existing_link = self.get_one_link(study, link_creation_info) + existing_link = existing_link.model_copy(update=link_creation_info.model_dump(exclude={"area1", "area2"}, exclude_none=True)) + # create new object + command = UpdateLink( + area1=link_creation_info.area1, + area2=link_creation_info.area2, + parameters=existing_link.model_dump(exclude={"area1", "area2"}, exclude_none=True, by_alias=True), + command_context=self.storage_service.variant_study_service.command_factory.command_context, + ) execute_or_add_commands(study, file_study, [command], self.storage_service) return existing_link - def get_one_link(self, study: RawStudy, area1: str, area2: str) -> LinkInfoDTOType: + def check_attributes_coherence(self, study_version: int, link_creation_info: LinkInfoDTOType) -> None: + if study_version < 820: + if link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None: + raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") + + def get_one_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: file_study = self.storage_service.get_storage(study).get_raw(study) - area_from, area_to = sorted([area1, area2]) + area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) try: link_config = file_study.tree.get(["input", "links", area_from, "properties", area_to]) except KeyError: @@ -137,8 +153,8 @@ def get_one_link(self, study: RawStudy, area1: str, area2: str) -> LinkInfoDTOTy link_data: LinkInfoDTOType if int(study.version) < 820: - return LinkInfoDTOBase(**link_config) - return LinkInfoDTO820(**link_config) + return LinkInfoDTOBase.model_validate(link_config) + return LinkInfoDTO820.model_validate(link_config) def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) diff --git a/antarest/study/storage/variantstudy/command_factory.py b/antarest/study/storage/variantstudy/command_factory.py index 409cf81bb5..4ab203ff21 100644 --- a/antarest/study/storage/variantstudy/command_factory.py +++ b/antarest/study/storage/variantstudy/command_factory.py @@ -41,6 +41,7 @@ from antarest.study.storage.variantstudy.model.command.update_comments import UpdateComments from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig from antarest.study.storage.variantstudy.model.command.update_district import UpdateDistrict +from antarest.study.storage.variantstudy.model.command.update_link import UpdateLink from antarest.study.storage.variantstudy.model.command.update_playlist import UpdatePlaylist from antarest.study.storage.variantstudy.model.command.update_raw_file import UpdateRawFile from antarest.study.storage.variantstudy.model.command.update_scenario_builder import UpdateScenarioBuilder @@ -53,6 +54,7 @@ CommandName.CREATE_DISTRICT.value: CreateDistrict, CommandName.REMOVE_DISTRICT.value: RemoveDistrict, CommandName.CREATE_LINK.value: CreateLink, + CommandName.UPDATE_LINK.value: UpdateLink, CommandName.REMOVE_LINK.value: RemoveLink, CommandName.CREATE_BINDING_CONSTRAINT.value: CreateBindingConstraint, CommandName.UPDATE_BINDING_CONSTRAINT.value: UpdateBindingConstraint, diff --git a/antarest/study/storage/variantstudy/model/command/common.py b/antarest/study/storage/variantstudy/model/command/common.py index dc5dce4331..0e961effa1 100644 --- a/antarest/study/storage/variantstudy/model/command/common.py +++ b/antarest/study/storage/variantstudy/model/command/common.py @@ -31,6 +31,7 @@ class CommandName(Enum): CREATE_DISTRICT = "create_district" REMOVE_DISTRICT = "remove_district" CREATE_LINK = "create_link" + UPDATE_LINK = "update_link" REMOVE_LINK = "remove_link" CREATE_BINDING_CONSTRAINT = "create_binding_constraint" UPDATE_BINDING_CONSTRAINT = "update_binding_constraint" diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py new file mode 100644 index 0000000000..8db6977d40 --- /dev/null +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -0,0 +1,74 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. +import typing as t + +from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig +from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy +from antarest.study.storage.variantstudy.model.command.common import CommandOutput, CommandName + +from antarest.study.storage.variantstudy.model.command.icommand import ICommand, OutputTuple, MATCH_SIGNATURE_SEPARATOR +from antarest.study.storage.variantstudy.model.model import CommandDTO + + +class UpdateLink(ICommand): + """ + Command used to create a link between two areas. + """ + + # Overloaded metadata + # =================== + + command_name: CommandName = CommandName.UPDATE_LINK + version: int = 1 + + # Command parameters + # ================== + area1: str + area2: str + parameters: t.Optional[t.Dict[str, t.Any]] = None + + def _apply_config(self, study_data: FileStudyTreeConfig) -> OutputTuple: + area_from, area_to = sorted([self.area1, self.area2]) + + return ( + CommandOutput( + status=True, + message=f"Link between '{self.area1}' and '{self.area2}' updated", + ), + {"area_from": area_from, "area_to": area_to}, + ) + + def _apply(self, study_data: FileStudy) -> CommandOutput: + area_from, area_to = sorted([self.area1, self.area2]) + study_data.tree.save(self.parameters, ["input", "links", area_from, "properties", area_to]) + output, _ = self._apply_config(study_data.config) + return output + + def to_dto(self) -> CommandDTO: + args = { + "area1": self.area1, + "area2": self.area2, + "parameters": self.parameters, + } + return CommandDTO( + action=CommandName.UPDATE_LINK.value, + args=args, + ) + + def match_signature(self) -> str: + return str(self.command_name.value + MATCH_SIGNATURE_SEPARATOR + self.area1 + MATCH_SIGNATURE_SEPARATOR + self.area2) + + def _create_diff(self, other: "ICommand") -> t.List["ICommand"]: + pass + + def get_inner_matrices(self) -> t.List[str]: + pass \ No newline at end of file diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 12bb34fd29..5028d7c2ca 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -19,6 +19,25 @@ @pytest.mark.unit_test class TestLink: + + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_link_update(self, client: TestClient, user_access_token: str, study_type: str) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=820) + if study_type == "variant": + study_id = preparer.create_variant(study_id, name="Variant 1") + + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "hurdles-cost": True}) + res = client.put(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "hurdles-cost": False, "colorr": 150, "filter-synthesis": "hourly"}) + + assert res.status_code == 200 + expected = {'area1': 'area 1', 'area2': 'area 2', 'asset-type': 'ac', 'colorb': 112, 'colorg': 112, 'colorr': 150, 'display-comments': True, 'filter-synthesis': 'hourly', 'filter-year-by-year': 'hourly, daily, weekly, monthly, annual', 'hurdles-cost': False, 'link-style': 'plain', 'link-width': 1.0, 'loop-flow': False, 'transmission-capacities': 'enabled', 'use-phase-shifter': False} + assert expected == res.json() + @pytest.mark.parametrize("study_type", ["raw", "variant"]) def test_link_820(self, client: TestClient, user_access_token: str, study_type: str) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore From 4d5c8ea2e035b924beecadfa9def90686da2b354 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 7 Oct 2024 16:45:05 +0200 Subject: [PATCH 039/103] conflict: merge dev --- antarest/study/business/link_management.py | 10 +--------- .../storage/variantstudy/model/command/create_link.py | 8 +++++--- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 1bf9d27036..a18ecd1e54 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,7 +12,6 @@ import typing as t -from antarest.core.exceptions import ConfigFileNotFound from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError from antarest.core.model import JSON from antarest.core.serialization import AntaresBaseModel @@ -33,23 +32,16 @@ _ALL_LINKS_PATH = "input/links" + class LinkInfoDTOBase(AreaInfo, LinkInfoProperties): pass -class LinkUIDTO(AntaresBaseModel): - color: str - width: float - style: str class LinkInfoDTO820(AreaInfo, LinkInfoProperties820): pass LinkInfoDTOType = t.Union[LinkInfoDTO820, LinkInfoDTOBase] -class LinkInfoDTO(AntaresBaseModel): - area1: str - area2: str - ui: t.Optional[LinkUIDTO] = None @all_optional_model diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index db71244e2d..fc52db1a49 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -12,13 +12,15 @@ import typing as t from typing import Any, Dict, List, Optional, Tuple, Union, cast -from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator +from pydantic import Field, ValidationInfo, field_validator, model_validator from antarest.core.exceptions import LinkValidationError +from antarest.core.serialization import AntaresBaseModel from antarest.core.utils.string import to_kebab_case from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData from antarest.study.model import STUDY_VERSION_8_2 +from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, LinkStyle, TransmissionCapacity from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix @@ -29,12 +31,12 @@ DEFAULT_COLOR = 112 -class AreaInfo(BaseModel): +class AreaInfo(AntaresBaseModel): area1: str area2: str -class LinkInfoProperties(BaseModel): +class LinkInfoProperties(AntaresBaseModel): hurdles_cost: bool = False loop_flow: bool = False use_phase_shifter: bool = False From 5ca372cfd1a4b2feb0e2efd3c9ed876d6a2d8628 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 8 Oct 2024 10:27:41 +0200 Subject: [PATCH 040/103] WIP --- antarest/study/business/link_management.py | 5 ++-- .../variantstudy/model/command/create_link.py | 10 +++++++ .../variantstudy/model/command/update_link.py | 9 +++--- .../study_data_blueprint/test_link.py | 29 +++++++++++++++++-- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index f0a3f3beed..063e9a8580 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -121,8 +121,9 @@ def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L file_study = self.storage_service.get_storage(study).get_raw(study) existing_link = self.get_one_link(study, link_creation_info) - existing_link = existing_link.model_copy(update=link_creation_info.model_dump(exclude={"area1", "area2"}, exclude_none=True)) - # create new object + existing_link = existing_link.model_copy( + update=link_creation_info.model_dump(exclude={"area1", "area2"}, exclude_none=True) + ) command = UpdateLink( area1=link_creation_info.area1, diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index fc52db1a49..7774c2d940 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -10,6 +10,7 @@ # # This file is part of the Antares project. import typing as t +from abc import ABCMeta from typing import Any, Dict, List, Optional, Tuple, Union, cast from pydantic import Field, ValidationInfo, field_validator, model_validator @@ -81,6 +82,15 @@ class LinkProperties(LinkInfoProperties820): pass +class AbstractLinkCommand(ICommand, metaclass=ABCMeta): + # area1: str + # area2: str + # parameters: Optional[Dict[str, Any]] = None + # series: Optional[Union[List[List[MatrixData]], str]] = None + # direct: Optional[Union[List[List[MatrixData]], str]] = None + # indirect: Optional[Union[List[List[MatrixData]], str]] = None + pass + class CreateLink(ICommand): """ Command used to create a link between two areas. diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 8db6977d40..4d0b034e48 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -13,9 +13,8 @@ from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy -from antarest.study.storage.variantstudy.model.command.common import CommandOutput, CommandName - -from antarest.study.storage.variantstudy.model.command.icommand import ICommand, OutputTuple, MATCH_SIGNATURE_SEPARATOR +from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput +from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand, OutputTuple from antarest.study.storage.variantstudy.model.model import CommandDTO @@ -65,7 +64,9 @@ def to_dto(self) -> CommandDTO: ) def match_signature(self) -> str: - return str(self.command_name.value + MATCH_SIGNATURE_SEPARATOR + self.area1 + MATCH_SIGNATURE_SEPARATOR + self.area2) + return str( + self.command_name.value + MATCH_SIGNATURE_SEPARATOR + self.area1 + MATCH_SIGNATURE_SEPARATOR + self.area2 + ) def _create_diff(self, other: "ICommand") -> t.List["ICommand"]: pass diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 5028d7c2ca..e632a9897f 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -32,10 +32,35 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ area1_id = preparer.create_area(study_id, name="Area 1")["id"] area2_id = preparer.create_area(study_id, name="Area 2")["id"] client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "hurdles-cost": True}) - res = client.put(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "hurdles-cost": False, "colorr": 150, "filter-synthesis": "hourly"}) + res = client.put( + f"/v1/studies/{study_id}/links", + json={ + "area1": area1_id, + "area2": area2_id, + "hurdles-cost": False, + "colorr": 150, + "filter-synthesis": "hourly", + }, + ) assert res.status_code == 200 - expected = {'area1': 'area 1', 'area2': 'area 2', 'asset-type': 'ac', 'colorb': 112, 'colorg': 112, 'colorr': 150, 'display-comments': True, 'filter-synthesis': 'hourly', 'filter-year-by-year': 'hourly, daily, weekly, monthly, annual', 'hurdles-cost': False, 'link-style': 'plain', 'link-width': 1.0, 'loop-flow': False, 'transmission-capacities': 'enabled', 'use-phase-shifter': False} + expected = { + "area1": "area 1", + "area2": "area 2", + "asset-type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 150, + "display-comments": True, + "filter-synthesis": "hourly", + "filter-year-by-year": "hourly, daily, weekly, monthly, annual", + "hurdles-cost": False, + "link-style": "plain", + "link-width": 1.0, + "loop-flow": False, + "transmission-capacities": "enabled", + "use-phase-shifter": False, + } assert expected == res.json() @pytest.mark.parametrize("study_type", ["raw", "variant"]) From 833a742183b711be0a0230e7cc567af030e0df87 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 8 Oct 2024 15:48:22 +0200 Subject: [PATCH 041/103] add abstract class for links --- antarest/study/business/link_management.py | 4 +- .../variantstudy/business/command_reverter.py | 4 + .../variantstudy/model/command/create_link.py | 155 ++++++++++-------- .../variantstudy/model/command/update_link.py | 63 ++++++- .../study_data_blueprint/test_link.py | 1 - tests/variantstudy/test_command_factory.py | 13 +- 6 files changed, 160 insertions(+), 80 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 063e9a8580..48b16647ae 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -128,7 +128,9 @@ def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L command = UpdateLink( area1=link_creation_info.area1, area2=link_creation_info.area2, - parameters=existing_link.model_dump(exclude={"area1", "area2"}, exclude_none=True, by_alias=True), + parameters=existing_link.model_dump( + mode="json", exclude={"area1", "area2"}, exclude_none=True, by_alias=True + ), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) diff --git a/antarest/study/storage/variantstudy/business/command_reverter.py b/antarest/study/storage/variantstudy/business/command_reverter.py index 5134a86bea..c47ed04b46 100644 --- a/antarest/study/storage/variantstudy/business/command_reverter.py +++ b/antarest/study/storage/variantstudy/business/command_reverter.py @@ -94,6 +94,10 @@ def _revert_create_link(base_command: CreateLink, history: t.List["ICommand"], b ) ] + @staticmethod + def _revert_update_link(base_command: CreateLink, history: t.List["ICommand"], base: FileStudy) -> t.List[ICommand]: + raise NotImplementedError("The revert function for UpdateLink is not available") + @staticmethod def _revert_remove_link(base_command: RemoveLink, history: t.List["ICommand"], base: FileStudy) -> t.List[ICommand]: raise NotImplementedError("The revert function for RemoveLink is not available") diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 7774c2d940..08b4025e69 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -83,25 +83,6 @@ class LinkProperties(LinkInfoProperties820): class AbstractLinkCommand(ICommand, metaclass=ABCMeta): - # area1: str - # area2: str - # parameters: Optional[Dict[str, Any]] = None - # series: Optional[Union[List[List[MatrixData]], str]] = None - # direct: Optional[Union[List[List[MatrixData]], str]] = None - # indirect: Optional[Union[List[List[MatrixData]], str]] = None - pass - -class CreateLink(ICommand): - """ - Command used to create a link between two areas. - """ - - # Overloaded metadata - # =================== - - command_name: CommandName = CommandName.CREATE_LINK - version: int = 1 - # Command parameters # ================== @@ -120,11 +101,94 @@ def validate_series( return validate_matrix(v, new_values) if v is not None else v @model_validator(mode="after") - def validate_areas(self) -> "CreateLink": + def validate_areas(self) -> "AbstractLinkCommand": if self.area1 == self.area2: raise ValueError("Cannot create link on same node") return self + def to_dto(self) -> CommandDTO: + args = { + "area1": self.area1, + "area2": self.area2, + "parameters": self.parameters, + } + if self.series: + args["series"] = strip_matrix_protocol(self.series) + if self.direct: + args["direct"] = strip_matrix_protocol(self.direct) + if self.indirect: + args["indirect"] = strip_matrix_protocol(self.indirect) + return CommandDTO( + action=CommandName.CREATE_LINK.value, + args=args, + ) + + def match(self, other: ICommand, equal: bool = False) -> bool: + if not isinstance(other, CreateLink): + return False + simple_match = self.area1 == other.area1 and self.area2 == other.area2 + if not equal: + return simple_match + return ( + simple_match + and self.parameters == other.parameters + and self.series == other.series + and self.direct == other.direct + and self.indirect == other.indirect + ) + + def _create_diff(self, other: "ICommand") -> List["ICommand"]: + other = cast(CreateLink, other) + from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix + from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig + + commands: List[ICommand] = [] + area_from, area_to = sorted([self.area1, self.area2]) + if self.parameters != other.parameters: + properties = LinkProperties.model_validate(other.parameters or {}) + link_property = properties.model_dump(mode="json", by_alias=True, exclude_none=True) + commands.append( + UpdateConfig( + target=f"input/links/{area_from}/properties/{area_to}", + data=link_property, + command_context=self.command_context, + ) + ) + if self.series != other.series: + commands.append( + ReplaceMatrix( + target=f"@links_series/{area_from}/{area_to}", + matrix=strip_matrix_protocol(other.series), + command_context=self.command_context, + ) + ) + return commands + + def get_inner_matrices(self) -> List[str]: + list_matrices = [] + if self.series: + assert_this(isinstance(self.series, str)) + list_matrices.append(strip_matrix_protocol(self.series)) + if self.direct: + assert_this(isinstance(self.direct, str)) + list_matrices.append(strip_matrix_protocol(self.direct)) + if self.indirect: + assert_this(isinstance(self.indirect, str)) + list_matrices.append(strip_matrix_protocol(self.indirect)) + return list_matrices + + +class CreateLink(AbstractLinkCommand): + """ + Command used to create a link between two areas. + """ + + # Overloaded metadata + # =================== + + command_name: CommandName = CommandName.CREATE_LINK + version: int = 1 + def _create_link_in_config(self, area_from: str, area_to: str, study_data: FileStudyTreeConfig) -> None: self.parameters = self.parameters or {} study_data.areas[area_from].links[area_to] = Link( @@ -276,55 +340,10 @@ def match_signature(self) -> str: ) def match(self, other: ICommand, equal: bool = False) -> bool: - if not isinstance(other, CreateLink): - return False - simple_match = self.area1 == other.area1 and self.area2 == other.area2 - if not equal: - return simple_match - return ( - simple_match - and self.parameters == other.parameters - and self.series == other.series - and self.direct == other.direct - and self.indirect == other.indirect - ) + return super().match(other, equal) def _create_diff(self, other: "ICommand") -> List["ICommand"]: - other = cast(CreateLink, other) - from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix - from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig - - commands: List[ICommand] = [] - area_from, area_to = sorted([self.area1, self.area2]) - if self.parameters != other.parameters: - properties = LinkProperties.model_validate(other.parameters or {}) - link_property = properties.model_dump(mode="json", by_alias=True, exclude_none=True) - commands.append( - UpdateConfig( - target=f"input/links/{area_from}/properties/{area_to}", - data=link_property, - command_context=self.command_context, - ) - ) - if self.series != other.series: - commands.append( - ReplaceMatrix( - target=f"@links_series/{area_from}/{area_to}", - matrix=strip_matrix_protocol(other.series), - command_context=self.command_context, - ) - ) - return commands + return super()._create_diff(other) def get_inner_matrices(self) -> List[str]: - list_matrices = [] - if self.series: - assert_this(isinstance(self.series, str)) - list_matrices.append(strip_matrix_protocol(self.series)) - if self.direct: - assert_this(isinstance(self.direct, str)) - list_matrices.append(strip_matrix_protocol(self.direct)) - if self.indirect: - assert_this(isinstance(self.indirect, str)) - list_matrices.append(strip_matrix_protocol(self.indirect)) - return list_matrices + return super().get_inner_matrices() diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 4d0b034e48..51e0fe0bd8 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -11,14 +11,17 @@ # This file is part of the Antares project. import typing as t +from antarest.study.model import STUDY_VERSION_8_2 from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy +from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput +from antarest.study.storage.variantstudy.model.command.create_link import AbstractLinkCommand from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand, OutputTuple from antarest.study.storage.variantstudy.model.model import CommandDTO -class UpdateLink(ICommand): +class UpdateLink(AbstractLinkCommand): """ Command used to create a link between two areas. """ @@ -29,12 +32,6 @@ class UpdateLink(ICommand): command_name: CommandName = CommandName.UPDATE_LINK version: int = 1 - # Command parameters - # ================== - area1: str - area2: str - parameters: t.Optional[t.Dict[str, t.Any]] = None - def _apply_config(self, study_data: FileStudyTreeConfig) -> OutputTuple: area_from, area_to = sorted([self.area1, self.area2]) @@ -47,9 +44,51 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> OutputTuple: ) def _apply(self, study_data: FileStudy) -> CommandOutput: + version = study_data.config.version area_from, area_to = sorted([self.area1, self.area2]) study_data.tree.save(self.parameters, ["input", "links", area_from, "properties", area_to]) output, _ = self._apply_config(study_data.config) + + self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) + self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) + self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) + + assert type(self.series) is str + if version < STUDY_VERSION_8_2: + study_data.tree.save(self.series, ["input", "links", area_from, area_to]) + else: + study_data.tree.save( + self.series, + ["input", "links", area_from, f"{area_to}_parameters"], + ) + + study_data.tree.save({}, ["input", "links", area_from, "capacities"]) + if self.direct: + assert isinstance(self.direct, str) + study_data.tree.save( + self.direct, + [ + "input", + "links", + area_from, + "capacities", + f"{area_to}_direct", + ], + ) + + if self.indirect: + assert isinstance(self.indirect, str) + study_data.tree.save( + self.indirect, + [ + "input", + "links", + area_from, + "capacities", + f"{area_to}_indirect", + ], + ) + return output def to_dto(self) -> CommandDTO: @@ -58,6 +97,12 @@ def to_dto(self) -> CommandDTO: "area2": self.area2, "parameters": self.parameters, } + if self.series: + args["series"] = strip_matrix_protocol(self.series) + if self.direct: + args["direct"] = strip_matrix_protocol(self.direct) + if self.indirect: + args["indirect"] = strip_matrix_protocol(self.indirect) return CommandDTO( action=CommandName.UPDATE_LINK.value, args=args, @@ -69,7 +114,7 @@ def match_signature(self) -> str: ) def _create_diff(self, other: "ICommand") -> t.List["ICommand"]: - pass + return super()._create_diff(other) def get_inner_matrices(self) -> t.List[str]: - pass \ No newline at end of file + return super().get_inner_matrices() diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index e632a9897f..8dc5884de5 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -19,7 +19,6 @@ @pytest.mark.unit_test class TestLink: - @pytest.mark.parametrize("study_type", ["raw", "variant"]) def test_link_update(self, client: TestClient, user_access_token: str, study_type: str) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore diff --git a/tests/variantstudy/test_command_factory.py b/tests/variantstudy/test_command_factory.py index b78ba393e5..26992ebfb5 100644 --- a/tests/variantstudy/test_command_factory.py +++ b/tests/variantstudy/test_command_factory.py @@ -94,6 +94,17 @@ } ], ), + CommandDTO( + action=CommandName.UPDATE_LINK.value, + args=[ + { + "area1": "area1", + "area2": "area2", + "parameters": {}, + "series": "series", + } + ], + ), CommandDTO( action=CommandName.REMOVE_LINK.value, args={ @@ -411,7 +422,7 @@ def _get_command_classes(self) -> Set[str]: f".{name}", package="antarest.study.storage.variantstudy.model.command", ) - abstract_commands = {"AbstractBindingConstraintCommand"} + abstract_commands = {"AbstractBindingConstraintCommand", "AbstractLinkCommand"} return {cmd.__name__ for cmd in ICommand.__subclasses__() if cmd.__name__ not in abstract_commands} def test_all_commands_are_tested(self, command_factory: CommandFactory): From 213a1b41bd9ea168c3d85b9d5ae342b69b77a44b Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 8 Oct 2024 15:51:56 +0200 Subject: [PATCH 042/103] isort --- antarest/study/business/link_management.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 48b16647ae..39832ccfdf 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -140,8 +140,12 @@ def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L def check_attributes_coherence(self, study_version: int, link_creation_info: LinkInfoDTOType) -> None: if study_version < 820: - if link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None: - raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") + if isinstance(link_creation_info, LinkInfoDTO820): + if ( + link_creation_info.filter_synthesis is not None + or link_creation_info.filter_year_by_year is not None + ): + raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") def get_one_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: file_study = self.storage_service.get_storage(study).get_raw(study) From a56337ce0db1a36a169a6524925f74451e63fe71 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 9 Oct 2024 14:51:31 +0200 Subject: [PATCH 043/103] adapt to new handling of study_versions --- antarest/study/business/link_management.py | 12 ++++++------ .../variantstudy/model/command/create_link.py | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index a18ecd1e54..f9a038bcec 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,12 +12,13 @@ import typing as t +from antares.study.version import StudyVersion + from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError from antarest.core.model import JSON -from antarest.core.serialization import AntaresBaseModel from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands -from antarest.study.model import RawStudy +from antarest.study.model import STUDY_VERSION_8_2, RawStudy from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions @@ -70,7 +71,7 @@ def get_all_links(self, study: RawStudy) -> t.List[LinkInfoDTOType]: link_creation_data.update(link_properties) link_data: LinkInfoDTOType - if int(study.version) < 820: + if StudyVersion.parse(study.version) < STUDY_VERSION_8_2: link_data = LinkInfoDTOBase(**link_creation_data) else: link_data = LinkInfoDTO820(**link_creation_data) @@ -83,15 +84,14 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L if link_creation_info.area1 == link_creation_info.area2: raise LinkValidationError(f"Cannot create link on same area: {link_creation_info.area1}") - study_version = int(study.version) - if study_version < 820 and isinstance(link_creation_info, LinkInfoDTO820): + if StudyVersion.parse(study.version) < STUDY_VERSION_8_2 and isinstance(link_creation_info, LinkInfoDTO820): if link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None: raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") link_info = link_creation_info.model_dump(exclude_none=True, by_alias=True) link_data: LinkInfoDTOType - if study_version < 820: + if StudyVersion.parse(study.version) < STUDY_VERSION_8_2: link_data = LinkInfoDTOBase(**link_info) else: link_data = LinkInfoDTO820(**link_info) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index fc52db1a49..397094dc2a 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -12,6 +12,7 @@ import typing as t from typing import Any, Dict, List, Optional, Tuple, Union, cast +from antares.study.version import StudyVersion from pydantic import Field, ValidationInfo, field_validator, model_validator from antarest.core.exceptions import LinkValidationError @@ -197,7 +198,9 @@ def _apply(self, study_data: FileStudy) -> CommandOutput: area_to = data["area_to"] properties = LinkProperties.model_validate(self.parameters or {}) - excludes = set() if version >= 820 else {"filter_synthesis", "filter_year_by_year"} + excludes = ( + set() if StudyVersion.parse(version) >= STUDY_VERSION_8_2 else {"filter_synthesis", "filter_year_by_year"} + ) link_property = properties.model_dump(mode="json", exclude=excludes, by_alias=True, exclude_none=True) study_data.tree.save(link_property, ["input", "links", area_from, "properties", area_to]) From 7a842d6d8c310d15f2bcf4adfa9fac21f78a097e Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 9 Oct 2024 17:20:12 +0200 Subject: [PATCH 044/103] refactor series saving and to_dto method --- antarest/study/business/link_management.py | 11 +-- .../variantstudy/model/command/create_link.py | 99 +++++++++---------- .../variantstudy/model/command/update_link.py | 63 ++---------- 3 files changed, 58 insertions(+), 115 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index dacda491c3..8dc30f0701 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -121,21 +121,20 @@ def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L file_study = self.storage_service.get_storage(study).get_raw(study) existing_link = self.get_one_link(study, link_creation_info) - existing_link = existing_link.model_copy( - update=link_creation_info.model_dump(exclude={"area1", "area2"}, exclude_none=True) - ) - command = UpdateLink( area1=link_creation_info.area1, area2=link_creation_info.area2, - parameters=existing_link.model_dump( - mode="json", exclude={"area1", "area2"}, exclude_none=True, by_alias=True + parameters=link_creation_info.model_dump( + mode="json", exclude={"area1", "area2"}, exclude_none=True, by_alias=True, exclude_unset=True ), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) execute_or_add_commands(study, file_study, [command], self.storage_service) + existing_link = existing_link.model_copy( + update=link_creation_info.model_dump(exclude={"area1", "area2"}, exclude_none=True) + ) return existing_link def check_attributes_coherence(self, study_version: int, link_creation_info: LinkInfoDTOType) -> None: diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 64f53091ca..e660c068db 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -84,6 +84,8 @@ class LinkProperties(LinkInfoProperties820): class AbstractLinkCommand(ICommand, metaclass=ABCMeta): + command_name: CommandName + # Command parameters # ================== @@ -120,7 +122,7 @@ def to_dto(self) -> CommandDTO: if self.indirect: args["indirect"] = strip_matrix_protocol(self.indirect) return CommandDTO( - action=CommandName.CREATE_LINK.value, + action=self.command_name.value, args=args, ) @@ -178,6 +180,46 @@ def get_inner_matrices(self) -> List[str]: list_matrices.append(strip_matrix_protocol(self.indirect)) return list_matrices + def save_series(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: + self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) + self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) + self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) + assert type(self.series) is str + if version < STUDY_VERSION_8_2: + study_data.tree.save(self.series, ["input", "links", area_from, area_to]) + else: + study_data.tree.save( + self.series, + ["input", "links", area_from, f"{area_to}_parameters"], + ) + + study_data.tree.save({}, ["input", "links", area_from, "capacities"]) + if self.direct: + assert isinstance(self.direct, str) + study_data.tree.save( + self.direct, + [ + "input", + "links", + area_from, + "capacities", + f"{area_to}_direct", + ], + ) + + if self.indirect: + assert isinstance(self.indirect, str) + study_data.tree.save( + self.indirect, + [ + "input", + "links", + area_from, + "capacities", + f"{area_to}_indirect", + ], + ) + class CreateLink(AbstractLinkCommand): """ @@ -278,64 +320,13 @@ def _apply(self, study_data: FileStudy) -> CommandOutput: link_property = properties.model_dump(mode="json", exclude=excludes, by_alias=True, exclude_none=True) study_data.tree.save(link_property, ["input", "links", area_from, "properties", area_to]) - self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) - self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) - self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) - - assert type(self.series) is str - if version < STUDY_VERSION_8_2: - study_data.tree.save(self.series, ["input", "links", area_from, area_to]) - else: - study_data.tree.save( - self.series, - ["input", "links", area_from, f"{area_to}_parameters"], - ) - study_data.tree.save({}, ["input", "links", area_from, "capacities"]) - if self.direct: - assert isinstance(self.direct, str) - study_data.tree.save( - self.direct, - [ - "input", - "links", - area_from, - "capacities", - f"{area_to}_direct", - ], - ) - - if self.indirect: - assert isinstance(self.indirect, str) - study_data.tree.save( - self.indirect, - [ - "input", - "links", - area_from, - "capacities", - f"{area_to}_indirect", - ], - ) + self.save_series(area_from, area_to, study_data, version) return output def to_dto(self) -> CommandDTO: - args = { - "area1": self.area1, - "area2": self.area2, - "parameters": self.parameters, - } - if self.series: - args["series"] = strip_matrix_protocol(self.series) - if self.direct: - args["direct"] = strip_matrix_protocol(self.direct) - if self.indirect: - args["indirect"] = strip_matrix_protocol(self.indirect) - return CommandDTO( - action=CommandName.CREATE_LINK.value, - args=args, - ) + return super().to_dto() def match_signature(self) -> str: return str( diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 51e0fe0bd8..cbcf025e74 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -46,67 +46,20 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> OutputTuple: def _apply(self, study_data: FileStudy) -> CommandOutput: version = study_data.config.version area_from, area_to = sorted([self.area1, self.area2]) - study_data.tree.save(self.parameters, ["input", "links", area_from, "properties", area_to]) + + current_parameters = study_data.tree.get(["input", "links", area_from, "properties", area_to]) + current_parameters.update(self.parameters) + + study_data.tree.save(current_parameters, ["input", "links", area_from, "properties", area_to]) + output, _ = self._apply_config(study_data.config) - self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) - self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) - self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) - - assert type(self.series) is str - if version < STUDY_VERSION_8_2: - study_data.tree.save(self.series, ["input", "links", area_from, area_to]) - else: - study_data.tree.save( - self.series, - ["input", "links", area_from, f"{area_to}_parameters"], - ) - - study_data.tree.save({}, ["input", "links", area_from, "capacities"]) - if self.direct: - assert isinstance(self.direct, str) - study_data.tree.save( - self.direct, - [ - "input", - "links", - area_from, - "capacities", - f"{area_to}_direct", - ], - ) - - if self.indirect: - assert isinstance(self.indirect, str) - study_data.tree.save( - self.indirect, - [ - "input", - "links", - area_from, - "capacities", - f"{area_to}_indirect", - ], - ) + self.save_series(area_from, area_to, study_data, version) return output def to_dto(self) -> CommandDTO: - args = { - "area1": self.area1, - "area2": self.area2, - "parameters": self.parameters, - } - if self.series: - args["series"] = strip_matrix_protocol(self.series) - if self.direct: - args["direct"] = strip_matrix_protocol(self.direct) - if self.indirect: - args["indirect"] = strip_matrix_protocol(self.indirect) - return CommandDTO( - action=CommandName.UPDATE_LINK.value, - args=args, - ) + return super().to_dto() def match_signature(self) -> str: return str( From 77f3195e92ce5a3ee2ffd19b24949d418dd2f4e5 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 9 Oct 2024 17:26:58 +0200 Subject: [PATCH 045/103] refactor series saving and to_dto method --- .../study/storage/variantstudy/model/command/update_link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index cbcf025e74..076bc144db 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -48,7 +48,7 @@ def _apply(self, study_data: FileStudy) -> CommandOutput: area_from, area_to = sorted([self.area1, self.area2]) current_parameters = study_data.tree.get(["input", "links", area_from, "properties", area_to]) - current_parameters.update(self.parameters) + current_parameters.update(self.parameters or {}) study_data.tree.save(current_parameters, ["input", "links", area_from, "properties", area_to]) From 7ac29159ec5f19a9e81aa78027a82672c91e3886 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 10 Oct 2024 15:28:49 +0200 Subject: [PATCH 046/103] add tests --- antarest/study/business/link_management.py | 23 +++++++++----- .../variantstudy/model/command/update_link.py | 2 +- .../study_data_blueprint/test_link.py | 31 +++++++++++++++++++ 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 8dc30f0701..977724bf69 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -20,6 +20,7 @@ from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import STUDY_VERSION_8_2, RawStudy from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties +from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_link import ( @@ -116,10 +117,9 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L return link_data def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: - self.check_attributes_coherence(int(study.version), link_creation_info) - file_study = self.storage_service.get_storage(study).get_raw(study) - existing_link = self.get_one_link(study, link_creation_info) + + self.check_attributes_coherence(file_study, int(study.version), link_creation_info) command = UpdateLink( area1=link_creation_info.area1, @@ -132,12 +132,21 @@ def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L execute_or_add_commands(study, file_study, [command], self.storage_service) - existing_link = existing_link.model_copy( - update=link_creation_info.model_dump(exclude={"area1", "area2"}, exclude_none=True) - ) + existing_link = self.get_one_link(study, link_creation_info) return existing_link - def check_attributes_coherence(self, study_version: int, link_creation_info: LinkInfoDTOType) -> None: + def check_attributes_coherence( + self, file_study: FileStudy, study_version: int, link_creation_info: LinkInfoDTOType + ) -> None: + if link_creation_info.area1 == link_creation_info.area2: + raise LinkValidationError("Area 1 and Area 2 can not be the same") + + area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) + try: + file_study.tree.get(["input", "links", area_from, "properties", area_to]) + except KeyError: + raise LinkValidationError(f"The link {area_from} -> {area_to} is not present in the study") + if study_version < 820: if isinstance(link_creation_info, LinkInfoDTO820): if ( diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 076bc144db..3d55413974 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -43,7 +43,7 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> OutputTuple: {"area_from": area_from, "area_to": area_to}, ) - def _apply(self, study_data: FileStudy) -> CommandOutput: + def _apply(self, study_data: FileStudy, *a: str) -> CommandOutput: version = study_data.config.version area_from, area_to = sorted([self.area1, self.area2]) diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 8dc5884de5..5bfd44ff00 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -62,6 +62,37 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ } assert expected == res.json() + # Test update link same area + + res = client.put( + f"/v1/studies/{study_id}/links", + json={ + "area1": area1_id, + "area2": area1_id, + "hurdles-cost": False, + }, + ) + assert res.status_code == 422 + expected = {"description": "Area 1 and Area 2 can not be the same", "exception": "LinkValidationError"} + assert expected == res.json() + + # Test update link with non existing area + + res = client.put( + f"/v1/studies/{study_id}/links", + json={ + "area1": area1_id, + "area2": "id_do_not_exist", + "hurdles-cost": False, + }, + ) + assert res.status_code == 422 + expected = { + "description": "The link area 1 -> id_do_not_exist is not present in the study", + "exception": "LinkValidationError", + } + assert expected == res.json() + @pytest.mark.parametrize("study_type", ["raw", "variant"]) def test_link_820(self, client: TestClient, user_access_token: str, study_type: str) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore From 70de77801fbe19424c5e713dec557a8036cd21a5 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 17 Oct 2024 17:37:51 +0200 Subject: [PATCH 047/103] code review changes --- antarest/study/business/link_management.py | 6 +++--- .../study/storage/variantstudy/model/command/create_link.py | 2 +- .../study/storage/variantstudy/model/command/update_link.py | 2 +- antarest/study/web/study_data_blueprint.py | 3 ++- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 977724bf69..c83fecc080 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -119,7 +119,7 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: file_study = self.storage_service.get_storage(study).get_raw(study) - self.check_attributes_coherence(file_study, int(study.version), link_creation_info) + self.check_attributes_coherence(file_study, StudyVersion.parse(study.version), link_creation_info) command = UpdateLink( area1=link_creation_info.area1, @@ -136,7 +136,7 @@ def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L return existing_link def check_attributes_coherence( - self, file_study: FileStudy, study_version: int, link_creation_info: LinkInfoDTOType + self, file_study: FileStudy, study_version: StudyVersion, link_creation_info: LinkInfoDTOType ) -> None: if link_creation_info.area1 == link_creation_info.area2: raise LinkValidationError("Area 1 and Area 2 can not be the same") @@ -147,7 +147,7 @@ def check_attributes_coherence( except KeyError: raise LinkValidationError(f"The link {area_from} -> {area_to} is not present in the study") - if study_version < 820: + if study_version < STUDY_VERSION_8_2: if isinstance(link_creation_info, LinkInfoDTO820): if ( link_creation_info.filter_synthesis is not None diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index e660c068db..bfd27da678 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -184,7 +184,7 @@ def save_series(self, area_from: str, area_to: str, study_data: FileStudy, versi self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) - assert type(self.series) is str + assert isinstance(self.series, str) if version < STUDY_VERSION_8_2: study_data.tree.save(self.series, ["input", "links", area_from, area_to]) else: diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 3d55413974..041526ba71 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -23,7 +23,7 @@ class UpdateLink(AbstractLinkCommand): """ - Command used to create a link between two areas. + Command used to update a link between two areas. """ # Overloaded metadata diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 1d568b92be..9fec56af18 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -208,8 +208,9 @@ def update_link( link_creation_info: LinkInfoDTOType, current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Any: + area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) logger.info( - f"Updating link for study {uuid}", + f"Updating link {area_from} -> {area_to} for study {uuid}", extra={"user": current_user.id}, ) params = RequestParameters(user=current_user) From a7e4d214e2bc4cb0045891f6b67da684e488d147 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Fri, 18 Oct 2024 14:36:29 +0200 Subject: [PATCH 048/103] code review changes --- .../variantstudy/model/command/create_link.py | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index bfd27da678..3733d748d7 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -31,6 +31,7 @@ from antarest.study.storage.variantstudy.model.model import CommandDTO DEFAULT_COLOR = 112 +MATRIX_ATTRIBUTES = ["series", "direct", "indirect"] class AreaInfo(AntaresBaseModel): @@ -115,12 +116,10 @@ def to_dto(self) -> CommandDTO: "area2": self.area2, "parameters": self.parameters, } - if self.series: - args["series"] = strip_matrix_protocol(self.series) - if self.direct: - args["direct"] = strip_matrix_protocol(self.direct) - if self.indirect: - args["indirect"] = strip_matrix_protocol(self.indirect) + for attr in MATRIX_ATTRIBUTES: + value = getattr(self, attr, None) + if value: + args[attr] = strip_matrix_protocol(value) return CommandDTO( action=self.command_name.value, args=args, @@ -169,15 +168,11 @@ def _create_diff(self, other: "ICommand") -> List["ICommand"]: def get_inner_matrices(self) -> List[str]: list_matrices = [] - if self.series: - assert_this(isinstance(self.series, str)) - list_matrices.append(strip_matrix_protocol(self.series)) - if self.direct: - assert_this(isinstance(self.direct, str)) - list_matrices.append(strip_matrix_protocol(self.direct)) - if self.indirect: - assert_this(isinstance(self.indirect, str)) - list_matrices.append(strip_matrix_protocol(self.indirect)) + for attr in MATRIX_ATTRIBUTES: + value = getattr(self, attr, None) + if value: + assert_this(isinstance(value, str)) + list_matrices.append(strip_matrix_protocol(value)) return list_matrices def save_series(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: From d96779fe3f424656bf47ee25c6becbdfa7cc7645 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 21 Oct 2024 13:36:34 +0200 Subject: [PATCH 049/103] code review changes --- .../variantstudy/model/command/create_link.py | 4 +- .../variantstudy/model/command/update_link.py | 18 +++++--- .../study_data_blueprint/test_link.py | 41 +++++++++++++++++++ 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 3733d748d7..fc4c2ea6d3 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -14,7 +14,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union, cast from antares.study.version import StudyVersion -from pydantic import Field, ValidationInfo, field_validator, model_validator +from pydantic import Field, ValidationInfo, field_validator, model_validator, ConfigDict from antarest.core.exceptions import LinkValidationError from antarest.core.serialization import AntaresBaseModel @@ -81,7 +81,7 @@ def validate_individual_filters(cls, value: Optional[str]) -> Optional[str]: class LinkProperties(LinkInfoProperties820): - pass + model_config = ConfigDict(extra="forbid") class AbstractLinkCommand(ICommand, metaclass=ABCMeta): diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 041526ba71..96f3713499 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -11,12 +11,13 @@ # This file is part of the Antares project. import typing as t -from antarest.study.model import STUDY_VERSION_8_2 +from pydantic import ValidationError + +from antarest.core.exceptions import LinkValidationError from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy -from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput -from antarest.study.storage.variantstudy.model.command.create_link import AbstractLinkCommand +from antarest.study.storage.variantstudy.model.command.create_link import AbstractLinkCommand, LinkProperties from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand, OutputTuple from antarest.study.storage.variantstudy.model.model import CommandDTO @@ -47,14 +48,21 @@ def _apply(self, study_data: FileStudy, *a: str) -> CommandOutput: version = study_data.config.version area_from, area_to = sorted([self.area1, self.area2]) + try: + properties = LinkProperties.model_validate(self.parameters or {}).model_dump(exclude_unset=True, by_alias=True) + except ValidationError: + raise LinkValidationError("One or more fields are forbidden") + current_parameters = study_data.tree.get(["input", "links", area_from, "properties", area_to]) - current_parameters.update(self.parameters or {}) + + current_parameters.update(properties or {}) study_data.tree.save(current_parameters, ["input", "links", area_from, "properties", area_to]) output, _ = self._apply_config(study_data.config) - self.save_series(area_from, area_to, study_data, version) + if any([self.series, self.direct, self.indirect]): + self.save_series(area_from, area_to, study_data, version) return output diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 5bfd44ff00..d0aa54e17d 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -93,6 +93,47 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ } assert expected == res.json() + # Test update link fails when given wrong parameters + if study_type == "raw": + res = client.post( + f"/v1/studies/{study_id}/commands", + json=[ + { + "action": "update_link", + "args": { + "area1": area1_id, + "area2": area2_id, + "parameters": { + "hurdles-cost": False, + "wrong": "parameter" + }, + }, + } + ], + ) + assert res.status_code == 500 + expected = {'description': 'Unexpected exception occurred when trying to apply command CommandName.UPDATE_LINK: 422: One or more fields are forbidden', 'exception': 'CommandApplicationError'} + assert expected == res.json() + + # Test update link variant returns only modified values + + if study_type == "variant": + res = client.put( + f"/v1/studies/{study_id}/links", + json={ + "area1": area1_id, + "area2": area2_id, + "hurdles-cost": False, + }, + ) + assert res.status_code == 200 + + res = client.get(f"/v1/studies/{study_id}/commands") + commands = res.json() + command_args = commands[-1]["args"] + assert command_args["parameters"]["hurdles-cost"] == False + assert "loop-flow" not in command_args["parameters"] + @pytest.mark.parametrize("study_type", ["raw", "variant"]) def test_link_820(self, client: TestClient, user_access_token: str, study_type: str) -> None: client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore From 9e4ebfc2751226dbc2e95ea4c7f1550b40dab027 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 21 Oct 2024 13:36:53 +0200 Subject: [PATCH 050/103] code review changes --- .../storage/variantstudy/model/command/create_link.py | 2 +- .../storage/variantstudy/model/command/update_link.py | 4 +++- tests/integration/study_data_blueprint/test_link.py | 10 +++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index fc4c2ea6d3..b08434f975 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -14,7 +14,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union, cast from antares.study.version import StudyVersion -from pydantic import Field, ValidationInfo, field_validator, model_validator, ConfigDict +from pydantic import ConfigDict, Field, ValidationInfo, field_validator, model_validator from antarest.core.exceptions import LinkValidationError from antarest.core.serialization import AntaresBaseModel diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 96f3713499..13ff57a59e 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -49,7 +49,9 @@ def _apply(self, study_data: FileStudy, *a: str) -> CommandOutput: area_from, area_to = sorted([self.area1, self.area2]) try: - properties = LinkProperties.model_validate(self.parameters or {}).model_dump(exclude_unset=True, by_alias=True) + properties = LinkProperties.model_validate(self.parameters or {}).model_dump( + exclude_unset=True, by_alias=True + ) except ValidationError: raise LinkValidationError("One or more fields are forbidden") diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index d0aa54e17d..1541cc6396 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -103,16 +103,16 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ "args": { "area1": area1_id, "area2": area2_id, - "parameters": { - "hurdles-cost": False, - "wrong": "parameter" - }, + "parameters": {"hurdles-cost": False, "wrong": "parameter"}, }, } ], ) assert res.status_code == 500 - expected = {'description': 'Unexpected exception occurred when trying to apply command CommandName.UPDATE_LINK: 422: One or more fields are forbidden', 'exception': 'CommandApplicationError'} + expected = { + "description": "Unexpected exception occurred when trying to apply command CommandName.UPDATE_LINK: 422: One or more fields are forbidden", + "exception": "CommandApplicationError", + } assert expected == res.json() # Test update link variant returns only modified values From f7468cfa2daaefb8f4a6a6e2e26dc0e817bd1ba7 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 22 Oct 2024 10:24:37 +0200 Subject: [PATCH 051/103] Refactor link creation and updating logic Split `save_series` method into three distinct methods: `save_series`, `save_direct`, and `save_indirect`. Updated the `update_link` command to handle the new methods and fixed an error message in the integration tests. --- .../variantstudy/model/command/create_link.py | 64 ++++++++++--------- .../variantstudy/model/command/update_link.py | 17 ++--- .../study_data_blueprint/test_link.py | 6 +- 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index b08434f975..341102ea63 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -117,8 +117,7 @@ def to_dto(self) -> CommandDTO: "parameters": self.parameters, } for attr in MATRIX_ATTRIBUTES: - value = getattr(self, attr, None) - if value: + if value := getattr(self, attr, None): args[attr] = strip_matrix_protocol(value) return CommandDTO( action=self.command_name.value, @@ -169,16 +168,13 @@ def _create_diff(self, other: "ICommand") -> List["ICommand"]: def get_inner_matrices(self) -> List[str]: list_matrices = [] for attr in MATRIX_ATTRIBUTES: - value = getattr(self, attr, None) - if value: + if value := getattr(self, attr, None): assert_this(isinstance(value, str)) list_matrices.append(strip_matrix_protocol(value)) return list_matrices def save_series(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) - self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) - self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) assert isinstance(self.series, str) if version < STUDY_VERSION_8_2: study_data.tree.save(self.series, ["input", "links", area_from, area_to]) @@ -187,33 +183,39 @@ def save_series(self, area_from: str, area_to: str, study_data: FileStudy, versi self.series, ["input", "links", area_from, f"{area_to}_parameters"], ) + study_data.tree.save({}, ["input", "links", area_from, "capacities"]) + def save_direct(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: + self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) + assert isinstance(self.direct, str) + if version >= STUDY_VERSION_8_2: study_data.tree.save({}, ["input", "links", area_from, "capacities"]) - if self.direct: - assert isinstance(self.direct, str) - study_data.tree.save( - self.direct, - [ - "input", - "links", - area_from, - "capacities", - f"{area_to}_direct", - ], - ) + study_data.tree.save( + self.direct, + [ + "input", + "links", + area_from, + "capacities", + f"{area_to}_direct", + ], + ) - if self.indirect: - assert isinstance(self.indirect, str) - study_data.tree.save( - self.indirect, - [ - "input", - "links", - area_from, - "capacities", - f"{area_to}_indirect", - ], - ) + def save_indirect(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: + self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) + assert isinstance(self.indirect, str) + if version >= STUDY_VERSION_8_2: + study_data.tree.save({}, ["input", "links", area_from, "capacities"]) + study_data.tree.save( + self.indirect, + [ + "input", + "links", + area_from, + "capacities", + f"{area_to}_indirect", + ], + ) class CreateLink(AbstractLinkCommand): @@ -317,6 +319,8 @@ def _apply(self, study_data: FileStudy) -> CommandOutput: study_data.tree.save(link_property, ["input", "links", area_from, "properties", area_to]) self.save_series(area_from, area_to, study_data, version) + self.save_direct(area_from, area_to, study_data, version) + self.save_indirect(area_from, area_to, study_data, version) return output diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 13ff57a59e..9209e47a43 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -48,24 +48,25 @@ def _apply(self, study_data: FileStudy, *a: str) -> CommandOutput: version = study_data.config.version area_from, area_to = sorted([self.area1, self.area2]) - try: - properties = LinkProperties.model_validate(self.parameters or {}).model_dump( - exclude_unset=True, by_alias=True - ) - except ValidationError: - raise LinkValidationError("One or more fields are forbidden") + properties = LinkProperties.model_validate(self.parameters or {}).model_dump(exclude_unset=True, by_alias=True) current_parameters = study_data.tree.get(["input", "links", area_from, "properties", area_to]) - current_parameters.update(properties or {}) + current_parameters.update(properties) study_data.tree.save(current_parameters, ["input", "links", area_from, "properties", area_to]) output, _ = self._apply_config(study_data.config) - if any([self.series, self.direct, self.indirect]): + if self.series: self.save_series(area_from, area_to, study_data, version) + if self.direct: + self.save_direct_series(area_from, area_to, study_data, version) + + if self.indirect: + self.save_indirect_series(area_from, area_to, study_data, version) + return output def to_dto(self) -> CommandDTO: diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 1541cc6396..9607771694 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -9,6 +9,7 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. +from sys import stderr import pytest from starlette.testclient import TestClient @@ -110,7 +111,7 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ ) assert res.status_code == 500 expected = { - "description": "Unexpected exception occurred when trying to apply command CommandName.UPDATE_LINK: 422: One or more fields are forbidden", + "description": "Unexpected exception occurred when trying to apply command CommandName.UPDATE_LINK: 1 validation error for LinkProperties\nwrong\n Extra inputs are not permitted [type=extra_forbidden, input_value='parameter', input_type=str]\n For further information visit https://errors.pydantic.dev/2.8/v/extra_forbidden", "exception": "CommandApplicationError", } assert expected == res.json() @@ -131,8 +132,7 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ res = client.get(f"/v1/studies/{study_id}/commands") commands = res.json() command_args = commands[-1]["args"] - assert command_args["parameters"]["hurdles-cost"] == False - assert "loop-flow" not in command_args["parameters"] + assert command_args["parameters"] == {"hurdles-cost": False} @pytest.mark.parametrize("study_type", ["raw", "variant"]) def test_link_820(self, client: TestClient, user_access_token: str, study_type: str) -> None: From 6d0814b9637f2c405294f11aa00673d3bdff57b8 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Sat, 26 Oct 2024 21:47:12 +0200 Subject: [PATCH 052/103] Refactor to include camelcase to the return object --- antarest/study/business/link_management.py | 57 ++++++++++--------- antarest/study/service.py | 8 +-- .../variantstudy/model/command/create_link.py | 15 +++-- antarest/study/web/study_data_blueprint.py | 10 ++-- .../study_data_blueprint/test_link.py | 52 ++++++++--------- tests/integration/test_integration.py | 20 +++---- .../storage/business/test_arealink_manager.py | 40 +++++++------ 7 files changed, 107 insertions(+), 95 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index f9a038bcec..676cacce30 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -13,9 +13,11 @@ import typing as t from antares.study.version import StudyVersion +from pydantic import BaseModel from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError from antarest.core.model import JSON +from antarest.core.utils.string import to_camel_case from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import STUDY_VERSION_8_2, RawStudy @@ -35,14 +37,18 @@ class LinkInfoDTOBase(AreaInfo, LinkInfoProperties): - pass + class Config: + alias_generator = to_camel_case + populate_by_name = True class LinkInfoDTO820(AreaInfo, LinkInfoProperties820): - pass + class Config: + alias_generator = to_camel_case + populate_by_name = True -LinkInfoDTOType = t.Union[LinkInfoDTO820, LinkInfoDTOBase] +LinkInfoDTO = t.Union[LinkInfoDTO820, LinkInfoDTOBase] @all_optional_model @@ -57,30 +63,31 @@ class LinkManager: def __init__(self, storage_service: StudyStorageService) -> None: self.storage_service = storage_service - def get_all_links(self, study: RawStudy) -> t.List[LinkInfoDTOType]: + def get_all_links(self, study: RawStudy) -> t.List[LinkInfoDTO]: file_study = self.storage_service.get_storage(study).get_raw(study) - result: t.List[LinkInfoDTOType] = [] + result: t.List[LinkInfoDTO] = [] for area_id, area in file_study.config.areas.items(): links_config = file_study.tree.get(["input", "links", area_id, "properties"]) for link in area.links: - link_properties = links_config[link] + link_tree_config: t.Dict[str, t.Any] = links_config[link] + link_tree_config.update({"area1": area_id, "area2": link}) - link_creation_data: t.Dict[str, t.Any] = {"area1": area_id, "area2": link} - link_creation_data.update(link_properties) - - link_data: LinkInfoDTOType + link_properties = {"area1": area_id, "area2": link} + link_dto: LinkInfoDTO if StudyVersion.parse(study.version) < STUDY_VERSION_8_2: - link_data = LinkInfoDTOBase(**link_creation_data) + link_properties.update(LinkInfoProperties(**link_tree_config).model_dump()) + link_dto = LinkInfoDTOBase(**link_properties) else: - link_data = LinkInfoDTO820(**link_creation_data) + link_properties.update(LinkInfoProperties820(**link_tree_config).model_dump()) + link_dto = LinkInfoDTO820(**link_properties) - result.append(link_data) + result.append(link_dto) return result - def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: + def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: if link_creation_info.area1 == link_creation_info.area2: raise LinkValidationError(f"Cannot create link on same area: {link_creation_info.area1}") @@ -88,17 +95,15 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L if link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None: raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") - link_info = link_creation_info.model_dump(exclude_none=True, by_alias=True) - - link_data: LinkInfoDTOType - if StudyVersion.parse(study.version) < STUDY_VERSION_8_2: - link_data = LinkInfoDTOBase(**link_info) + link_dto: LinkInfoDTO + if StudyVersion.parse(study.version) >= STUDY_VERSION_8_2 and isinstance(link_creation_info, LinkInfoDTO820): + if link_creation_info.filter_synthesis is None: + link_creation_info.filter_synthesis = FilteringOptions.FILTER_SYNTHESIS + if link_creation_info.filter_year_by_year is None: + link_creation_info.filter_year_by_year = FilteringOptions.FILTER_YEAR_BY_YEAR + link_dto = LinkInfoDTO820(**link_creation_info.model_dump()) else: - link_data = LinkInfoDTO820(**link_info) - if isinstance(link_data, LinkInfoDTO820) and link_data.filter_synthesis is None: - link_data.filter_synthesis = FilteringOptions.FILTER_SYNTHESIS - if isinstance(link_data, LinkInfoDTO820) and link_data.filter_year_by_year is None: - link_data.filter_year_by_year = FilteringOptions.FILTER_YEAR_BY_YEAR + link_dto = LinkInfoDTOBase(**link_creation_info.model_dump()) storage_service = self.storage_service.get_storage(study) file_study = storage_service.get_raw(study) @@ -106,13 +111,13 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L command = CreateLink( area1=link_creation_info.area1, area2=link_creation_info.area2, - parameters=link_data.model_dump(exclude={"area1", "area2"}, exclude_none=True, by_alias=True), + parameters=link_dto.model_dump(exclude_none=True), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) execute_or_add_commands(study, file_study, [command], self.storage_service) - return link_data + return link_dto def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) diff --git a/antarest/study/service.py b/antarest/study/service.py index 96a46021cf..ecf8d71b30 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -85,7 +85,7 @@ from antarest.study.business.correlation_management import CorrelationManager from antarest.study.business.district_manager import DistrictManager from antarest.study.business.general_management import GeneralManager -from antarest.study.business.link_management import LinkInfoDTOType, LinkManager +from antarest.study.business.link_management import LinkInfoDTO, LinkManager, LinkOutput from antarest.study.business.matrix_management import MatrixManager, MatrixManagerError from antarest.study.business.optimization_management import OptimizationManager from antarest.study.business.playlist_management import PlaylistManager @@ -1826,7 +1826,7 @@ def get_all_links( self, uuid: str, params: RequestParameters, - ) -> t.List[LinkInfoDTOType]: + ) -> t.List[LinkInfoDTO]: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.READ) return self.links.get_all_links(study) @@ -1853,9 +1853,9 @@ def create_area( def create_link( self, uuid: str, - link_creation_dto: LinkInfoDTOType, + link_creation_dto: LinkInfoDTO, params: RequestParameters, - ) -> LinkInfoDTOType: + ) -> LinkInfoDTO: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.WRITE) self._assert_study_unarchived(study) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 397094dc2a..5e29cc2683 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -194,15 +194,18 @@ def _apply(self, study_data: FileStudy) -> CommandOutput: output, data = self._apply_config(study_data.config) if not output.status: return output + + properties: LinkInfoProperties + if StudyVersion.parse(version) >= STUDY_VERSION_8_2: + properties = LinkInfoProperties820.model_validate(self.parameters or {}) + else: + properties = LinkInfoProperties.model_validate(self.parameters or {}) + + link_property = properties.model_dump(mode="json", exclude={"area1", "area2"}, by_alias=True, exclude_none=True) + area_from = data["area_from"] area_to = data["area_to"] - properties = LinkProperties.model_validate(self.parameters or {}) - excludes = ( - set() if StudyVersion.parse(version) >= STUDY_VERSION_8_2 else {"filter_synthesis", "filter_year_by_year"} - ) - link_property = properties.model_dump(mode="json", exclude=excludes, by_alias=True, exclude_none=True) - study_data.tree.save(link_property, ["input", "links", area_from, "properties", area_to]) self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 86f6a6999e..e5a096de40 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -68,7 +68,7 @@ ) from antarest.study.business.district_manager import DistrictCreationDTO, DistrictInfoDTO, DistrictUpdateDTO from antarest.study.business.general_management import GeneralFormFields -from antarest.study.business.link_management import LinkInfoDTOType +from antarest.study.business.link_management import LinkInfoDTO, LinkOutput from antarest.study.business.optimization_management import OptimizationFormFields from antarest.study.business.playlist_management import PlaylistColumns from antarest.study.business.scenario_builder_management import Rulesets, ScenarioType @@ -147,7 +147,7 @@ def get_areas( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Get all links", - response_model=t.List[LinkInfoDTOType], + response_model=t.List[LinkInfoDTO], ) def get_links( uuid: str, @@ -183,13 +183,13 @@ def create_area( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Create a link", - response_model=LinkInfoDTOType, + response_model=LinkInfoDTO, ) def create_link( uuid: str, - link_creation_info: LinkInfoDTOType, + link_creation_info: LinkInfoDTO, current_user: JWTUser = Depends(auth.get_current_user), - ) -> t.Any: + ) -> LinkInfoDTO: logger.info( f"Creating new link for study {uuid}", extra={"user": current_user.id}, diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 12bb34fd29..e68bfc9d5d 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -40,19 +40,19 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: expected = { "area1": "area 1", "area2": "area 2", - "asset-type": "ac", + "assetType": "ac", "colorb": 112, "colorg": 112, "colorr": 112, - "display-comments": True, - "filter-synthesis": "hourly, daily, weekly, monthly, annual", - "filter-year-by-year": "hourly, daily, weekly, monthly, annual", - "hurdles-cost": False, - "link-style": "plain", - "link-width": 1.0, - "loop-flow": False, - "transmission-capacities": "enabled", - "use-phase-shifter": False, + "displayComments": True, + "filterSynthesis": "hourly, daily, weekly, monthly, annual", + "filterYearByYear": "hourly, daily, weekly, monthly, annual", + "hurdlesCost": False, + "linkStyle": "plain", + "linkWidth": 1.0, + "loopFlow": False, + "transmissionCapacities": "enabled", + "usePhaseShifter": False, } assert expected == res.json() res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") @@ -61,20 +61,21 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: # Test create link with parameters parameters = { - "area1": area1_id, - "area2": area2_id, - "asset-type": "dc", + "area1": "area 1", + "area2": "area 2", + "assetType": "ac", "colorb": 160, "colorg": 170, "colorr": 180, - "display-comments": True, - "filter-synthesis": "hourly", - "hurdles-cost": True, - "link-style": "plain", - "link-width": 2.0, - "loop-flow": False, - "transmission-capacities": "enabled", - "use-phase-shifter": True, + "displayComments": True, + "filterSynthesis": "hourly, daily, weekly, monthly, annual", + "filterYearByYear": "hourly, daily, weekly, monthly, annual", + "hurdlesCost": False, + "linkStyle": "plain", + "linkWidth": 1.0, + "loopFlow": False, + "transmissionCapacities": "enabled", + "usePhaseShifter": False, } res = client.post( f"/v1/studies/{study_id}/links", @@ -82,7 +83,6 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: ) assert res.status_code == 200, res.json() - parameters["filter-year-by-year"] = "hourly, daily, weekly, monthly, annual" assert parameters == res.json() res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") @@ -123,11 +123,11 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: res = client.post( f"/v1/studies/{study_id}/links", - json={"area1": area1_id, "area2": area1_id, "asset-type": TransmissionCapacity.ENABLED}, + json={"area1": area1_id, "area2": area2_id, "assetType": TransmissionCapacity.ENABLED}, ) assert res.status_code == 422, res.json() expected = { - "body": {"area1": "area 1", "area2": "area 1", "asset-type": "enabled"}, + "body": {"area1": "area 1", "area2": "area 2", "assetType": "enabled"}, "description": "Input should be 'ac', 'dc', 'gaz', 'virt' or 'other'", "exception": "RequestValidationError", } @@ -149,7 +149,7 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: res = client.post( f"/v1/studies/{study_id}/links", - json={"area1": area1_id, "area2": area2_id, "filter-synthesis": "centurial"}, + json={"area1": area1_id, "area2": area2_id, "filterSynthesis": "centurial"}, ) assert res.status_code == 422, res.json() @@ -168,7 +168,7 @@ def test_create_link_810(self, client: TestClient, user_access_token: str) -> No area2_id = preparer.create_area(study_id, name="Area 2")["id"] res = client.post( - f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filter-synthesis": "hourly"} + f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filterSynthesis": "hourly"} ) assert res.status_code == 422, res.json() diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 86afb4902a..0a76b345b4 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -598,19 +598,19 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: { "area1": "area 1", "area2": "area 2", - "asset-type": "ac", + "assetType": "ac", "colorb": 112, "colorg": 112, "colorr": 112, - "display-comments": True, - "filter-synthesis": "hourly, daily, weekly, monthly, annual", - "filter-year-by-year": "hourly, daily, weekly, monthly, annual", - "hurdles-cost": False, - "link-style": "plain", - "link-width": 1.0, - "loop-flow": False, - "transmission-capacities": "enabled", - "use-phase-shifter": False, + "displayComments": True, + "filterSynthesis": "hourly, daily, weekly, monthly, annual", + "filterYearByYear": "hourly, daily, weekly, monthly, annual", + "hurdlesCost": False, + "linkStyle": "plain", + "linkWidth": 1.0, + "loopFlow": False, + "transmissionCapacities": "enabled", + "usePhaseShifter": False, } ] diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 025de3052b..c79a234580 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -244,19 +244,21 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "area1": "test", "area2": "test2", "parameters": { - "hurdles-cost": False, - "loop-flow": False, - "use-phase-shifter": False, - "transmission-capacities": TransmissionCapacity.ENABLED, - "asset-type": AssetType.AC, - "display-comments": True, + "area1": "test", + "area2": "test2", + "hurdles_cost": False, + "loop_flow": False, + "use_phase_shifter": False, + "transmission_capacities": TransmissionCapacity.ENABLED, + "asset_type": AssetType.AC, + "display_comments": True, "colorr": 112, "colorg": 112, "colorb": 112, - "link-width": 1.0, - "link-style": LinkStyle.PLAIN, - "filter-synthesis": "hourly, daily, weekly, monthly, annual", - "filter-year-by-year": "hourly, daily, weekly, monthly, annual", + "link_width": 1.0, + "link_style": LinkStyle.PLAIN, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", }, }, ), @@ -280,17 +282,19 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): "area1": "test", "area2": "test2", "parameters": { - "hurdles-cost": False, - "loop-flow": False, - "use-phase-shifter": False, - "transmission-capacities": TransmissionCapacity.ENABLED, - "asset-type": AssetType.AC, - "display-comments": True, + "area1": "test", + "area2": "test2", + "hurdles_cost": False, + "loop_flow": False, + "use_phase_shifter": False, + "transmission_capacities": TransmissionCapacity.ENABLED, + "asset_type": AssetType.AC, + "display_comments": True, "colorr": 112, "colorg": 112, "colorb": 112, - "link-width": 1.0, - "link-style": LinkStyle.PLAIN, + "link_width": 1.0, + "link_style": LinkStyle.PLAIN, }, }, ), From eb366def0b6de7d96bc81f6cf18d432479639a30 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 28 Oct 2024 10:43:42 +0100 Subject: [PATCH 053/103] Refactor link DTOs and validation logic Updated the structure of LinkInfoDTO classes to use Pydantic's ConfigDict for configuration and streamlined validation logic. Removed unnecessary imports and redundant configurations, while ensuring filter fields are checked appropriately for different study versions. --- antarest/study/business/link_management.py | 29 +++++++------------ .../variantstudy/model/command/create_link.py | 14 ++++----- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 676cacce30..2a17d1e5dd 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -13,7 +13,7 @@ import typing as t from antares.study.version import StudyVersion -from pydantic import BaseModel +from pydantic import ConfigDict from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError from antarest.core.model import JSON @@ -23,7 +23,6 @@ from antarest.study.model import STUDY_VERSION_8_2, RawStudy from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties from antarest.study.storage.storage_service import StudyStorageService -from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_link import ( AreaInfo, CreateLink, @@ -37,15 +36,11 @@ class LinkInfoDTOBase(AreaInfo, LinkInfoProperties): - class Config: - alias_generator = to_camel_case - populate_by_name = True + model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") class LinkInfoDTO820(AreaInfo, LinkInfoProperties820): - class Config: - alias_generator = to_camel_case - populate_by_name = True + model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") LinkInfoDTO = t.Union[LinkInfoDTO820, LinkInfoDTOBase] @@ -91,19 +86,15 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkI if link_creation_info.area1 == link_creation_info.area2: raise LinkValidationError(f"Cannot create link on same area: {link_creation_info.area1}") - if StudyVersion.parse(study.version) < STUDY_VERSION_8_2 and isinstance(link_creation_info, LinkInfoDTO820): - if link_creation_info.filter_synthesis is not None or link_creation_info.filter_year_by_year is not None: - raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") - link_dto: LinkInfoDTO - if StudyVersion.parse(study.version) >= STUDY_VERSION_8_2 and isinstance(link_creation_info, LinkInfoDTO820): - if link_creation_info.filter_synthesis is None: - link_creation_info.filter_synthesis = FilteringOptions.FILTER_SYNTHESIS - if link_creation_info.filter_year_by_year is None: - link_creation_info.filter_year_by_year = FilteringOptions.FILTER_YEAR_BY_YEAR - link_dto = LinkInfoDTO820(**link_creation_info.model_dump()) + if StudyVersion.parse(study.version) >= STUDY_VERSION_8_2: + link_dto = LinkInfoDTO820.model_validate(link_creation_info.model_dump()) else: - link_dto = LinkInfoDTOBase(**link_creation_info.model_dump()) + forbidden_fields = {"filter_synthesis", "filter_year_by_year"} + fields = set(link_creation_info.model_dump(exclude_defaults=True)) + if forbidden_fields & fields: + raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") + link_dto = LinkInfoDTOBase.model_validate(link_creation_info.model_dump(exclude=forbidden_fields)) storage_service = self.storage_service.get_storage(study) file_study = storage_service.get_raw(study) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 5e29cc2683..5a1ebf00de 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -13,7 +13,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union, cast from antares.study.version import StudyVersion -from pydantic import Field, ValidationInfo, field_validator, model_validator +from pydantic import ConfigDict, Field, ValidationInfo, field_validator, model_validator from antarest.core.exceptions import LinkValidationError from antarest.core.serialization import AntaresBaseModel @@ -50,18 +50,14 @@ class LinkInfoProperties(AntaresBaseModel): link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN - class Config: - alias_generator = to_kebab_case - populate_by_name = True + model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True) class LinkInfoProperties820(LinkInfoProperties): - filter_synthesis: t.Optional[str] = None - filter_year_by_year: t.Optional[str] = None + filter_synthesis: t.Optional[str] = "hourly, daily, weekly, monthly, annual" + filter_year_by_year: t.Optional[str] = "hourly, daily, weekly, monthly, annual" - class Config: - alias_generator = to_kebab_case - populate_by_name = True + model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True) @field_validator("filter_synthesis", "filter_year_by_year", mode="before") def validate_individual_filters(cls, value: Optional[str]) -> Optional[str]: From dea67107cdc18d5bef56fe72955a887214cc1f7d Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 28 Oct 2024 10:46:24 +0100 Subject: [PATCH 054/103] Refactor filter values to use a constant list Use a centralized FILTER_VALUES list for filter_synthesis and filter_year_by_year in LinkInfoProperties820. This change simplifies code maintenance and ensures consistency across filter validations. --- .../storage/variantstudy/model/command/create_link.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 5a1ebf00de..3d522b82a8 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -30,7 +30,7 @@ from antarest.study.storage.variantstudy.model.model import CommandDTO DEFAULT_COLOR = 112 - +FILTER_VALUES = ["hourly", "daily", "weekly", "monthly", "annual"] class AreaInfo(AntaresBaseModel): area1: str @@ -54,15 +54,15 @@ class LinkInfoProperties(AntaresBaseModel): class LinkInfoProperties820(LinkInfoProperties): - filter_synthesis: t.Optional[str] = "hourly, daily, weekly, monthly, annual" - filter_year_by_year: t.Optional[str] = "hourly, daily, weekly, monthly, annual" + filter_synthesis: t.Optional[str] = ", ".join(FILTER_VALUES) + filter_year_by_year: t.Optional[str] = ", ".join(FILTER_VALUES) model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True) @field_validator("filter_synthesis", "filter_year_by_year", mode="before") def validate_individual_filters(cls, value: Optional[str]) -> Optional[str]: if value is not None: - filter_values = ["hourly", "daily", "weekly", "monthly", "annual"] + filter_values = FILTER_VALUES options = value.replace(" ", "").split(",") invalid_options = [opt for opt in options if opt not in filter_values] From 0ff40b9ea30161227d5b1ea78fccd7e2f8d5ddc3 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 28 Oct 2024 16:38:59 +0100 Subject: [PATCH 055/103] Refactor filter fields to use enumerations Replaced filter fields with a comma-separated list of enums for better validation and clarity. This enhances type safety and reduces the risk of invalid filter values being used. The previous string-based validation approach has been removed in favor of utility functions for converting and serializing enum lists. --- .../variantstudy/model/command/create_link.py | 75 ++++++++++++++----- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 3d522b82a8..4e567c5d46 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -10,10 +10,19 @@ # # This file is part of the Antares project. import typing as t +from enum import Enum from typing import Any, Dict, List, Optional, Tuple, Union, cast from antares.study.version import StudyVersion -from pydantic import ConfigDict, Field, ValidationInfo, field_validator, model_validator +from pydantic import ( + BeforeValidator, + ConfigDict, + Field, + PlainSerializer, + ValidationInfo, + field_validator, + model_validator, +) from antarest.core.exceptions import LinkValidationError from antarest.core.serialization import AntaresBaseModel @@ -21,7 +30,12 @@ from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData from antarest.study.model import STUDY_VERSION_8_2 -from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, LinkStyle, TransmissionCapacity +from antarest.study.storage.rawstudy.model.filesystem.config.links import ( + AssetType, + FilterOption, + LinkStyle, + TransmissionCapacity, +) from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix @@ -30,7 +44,38 @@ from antarest.study.storage.variantstudy.model.model import CommandDTO DEFAULT_COLOR = 112 -FILTER_VALUES = ["hourly", "daily", "weekly", "monthly", "annual"] +FILTER_VALUES: t.List[FilterOption] = [ + FilterOption.HOURLY, + FilterOption.DAILY, + FilterOption.WEEKLY, + FilterOption.MONTHLY, + FilterOption.ANNUAL, +] + + +def validate_filters( + filter_value: Union[List[FilterOption], str], enum_cls: t.Type[FilterOption] +) -> List[FilterOption]: + if filter_value is not None and isinstance(filter_value, str): + filter_accepted_values = [e for e in enum_cls] + + options = filter_value.replace(" ", "").split(",") + + invalid_options = [opt for opt in options if opt not in filter_accepted_values] + if invalid_options: + raise LinkValidationError( + f"Invalid value(s) in filters: {', '.join(invalid_options)}. " + f"Allowed values are: {', '.join(filter_accepted_values)}." + ) + + return [enum_cls(opt) for opt in options] + + return filter_value + + +def join_with_comma(values: t.List[FilterOption]) -> str: + return ", ".join(value.name.lower() for value in values) + class AreaInfo(AntaresBaseModel): area1: str @@ -53,26 +98,18 @@ class LinkInfoProperties(AntaresBaseModel): model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True) +comma_separated_enum_list = t.Annotated[ + t.List[FilterOption], + BeforeValidator(lambda x: validate_filters(x, FilterOption)), + PlainSerializer(lambda x: join_with_comma(x)), +] + class LinkInfoProperties820(LinkInfoProperties): - filter_synthesis: t.Optional[str] = ", ".join(FILTER_VALUES) - filter_year_by_year: t.Optional[str] = ", ".join(FILTER_VALUES) + filter_synthesis: comma_separated_enum_list = FILTER_VALUES + filter_year_by_year: comma_separated_enum_list = FILTER_VALUES model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True) - @field_validator("filter_synthesis", "filter_year_by_year", mode="before") - def validate_individual_filters(cls, value: Optional[str]) -> Optional[str]: - if value is not None: - filter_values = FILTER_VALUES - - options = value.replace(" ", "").split(",") - invalid_options = [opt for opt in options if opt not in filter_values] - if invalid_options: - raise LinkValidationError( - f"Invalid value(s) in filters: {', '.join(invalid_options)}. " - f"Allowed values are: {', '.join(filter_values)}." - ) - return value - class LinkProperties(LinkInfoProperties820): pass From b8a52e4cc541488d8a0ce44f1de1d84f01d61336 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 28 Oct 2024 16:39:25 +0100 Subject: [PATCH 056/103] Add a missing newline before LinkInfoProperties820 class Ensure code readability and consistency by adding a newline before the definition of the `LinkInfoProperties820` class. This change improves the visual separation and organization of the code. --- antarest/study/storage/variantstudy/model/command/create_link.py | 1 + 1 file changed, 1 insertion(+) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 4e567c5d46..e27551d792 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -104,6 +104,7 @@ class LinkInfoProperties(AntaresBaseModel): PlainSerializer(lambda x: join_with_comma(x)), ] + class LinkInfoProperties820(LinkInfoProperties): filter_synthesis: comma_separated_enum_list = FILTER_VALUES filter_year_by_year: comma_separated_enum_list = FILTER_VALUES From 65f1a8fe7ec91ce06c1b23dba3074951837b0ea4 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 28 Oct 2024 17:30:46 +0100 Subject: [PATCH 057/103] Add a missing newline before LinkInfoProperties820 class Ensure code readability and consistency by adding a newline before the definition of the `LinkInfoProperties820` class. This change improves the visual separation and organization of the code. --- antarest/study/business/link_management.py | 20 ++++------ antarest/study/service.py | 4 +- .../variantstudy/model/command/create_link.py | 2 - .../variantstudy/model/command/update_link.py | 10 ++++- antarest/study/web/study_data_blueprint.py | 4 +- .../study_data_blueprint/test_link.py | 38 ++++++++----------- 6 files changed, 36 insertions(+), 42 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index d3da53ac38..ffb1a0839f 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -112,7 +112,7 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkI return link_dto - def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: + def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: file_study = self.storage_service.get_storage(study).get_raw(study) self.check_attributes_coherence(file_study, StudyVersion.parse(study.version), link_creation_info) @@ -121,7 +121,7 @@ def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L area1=link_creation_info.area1, area2=link_creation_info.area2, parameters=link_creation_info.model_dump( - mode="json", exclude={"area1", "area2"}, exclude_none=True, by_alias=True, exclude_unset=True + mode="json", exclude={"area1", "area2"}, exclude_none=True, exclude_unset=True ), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) @@ -132,7 +132,7 @@ def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> L return existing_link def check_attributes_coherence( - self, file_study: FileStudy, study_version: StudyVersion, link_creation_info: LinkInfoDTOType + self, file_study: FileStudy, study_version: StudyVersion, link_creation_info: LinkInfoDTO ) -> None: if link_creation_info.area1 == link_creation_info.area2: raise LinkValidationError("Area 1 and Area 2 can not be the same") @@ -151,22 +151,18 @@ def check_attributes_coherence( ): raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") - def get_one_link(self, study: RawStudy, link_creation_info: LinkInfoDTOType) -> LinkInfoDTOType: + def get_one_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: file_study = self.storage_service.get_storage(study).get_raw(study) area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) try: - link_config = file_study.tree.get(["input", "links", area_from, "properties", area_to]) + link_updated = LinkInfoProperties.model_validate( + file_study.tree.get(["input", "links", area_from, "properties", area_to]) + ) except KeyError: raise LinkValidationError(f"The link {area_from} -> {area_to} is not present in the study") - link_config["area1"] = area_from - link_config["area2"] = area_to - - link_data: LinkInfoDTOType - if int(study.version) < 820: - return LinkInfoDTOBase.model_validate(link_config) - return LinkInfoDTO820.model_validate(link_config) + return link_creation_info.model_copy(update=link_updated.model_dump()) def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) diff --git a/antarest/study/service.py b/antarest/study/service.py index 4c398eb564..04b337f800 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -1872,9 +1872,9 @@ def create_link( def update_link( self, uuid: str, - link_update_dto: LinkInfoDTOType, + link_update_dto: LinkInfoDTO, params: RequestParameters, - ) -> LinkInfoDTOType: + ) -> LinkInfoDTO: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.WRITE) self._assert_study_unarchived(study) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 6e8bfb57c8..121eec7e15 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -10,7 +10,6 @@ # # This file is part of the Antares project. import typing as t -from enum import Enum from abc import ABCMeta from typing import Any, Dict, List, Optional, Tuple, Union, cast @@ -24,7 +23,6 @@ field_validator, model_validator, ) -from pydantic import ConfigDict, Field, ValidationInfo, field_validator, model_validator from antarest.core.exceptions import LinkValidationError from antarest.core.serialization import AntaresBaseModel diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 9209e47a43..6a27c98f92 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -11,13 +11,20 @@ # This file is part of the Antares project. import typing as t +from antares.study.version import StudyVersion from pydantic import ValidationError from antarest.core.exceptions import LinkValidationError +from antarest.study.model import STUDY_VERSION_8_2 from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput -from antarest.study.storage.variantstudy.model.command.create_link import AbstractLinkCommand, LinkProperties +from antarest.study.storage.variantstudy.model.command.create_link import ( + AbstractLinkCommand, + LinkInfoProperties, + LinkInfoProperties820, + LinkProperties, +) from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand, OutputTuple from antarest.study.storage.variantstudy.model.model import CommandDTO @@ -51,7 +58,6 @@ def _apply(self, study_data: FileStudy, *a: str) -> CommandOutput: properties = LinkProperties.model_validate(self.parameters or {}).model_dump(exclude_unset=True, by_alias=True) current_parameters = study_data.tree.get(["input", "links", area_from, "properties", area_to]) - current_parameters.update(properties) study_data.tree.save(current_parameters, ["input", "links", area_from, "properties", area_to]) diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 28a02d9a74..3ae20536a1 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -201,11 +201,11 @@ def create_link( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Update a link", - response_model=LinkInfoDTOType, + response_model=LinkInfoDTO, ) def update_link( uuid: str, - link_creation_info: LinkInfoDTOType, + link_creation_info: LinkInfoDTO, current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Any: area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 52330c066f..d5256b66d8 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -31,35 +31,29 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ area1_id = preparer.create_area(study_id, name="Area 1")["id"] area2_id = preparer.create_area(study_id, name="Area 2")["id"] - client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "hurdles-cost": True}) + client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "hurdlesCost": True}) res = client.put( f"/v1/studies/{study_id}/links", - json={ - "area1": area1_id, - "area2": area2_id, - "hurdles-cost": False, - "colorr": 150, - "filter-synthesis": "hourly", - }, + json={"area1": area1_id, "area2": area2_id, "colorr": 150}, ) assert res.status_code == 200 expected = { "area1": "area 1", "area2": "area 2", - "asset-type": "ac", + "assetType": "ac", "colorb": 112, "colorg": 112, "colorr": 150, - "display-comments": True, - "filter-synthesis": "hourly", - "filter-year-by-year": "hourly, daily, weekly, monthly, annual", - "hurdles-cost": False, - "link-style": "plain", - "link-width": 1.0, - "loop-flow": False, - "transmission-capacities": "enabled", - "use-phase-shifter": False, + "displayComments": True, + "filterSynthesis": "hourly, daily, weekly, monthly, annual", + "filterYearByYear": "hourly, daily, weekly, monthly, annual", + "hurdlesCost": True, + "linkStyle": "plain", + "linkWidth": 1.0, + "loopFlow": False, + "transmissionCapacities": "enabled", + "usePhaseShifter": False, } assert expected == res.json() @@ -70,7 +64,7 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ json={ "area1": area1_id, "area2": area1_id, - "hurdles-cost": False, + "hurdlesCost": False, }, ) assert res.status_code == 422 @@ -84,7 +78,7 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ json={ "area1": area1_id, "area2": "id_do_not_exist", - "hurdles-cost": False, + "hurdlesCost": False, }, ) assert res.status_code == 422 @@ -124,7 +118,7 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ json={ "area1": area1_id, "area2": area2_id, - "hurdles-cost": False, + "hurdlesCost": False, }, ) assert res.status_code == 200 @@ -132,7 +126,7 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ res = client.get(f"/v1/studies/{study_id}/commands") commands = res.json() command_args = commands[-1]["args"] - assert command_args["parameters"] == {"hurdles-cost": False} + assert command_args["parameters"] == {"hurdles_cost": False} @pytest.mark.parametrize("study_type", ["raw", "variant"]) def test_link_820(self, client: TestClient, user_access_token: str, study_type: str) -> None: From 53bf23c39ad0c752dbf24a78f710c09c5f120418 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 28 Oct 2024 17:32:16 +0100 Subject: [PATCH 058/103] Remove unused import and update type annotations --- .../study/storage/variantstudy/model/command/create_link.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index e27551d792..c27ffb6216 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -10,7 +10,6 @@ # # This file is part of the Antares project. import typing as t -from enum import Enum from typing import Any, Dict, List, Optional, Tuple, Union, cast from antares.study.version import StudyVersion @@ -23,6 +22,7 @@ field_validator, model_validator, ) +from typing_extensions import Annotated from antarest.core.exceptions import LinkValidationError from antarest.core.serialization import AntaresBaseModel @@ -98,7 +98,7 @@ class LinkInfoProperties(AntaresBaseModel): model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True) -comma_separated_enum_list = t.Annotated[ +comma_separated_enum_list = Annotated[ t.List[FilterOption], BeforeValidator(lambda x: validate_filters(x, FilterOption)), PlainSerializer(lambda x: join_with_comma(x)), From 78ea2aebc02e198cb234c72abd2d6aab356b8e36 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 28 Oct 2024 17:36:26 +0100 Subject: [PATCH 059/103] Refactor method names in update_link module --- .../study/storage/variantstudy/model/command/update_link.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 6a27c98f92..cf41068829 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -68,10 +68,10 @@ def _apply(self, study_data: FileStudy, *a: str) -> CommandOutput: self.save_series(area_from, area_to, study_data, version) if self.direct: - self.save_direct_series(area_from, area_to, study_data, version) + self.save_direct(area_from, area_to, study_data, version) if self.indirect: - self.save_indirect_series(area_from, area_to, study_data, version) + self.save_indirect(area_from, area_to, study_data, version) return output From a0c864d1c5cde20fa81e3388c411a6007618c263 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 28 Oct 2024 17:39:21 +0100 Subject: [PATCH 060/103] Rename variables to reflect update context in link management --- antarest/study/business/link_management.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index ffb1a0839f..21ebaa2100 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -112,15 +112,15 @@ def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkI return link_dto - def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: + def update_link(self, study: RawStudy, link_update_info: LinkInfoDTO) -> LinkInfoDTO: file_study = self.storage_service.get_storage(study).get_raw(study) - self.check_attributes_coherence(file_study, StudyVersion.parse(study.version), link_creation_info) + self.check_attributes_coherence(file_study, StudyVersion.parse(study.version), link_update_info) command = UpdateLink( - area1=link_creation_info.area1, - area2=link_creation_info.area2, - parameters=link_creation_info.model_dump( + area1=link_update_info.area1, + area2=link_update_info.area2, + parameters=link_update_info.model_dump( mode="json", exclude={"area1", "area2"}, exclude_none=True, exclude_unset=True ), command_context=self.storage_service.variant_study_service.command_factory.command_context, @@ -128,8 +128,8 @@ def update_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkI execute_or_add_commands(study, file_study, [command], self.storage_service) - existing_link = self.get_one_link(study, link_creation_info) - return existing_link + updated_link = self.get_one_link(study, link_update_info) + return updated_link def check_attributes_coherence( self, file_study: FileStudy, study_version: StudyVersion, link_creation_info: LinkInfoDTO From a272fd40068d537584683211b7258e09e33bfe3c Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 28 Oct 2024 17:39:50 +0100 Subject: [PATCH 061/103] Refactor `get_one_link` to `get_updated_link` --- antarest/study/business/link_management.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 21ebaa2100..a750553955 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -128,7 +128,8 @@ def update_link(self, study: RawStudy, link_update_info: LinkInfoDTO) -> LinkInf execute_or_add_commands(study, file_study, [command], self.storage_service) - updated_link = self.get_one_link(study, link_update_info) + updated_link = self.get_updated_link(study, link_update_info) + return updated_link def check_attributes_coherence( @@ -151,7 +152,7 @@ def check_attributes_coherence( ): raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") - def get_one_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: + def get_updated_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: file_study = self.storage_service.get_storage(study).get_raw(study) area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) From 3f4cb36f2d38af2a6986211933453c5e5ee37fc8 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 28 Oct 2024 17:41:19 +0100 Subject: [PATCH 062/103] Refactor link update logic in link_management. --- antarest/study/business/link_management.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index a750553955..d7534300e6 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -157,13 +157,12 @@ def get_updated_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) try: - link_updated = LinkInfoProperties.model_validate( - file_study.tree.get(["input", "links", area_from, "properties", area_to]) - ) + link_properties = file_study.tree.get(["input", "links", area_from, "properties", area_to]) except KeyError: raise LinkValidationError(f"The link {area_from} -> {area_to} is not present in the study") - return link_creation_info.model_copy(update=link_updated.model_dump()) + updated_link = LinkInfoProperties.model_validate(link_properties) + return link_creation_info.model_copy(update=updated_link.model_dump()) def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) From 72f79e27ae7aae21b0da33537db9900f04d2da89 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 31 Oct 2024 11:21:35 +0100 Subject: [PATCH 063/103] feat: refactor link management to use new LinkDTO class --- antarest/study/business/link_management.py | 61 ++-------- antarest/study/business/model/__init__.py | 0 antarest/study/business/model/link_model.py | 111 ++++++++++++++++++ antarest/study/service.py | 18 +-- .../rawstudy/model/filesystem/config/links.py | 32 ++++- .../variantstudy/model/command/create_link.py | 107 ++--------------- antarest/study/web/study_data_blueprint.py | 10 +- .../storage/business/test_arealink_manager.py | 8 +- .../model/command/test_create_link.py | 5 +- 9 files changed, 184 insertions(+), 168 deletions(-) create mode 100644 antarest/study/business/model/__init__.py create mode 100644 antarest/study/business/model/link_model.py diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index ab7ba84dab..8bd0beb0df 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -13,39 +13,22 @@ import typing as t from antares.study.version import StudyVersion -from pydantic import ConfigDict from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError from antarest.core.model import JSON -from antarest.core.utils.string import to_camel_case from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model +from antarest.study.business.model.link_model import LinkDTO, LinkInternal from antarest.study.business.utils import execute_or_add_commands -from antarest.study.model import STUDY_VERSION_8_2, RawStudy +from antarest.study.model import RawStudy from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties from antarest.study.storage.storage_service import StudyStorageService -from antarest.study.storage.variantstudy.model.command.create_link import ( - AreaInfo, - CreateLink, - LinkInfoProperties, - LinkInfoProperties820, -) +from antarest.study.storage.variantstudy.model.command.create_link import CreateLink from antarest.study.storage.variantstudy.model.command.remove_link import RemoveLink from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig _ALL_LINKS_PATH = "input/links" -class LinkInfoDTOBase(AreaInfo, LinkInfoProperties): - model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") - - -class LinkInfoDTO820(AreaInfo, LinkInfoProperties820): - model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") - - -LinkInfoDTO = t.Union[LinkInfoDTO820, LinkInfoDTOBase] - - @all_optional_model @camel_case_model class LinkOutput(LinkProperties): @@ -58,9 +41,9 @@ class LinkManager: def __init__(self, storage_service: StudyStorageService) -> None: self.storage_service = storage_service - def get_all_links(self, study: RawStudy) -> t.List[LinkInfoDTO]: + def get_all_links(self, study: RawStudy) -> t.List[LinkDTO]: file_study = self.storage_service.get_storage(study).get_raw(study) - result: t.List[LinkInfoDTO] = [] + result: t.List[LinkDTO] = [] for area_id, area in file_study.config.areas.items(): links_config = file_study.tree.get(["input", "links", area_id, "properties"]) @@ -69,46 +52,28 @@ def get_all_links(self, study: RawStudy) -> t.List[LinkInfoDTO]: link_tree_config: t.Dict[str, t.Any] = links_config[link] link_tree_config.update({"area1": area_id, "area2": link}) - link_properties = {"area1": area_id, "area2": link} - link_dto: LinkInfoDTO - if StudyVersion.parse(study.version) < STUDY_VERSION_8_2: - link_properties.update(LinkInfoProperties(**link_tree_config).model_dump()) - link_dto = LinkInfoDTOBase(**link_properties) - else: - link_properties.update(LinkInfoProperties820(**link_tree_config).model_dump()) - link_dto = LinkInfoDTO820(**link_properties) + link = LinkInternal.model_validate(link_tree_config) - result.append(link_dto) + result.append(link.to_dto()) return result - def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: - if link_creation_info.area1 == link_creation_info.area2: - raise LinkValidationError(f"Cannot create link on same area: {link_creation_info.area1}") - - link_dto: LinkInfoDTO - if StudyVersion.parse(study.version) >= STUDY_VERSION_8_2: - link_dto = LinkInfoDTO820.model_validate(link_creation_info.model_dump()) - else: - forbidden_fields = {"filter_synthesis", "filter_year_by_year"} - fields = set(link_creation_info.model_dump(exclude_defaults=True)) - if forbidden_fields & fields: - raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") - link_dto = LinkInfoDTOBase.model_validate(link_creation_info.model_dump(exclude=forbidden_fields)) + def create_link(self, study: RawStudy, link_creation_dto: LinkDTO) -> LinkDTO: + link = link_creation_dto.to_internal(StudyVersion.parse(study.version)) storage_service = self.storage_service.get_storage(study) file_study = storage_service.get_raw(study) command = CreateLink( - area1=link_creation_info.area1, - area2=link_creation_info.area2, - parameters=link_dto.model_dump(exclude_none=True), + area1=link.area1, + area2=link.area2, + parameters=link.model_dump(), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) execute_or_add_commands(study, file_study, [command], self.storage_service) - return link_dto + return link_creation_dto def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) diff --git a/antarest/study/business/model/__init__.py b/antarest/study/business/model/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py new file mode 100644 index 0000000000..475f93e9e8 --- /dev/null +++ b/antarest/study/business/model/link_model.py @@ -0,0 +1,111 @@ +import typing as t + +from antares.study.version import StudyVersion +from pydantic import Field, ConfigDict, model_validator + +from antarest.core.exceptions import LinkValidationError +from antarest.core.serialization import AntaresBaseModel +from antarest.core.utils.string import to_camel_case, to_kebab_case +from antarest.study.model import STUDY_VERSION_8_2 +from antarest.study.storage.rawstudy.model.filesystem.config.links import TransmissionCapacity, AssetType, FilterOption, \ + LinkStyle, comma_separated_enum_list + +DEFAULT_COLOR = 112 +FILTER_VALUES: t.List[FilterOption] = [ + FilterOption.HOURLY, + FilterOption.DAILY, + FilterOption.WEEKLY, + FilterOption.MONTHLY, + FilterOption.ANNUAL, +] + +class Area(AntaresBaseModel): + area1: str + area2: str + + @model_validator(mode='after') + def validate_areas(self) -> t.Self: + if self.area1 == self.area2 : + raise LinkValidationError(f"Cannot create link on same area: {self.area1}") + return self + +class LinkDTO(Area): + model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") + + hurdles_cost: bool = False + loop_flow: bool = False + use_phase_shifter: bool = False + transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED + asset_type: AssetType = AssetType.AC + display_comments: bool = True + colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + colorg: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + link_width: float = 1 + link_style: LinkStyle = LinkStyle.PLAIN + + filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES + filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES + + def to_internal(self, version: StudyVersion) -> "LinkInternal": + if version < STUDY_VERSION_8_2 and ( + 'filter_synthesis' in self.model_fields_set or 'filter_year_by_year' in self.model_fields_set): + raise LinkValidationError( + "Cannot specify a filter value for study's version earlier than v8.2") + + return LinkInternal( + area1=self.area1, + area2=self.area2, + hurdles_cost=self.hurdles_cost, + loop_flow=self.loop_flow, + use_phase_shifter=self.use_phase_shifter, + transmission_capacities=self.transmission_capacities, + asset_type=self.asset_type, + display_comments=self.display_comments, + colorr=self.colorr, + colorb=self.colorb, + colorg=self.colorg, + link_width=self.link_width, + link_style=self.link_style, + filter_synthesis=self.filter_synthesis if version >= STUDY_VERSION_8_2 else None, + filter_year_by_year=self.filter_year_by_year if version >= STUDY_VERSION_8_2 else None, + ) + + +class LinkInternal(AntaresBaseModel): + model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True, extra="forbid") + + area1: str + area2: str + hurdles_cost: bool = False + loop_flow: bool = False + use_phase_shifter: bool = False + transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED + asset_type: AssetType = AssetType.AC + display_comments: bool = True + colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + colorg: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + link_width: float = 1 + link_style: LinkStyle = LinkStyle.PLAIN + filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES + filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES + + def to_dto(self) -> LinkDTO: + return LinkDTO( + area1=self.area1, + area2=self.area2, + hurdles_cost=self.hurdles_cost, + loop_flow=self.loop_flow, + use_phase_shifter=self.use_phase_shifter, + transmission_capacities=self.transmission_capacities, + asset_type=self.asset_type, + display_comments=self.display_comments, + colorr=self.colorr, + colorb=self.colorb, + colorg=self.colorg, + link_width=self.link_width, + link_style=self.link_style, + filter_synthesis=self.filter_synthesis, + filter_year_by_year=self.filter_year_by_year, + ) \ No newline at end of file diff --git a/antarest/study/service.py b/antarest/study/service.py index 964e214ff6..158166e8e8 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -85,7 +85,7 @@ from antarest.study.business.correlation_management import CorrelationManager from antarest.study.business.district_manager import DistrictManager from antarest.study.business.general_management import GeneralManager -from antarest.study.business.link_management import LinkInfoDTO, LinkManager, LinkOutput +from antarest.study.business.link_management import LinkDTO, LinkManager from antarest.study.business.matrix_management import MatrixManager, MatrixManagerError from antarest.study.business.optimization_management import OptimizationManager from antarest.study.business.playlist_management import PlaylistManager @@ -357,7 +357,7 @@ def __init__( self.task_service = task_service self.areas = AreaManager(self.storage_service, self.repository) self.district_manager = DistrictManager(self.storage_service) - self.links = LinkManager(self.storage_service) + self.links_manager = LinkManager(self.storage_service) self.config_manager = ConfigManager(self.storage_service) self.general_manager = GeneralManager(self.storage_service) self.thematic_trimming_manager = ThematicTrimmingManager(self.storage_service) @@ -379,7 +379,7 @@ def __init__( self.correlation_manager = CorrelationManager(self.storage_service) self.table_mode_manager = TableModeManager( self.areas, - self.links, + self.links_manager, self.thermal_manager, self.renewable_manager, self.st_storage_manager, @@ -1850,10 +1850,10 @@ def get_all_links( self, uuid: str, params: RequestParameters, - ) -> t.List[LinkInfoDTO]: + ) -> t.List[LinkDTO]: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.READ) - return self.links.get_all_links(study) + return self.links_manager.get_all_links(study) def create_area( self, @@ -1877,13 +1877,13 @@ def create_area( def create_link( self, uuid: str, - link_creation_dto: LinkInfoDTO, + link_creation_dto: LinkDTO, params: RequestParameters, - ) -> LinkInfoDTO: + ) -> LinkDTO: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.WRITE) self._assert_study_unarchived(study) - new_link = self.links.create_link(study, link_creation_dto) + new_link = self.links_manager.create_link(study, link_creation_dto) self.event_bus.push( Event( type=EventType.STUDY_DATA_EDITED, @@ -1999,7 +1999,7 @@ def delete_link( if referencing_binding_constraints: binding_ids = [bc.id for bc in referencing_binding_constraints] raise ReferencedObjectDeletionNotAllowed(link_id, binding_ids, object_type="Link") - self.links.delete_link(study, area_from, area_to) + self.links_manager.delete_link(study, area_from, area_to) self.event_bus.push( Event( type=EventType.STUDY_DATA_EDITED, diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/links.py b/antarest/study/storage/rawstudy/model/filesystem/config/links.py index 624f639784..3d64b07ebb 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/links.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/links.py @@ -16,8 +16,9 @@ import typing as t -from pydantic import Field, field_validator, model_validator +from pydantic import Field, field_validator, model_validator, BeforeValidator, PlainSerializer +from antarest.core.exceptions import LinkValidationError from antarest.study.business.enum_ignore_case import EnumIgnoreCase from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import ( validate_color_rgb, @@ -102,6 +103,35 @@ class FilterOption(EnumIgnoreCase): MONTHLY = "monthly" ANNUAL = "annual" +def validate_filters( + filter_value: t.Union[t.List[FilterOption], str], enum_cls: t.Type[FilterOption] +) -> t.List[FilterOption]: + if filter_value is not None and isinstance(filter_value, str): + filter_accepted_values = [e for e in enum_cls] + + options = filter_value.replace(" ", "").split(",") + + invalid_options = [opt for opt in options if opt not in filter_accepted_values] + if invalid_options: + raise LinkValidationError( + f"Invalid value(s) in filters: {', '.join(invalid_options)}. " + f"Allowed values are: {', '.join(filter_accepted_values)}." + ) + + return [enum_cls(opt) for opt in options] + + return filter_value + + +def join_with_comma(values: t.List[FilterOption]) -> str: + return ", ".join(value.name.lower() for value in values) + +comma_separated_enum_list = t.Annotated[ + t.List[FilterOption], + BeforeValidator(lambda x: validate_filters(x, FilterOption)), + PlainSerializer(lambda x: join_with_comma(x)), +] + class LinkProperties(IniProperties): """ diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index ae06e330ff..aa9f27b343 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -9,112 +9,28 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. -import typing as t from typing import Any, Dict, List, Optional, Tuple, Union, cast -from antares.study.version import StudyVersion from pydantic import ( - BeforeValidator, - ConfigDict, - Field, - PlainSerializer, ValidationInfo, field_validator, - model_validator, + model_validator ) -from typing_extensions import Annotated -from antarest.core.exceptions import LinkValidationError -from antarest.core.serialization import AntaresBaseModel -from antarest.core.utils.string import to_kebab_case from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData +from antarest.study.business.model.link_model import LinkInternal from antarest.study.model import STUDY_VERSION_8_2 -from antarest.study.storage.rawstudy.model.filesystem.config.links import ( - AssetType, - FilterOption, - LinkStyle, - TransmissionCapacity, -) from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput, FilteringOptions from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand +from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix +from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig from antarest.study.storage.variantstudy.model.command_listener.command_listener import ICommandListener from antarest.study.storage.variantstudy.model.model import CommandDTO -DEFAULT_COLOR = 112 -FILTER_VALUES: t.List[FilterOption] = [ - FilterOption.HOURLY, - FilterOption.DAILY, - FilterOption.WEEKLY, - FilterOption.MONTHLY, - FilterOption.ANNUAL, -] - - -def validate_filters( - filter_value: Union[List[FilterOption], str], enum_cls: t.Type[FilterOption] -) -> List[FilterOption]: - if filter_value is not None and isinstance(filter_value, str): - filter_accepted_values = [e for e in enum_cls] - - options = filter_value.replace(" ", "").split(",") - - invalid_options = [opt for opt in options if opt not in filter_accepted_values] - if invalid_options: - raise LinkValidationError( - f"Invalid value(s) in filters: {', '.join(invalid_options)}. " - f"Allowed values are: {', '.join(filter_accepted_values)}." - ) - - return [enum_cls(opt) for opt in options] - - return filter_value - - -def join_with_comma(values: t.List[FilterOption]) -> str: - return ", ".join(value.name.lower() for value in values) - - -class AreaInfo(AntaresBaseModel): - area1: str - area2: str - - -class LinkInfoProperties(AntaresBaseModel): - hurdles_cost: bool = False - loop_flow: bool = False - use_phase_shifter: bool = False - transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED - asset_type: AssetType = AssetType.AC - display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) - colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) - colorg: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) - link_width: float = 1 - link_style: LinkStyle = LinkStyle.PLAIN - - model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True) - - -comma_separated_enum_list = Annotated[ - t.List[FilterOption], - BeforeValidator(lambda x: validate_filters(x, FilterOption)), - PlainSerializer(lambda x: join_with_comma(x)), -] - - -class LinkInfoProperties820(LinkInfoProperties): - filter_synthesis: comma_separated_enum_list = FILTER_VALUES - filter_year_by_year: comma_separated_enum_list = FILTER_VALUES - - model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True) - - -class LinkProperties(LinkInfoProperties820): - pass class CreateLink(ICommand): @@ -230,18 +146,13 @@ def _apply(self, study_data: FileStudy, listener: Optional[ICommandListener] = N if not output.status: return output - properties: LinkInfoProperties - if StudyVersion.parse(version) >= STUDY_VERSION_8_2: - properties = LinkInfoProperties820.model_validate(self.parameters or {}) - else: - properties = LinkInfoProperties.model_validate(self.parameters or {}) - - link_property = properties.model_dump(mode="json", exclude={"area1", "area2"}, by_alias=True, exclude_none=True) + validated_properties = LinkInternal.model_validate(self.parameters).model_dump(mode="json", by_alias=True, exclude_none=True) area_from = data["area_from"] area_to = data["area_to"] - study_data.tree.save(link_property, ["input", "links", area_from, "properties", area_to]) + study_data.tree.save(validated_properties, ["input", "links", area_from, "properties", area_to]) + self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) @@ -322,13 +233,11 @@ def match(self, other: ICommand, equal: bool = False) -> bool: def _create_diff(self, other: "ICommand") -> List["ICommand"]: other = cast(CreateLink, other) - from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix - from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig commands: List[ICommand] = [] area_from, area_to = sorted([self.area1, self.area2]) if self.parameters != other.parameters: - properties = LinkProperties.model_validate(other.parameters or {}) + properties = LinkInternal.model_validate(other.parameters or {}) link_property = properties.model_dump(mode="json", by_alias=True, exclude_none=True) commands.append( UpdateConfig( diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 7361d8be49..71f5d1ff92 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -68,7 +68,7 @@ ) from antarest.study.business.district_manager import DistrictCreationDTO, DistrictInfoDTO, DistrictUpdateDTO from antarest.study.business.general_management import GeneralFormFields -from antarest.study.business.link_management import LinkInfoDTO, LinkOutput +from antarest.study.business.link_management import LinkDTO from antarest.study.business.optimization_management import OptimizationFormFields from antarest.study.business.playlist_management import PlaylistColumns from antarest.study.business.scenario_builder_management import Rulesets, ScenarioType @@ -147,7 +147,7 @@ def get_areas( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Get all links", - response_model=t.List[LinkInfoDTO], + response_model=t.List[LinkDTO], ) def get_links( uuid: str, @@ -183,13 +183,13 @@ def create_area( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Create a link", - response_model=LinkInfoDTO, + response_model=LinkDTO, ) def create_link( uuid: str, - link_creation_info: LinkInfoDTO, + link_creation_info: LinkDTO, current_user: JWTUser = Depends(auth.get_current_user), - ) -> LinkInfoDTO: + ) -> LinkDTO: logger.info( f"Creating new link for study {uuid}", extra={"user": current_user.id}, diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index c79a234580..9c5ee0a449 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -24,7 +24,7 @@ from antarest.matrixstore.repository import MatrixContentRepository from antarest.matrixstore.service import SimpleMatrixService from antarest.study.business.area_management import AreaCreationDTO, AreaManager, AreaType, UpdateAreaUi -from antarest.study.business.link_management import LinkInfoDTO820, LinkInfoDTOBase, LinkManager +from antarest.study.business.link_management import LinkManager, LinkDTO from antarest.study.model import Patch, PatchArea, PatchCluster, RawStudy, StudyAdditionalData from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService @@ -139,7 +139,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): study.version = 820 link_manager.create_link( study, - LinkInfoDTO820( + LinkDTO( area1="test", area2="test2", ), @@ -230,7 +230,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): study.version = 880 link_manager.create_link( study, - LinkInfoDTO820( + LinkDTO( area1="test", area2="test2", ), @@ -268,7 +268,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): study.version = 810 link_manager.create_link( study, - LinkInfoDTOBase( + LinkDTO( area1="test", area2="test2", ), diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index 14003dcf6c..5d363ffeb4 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -14,12 +14,13 @@ import pytest from pydantic import ValidationError +from antarest.study.business.link_management import LinkInternal from antarest.study.storage.rawstudy.ini_reader import IniReader from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter from antarest.study.storage.variantstudy.model.command.create_area import CreateArea -from antarest.study.storage.variantstudy.model.command.create_link import CreateLink, LinkProperties +from antarest.study.storage.variantstudy.model.command.create_link import CreateLink from antarest.study.storage.variantstudy.model.command.icommand import ICommand from antarest.study.storage.variantstudy.model.command.remove_area import RemoveArea from antarest.study.storage.variantstudy.model.command.remove_link import RemoveLink @@ -257,7 +258,7 @@ def test_create_diff(command_context: CommandContext): assert base.create_diff(other_match) == [ UpdateConfig( target="input/links/bar/properties/foo", - data=LinkProperties.model_validate({"hurdles_cost": "true"}).model_dump(by_alias=True, exclude_none=True), + data=LinkInternal.model_validate({"area1": "bar", "area2": "foo", "hurdles_cost": "true"}).model_dump(by_alias=True, exclude_none=True, exclude={"area1", "area2"}), command_context=command_context, ), ReplaceMatrix( From e611ad09e920e61d060e06d6515f8086086e9a7e Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 31 Oct 2024 11:25:40 +0100 Subject: [PATCH 064/103] feat: simplify JSON dumping in create_link --- .../study/storage/variantstudy/model/command/create_link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index aa9f27b343..0ced84bf17 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -146,7 +146,7 @@ def _apply(self, study_data: FileStudy, listener: Optional[ICommandListener] = N if not output.status: return output - validated_properties = LinkInternal.model_validate(self.parameters).model_dump(mode="json", by_alias=True, exclude_none=True) + validated_properties = LinkInternal.model_validate(self.parameters).model_dump(by_alias=True) area_from = data["area_from"] area_to = data["area_to"] From 03570f5866a3943d7303c0223cf1ed10c13df5c6 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 31 Oct 2024 23:06:21 +0100 Subject: [PATCH 065/103] Refactor link management imports and optimize validations --- antarest/study/business/link_management.py | 6 ++-- antarest/study/business/model/link_model.py | 29 ++++++++++++------- antarest/study/service.py | 3 +- .../rawstudy/model/filesystem/config/links.py | 4 ++- .../variantstudy/model/command/create_link.py | 15 +++++----- antarest/study/web/study_data_blueprint.py | 2 +- .../storage/business/test_arealink_manager.py | 2 +- .../model/command/test_create_link.py | 4 ++- 8 files changed, 38 insertions(+), 27 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 8bd0beb0df..cca12fcc2c 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -52,9 +52,9 @@ def get_all_links(self, study: RawStudy) -> t.List[LinkDTO]: link_tree_config: t.Dict[str, t.Any] = links_config[link] link_tree_config.update({"area1": area_id, "area2": link}) - link = LinkInternal.model_validate(link_tree_config) + link_internal = LinkInternal.model_validate(link_tree_config) - result.append(link.to_dto()) + result.append(link_internal.to_dto()) return result @@ -67,7 +67,7 @@ def create_link(self, study: RawStudy, link_creation_dto: LinkDTO) -> LinkDTO: command = CreateLink( area1=link.area1, area2=link.area2, - parameters=link.model_dump(), + parameters=link.model_dump(exclude_none=True), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index 475f93e9e8..fd6bbf9670 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -1,14 +1,19 @@ import typing as t from antares.study.version import StudyVersion -from pydantic import Field, ConfigDict, model_validator +from pydantic import ConfigDict, Field, model_validator from antarest.core.exceptions import LinkValidationError from antarest.core.serialization import AntaresBaseModel from antarest.core.utils.string import to_camel_case, to_kebab_case from antarest.study.model import STUDY_VERSION_8_2 -from antarest.study.storage.rawstudy.model.filesystem.config.links import TransmissionCapacity, AssetType, FilterOption, \ - LinkStyle, comma_separated_enum_list +from antarest.study.storage.rawstudy.model.filesystem.config.links import ( + AssetType, + FilterOption, + LinkStyle, + TransmissionCapacity, + comma_separated_enum_list, +) DEFAULT_COLOR = 112 FILTER_VALUES: t.List[FilterOption] = [ @@ -19,16 +24,18 @@ FilterOption.ANNUAL, ] + class Area(AntaresBaseModel): area1: str area2: str - @model_validator(mode='after') + @model_validator(mode="after") def validate_areas(self) -> t.Self: - if self.area1 == self.area2 : + if self.area1 == self.area2: raise LinkValidationError(f"Cannot create link on same area: {self.area1}") return self + class LinkDTO(Area): model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") @@ -49,9 +56,9 @@ class LinkDTO(Area): def to_internal(self, version: StudyVersion) -> "LinkInternal": if version < STUDY_VERSION_8_2 and ( - 'filter_synthesis' in self.model_fields_set or 'filter_year_by_year' in self.model_fields_set): - raise LinkValidationError( - "Cannot specify a filter value for study's version earlier than v8.2") + "filter_synthesis" in self.model_fields_set or "filter_year_by_year" in self.model_fields_set + ): + raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") return LinkInternal( area1=self.area1, @@ -75,8 +82,8 @@ def to_internal(self, version: StudyVersion) -> "LinkInternal": class LinkInternal(AntaresBaseModel): model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True, extra="forbid") - area1: str - area2: str + area1: str = "area1" + area2: str = "area2" hurdles_cost: bool = False loop_flow: bool = False use_phase_shifter: bool = False @@ -108,4 +115,4 @@ def to_dto(self) -> LinkDTO: link_style=self.link_style, filter_synthesis=self.filter_synthesis, filter_year_by_year=self.filter_year_by_year, - ) \ No newline at end of file + ) diff --git a/antarest/study/service.py b/antarest/study/service.py index 158166e8e8..2b3d2b8d41 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -85,8 +85,9 @@ from antarest.study.business.correlation_management import CorrelationManager from antarest.study.business.district_manager import DistrictManager from antarest.study.business.general_management import GeneralManager -from antarest.study.business.link_management import LinkDTO, LinkManager +from antarest.study.business.link_management import LinkManager from antarest.study.business.matrix_management import MatrixManager, MatrixManagerError +from antarest.study.business.model.link_model import LinkDTO from antarest.study.business.optimization_management import OptimizationManager from antarest.study.business.playlist_management import PlaylistManager from antarest.study.business.scenario_builder_management import ScenarioBuilderManager diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/links.py b/antarest/study/storage/rawstudy/model/filesystem/config/links.py index 3d64b07ebb..f782cfb406 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/links.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/links.py @@ -16,7 +16,7 @@ import typing as t -from pydantic import Field, field_validator, model_validator, BeforeValidator, PlainSerializer +from pydantic import BeforeValidator, Field, PlainSerializer, field_validator, model_validator from antarest.core.exceptions import LinkValidationError from antarest.study.business.enum_ignore_case import EnumIgnoreCase @@ -103,6 +103,7 @@ class FilterOption(EnumIgnoreCase): MONTHLY = "monthly" ANNUAL = "annual" + def validate_filters( filter_value: t.Union[t.List[FilterOption], str], enum_cls: t.Type[FilterOption] ) -> t.List[FilterOption]: @@ -126,6 +127,7 @@ def validate_filters( def join_with_comma(values: t.List[FilterOption]) -> str: return ", ".join(value.name.lower() for value in values) + comma_separated_enum_list = t.Annotated[ t.List[FilterOption], BeforeValidator(lambda x: validate_filters(x, FilterOption)), diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 0ced84bf17..bd93a8252f 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -11,11 +11,7 @@ # This file is part of the Antares project. from typing import Any, Dict, List, Optional, Tuple, Union, cast -from pydantic import ( - ValidationInfo, - field_validator, - model_validator -) +from pydantic import ValidationInfo, field_validator, model_validator from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData @@ -32,7 +28,6 @@ from antarest.study.storage.variantstudy.model.model import CommandDTO - class CreateLink(ICommand): """ Command used to create a link between two areas. @@ -146,7 +141,9 @@ def _apply(self, study_data: FileStudy, listener: Optional[ICommandListener] = N if not output.status: return output - validated_properties = LinkInternal.model_validate(self.parameters).model_dump(by_alias=True) + validated_properties = LinkInternal.model_validate(self.parameters).model_dump( + by_alias=True, exclude={"area1", "area2"} + ) area_from = data["area_from"] area_to = data["area_to"] @@ -238,7 +235,9 @@ def _create_diff(self, other: "ICommand") -> List["ICommand"]: area_from, area_to = sorted([self.area1, self.area2]) if self.parameters != other.parameters: properties = LinkInternal.model_validate(other.parameters or {}) - link_property = properties.model_dump(mode="json", by_alias=True, exclude_none=True) + link_property = properties.model_dump( + mode="json", by_alias=True, exclude_none=True, exclude={"area1", "area2"} + ) commands.append( UpdateConfig( target=f"input/links/{area_from}/properties/{area_to}", diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 71f5d1ff92..13f8fd48cc 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -68,7 +68,7 @@ ) from antarest.study.business.district_manager import DistrictCreationDTO, DistrictInfoDTO, DistrictUpdateDTO from antarest.study.business.general_management import GeneralFormFields -from antarest.study.business.link_management import LinkDTO +from antarest.study.business.model.link_model import LinkDTO from antarest.study.business.optimization_management import OptimizationFormFields from antarest.study.business.playlist_management import PlaylistColumns from antarest.study.business.scenario_builder_management import Rulesets, ScenarioType diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 9c5ee0a449..582c6067c1 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -24,7 +24,7 @@ from antarest.matrixstore.repository import MatrixContentRepository from antarest.matrixstore.service import SimpleMatrixService from antarest.study.business.area_management import AreaCreationDTO, AreaManager, AreaType, UpdateAreaUi -from antarest.study.business.link_management import LinkManager, LinkDTO +from antarest.study.business.link_management import LinkDTO, LinkManager from antarest.study.model import Patch, PatchArea, PatchCluster, RawStudy, StudyAdditionalData from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index 5d363ffeb4..33a536b514 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -258,7 +258,9 @@ def test_create_diff(command_context: CommandContext): assert base.create_diff(other_match) == [ UpdateConfig( target="input/links/bar/properties/foo", - data=LinkInternal.model_validate({"area1": "bar", "area2": "foo", "hurdles_cost": "true"}).model_dump(by_alias=True, exclude_none=True, exclude={"area1", "area2"}), + data=LinkInternal.model_validate({"area1": "bar", "area2": "foo", "hurdles_cost": "true"}).model_dump( + by_alias=True, exclude_none=True, exclude={"area1", "area2"} + ), command_context=command_context, ), ReplaceMatrix( From c33c195ee58a1a64f488ec8d2a55f071f0e7ff31 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 4 Nov 2024 10:37:56 +0100 Subject: [PATCH 066/103] feat: refactor link update process for improved consistency --- antarest/study/business/link_management.py | 46 ++++++++----------- antarest/study/business/model/link_model.py | 2 +- antarest/study/service.py | 6 +-- .../variantstudy/model/command/create_link.py | 10 ++-- .../variantstudy/model/command/update_link.py | 25 ++++------ antarest/study/web/study_data_blueprint.py | 4 +- .../study_data_blueprint/test_link.py | 4 +- 7 files changed, 41 insertions(+), 56 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 1dc0b45f4f..62857cdfaf 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -75,59 +75,49 @@ def create_link(self, study: RawStudy, link_creation_dto: LinkDTO) -> LinkDTO: execute_or_add_commands(study, file_study, [command], self.storage_service) - return link_creation_dto + return link.to_dto() - def update_link(self, study: RawStudy, link_update_info: LinkInfoDTO) -> LinkInfoDTO: + def update_link(self, study: RawStudy, link_dto: LinkDTO) -> LinkDTO: + link = link_dto.to_internal(StudyVersion.parse(study.version)) file_study = self.storage_service.get_storage(study).get_raw(study) - self.check_attributes_coherence(file_study, StudyVersion.parse(study.version), link_update_info) + self.check_link_existence(file_study, link) command = UpdateLink( - area1=link_update_info.area1, - area2=link_update_info.area2, - parameters=link_update_info.model_dump( - mode="json", exclude={"area1", "area2"}, exclude_none=True, exclude_unset=True + area1=link.area1, + area2=link.area2, + parameters=link.model_dump( + include=link_dto.model_fields_set, exclude={"area1", "area2"}, exclude_none=True ), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) execute_or_add_commands(study, file_study, [command], self.storage_service) - updated_link = self.get_updated_link(study, link_update_info) - - return updated_link + updated_link = self.get_ini_link(study, link) - def check_attributes_coherence( - self, file_study: FileStudy, study_version: StudyVersion, link_creation_info: LinkInfoDTO - ) -> None: - if link_creation_info.area1 == link_creation_info.area2: - raise LinkValidationError("Area 1 and Area 2 can not be the same") + return updated_link.to_dto() - area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) + def check_link_existence(self, file_study: FileStudy, link: LinkInternal) -> None: + area_from, area_to = sorted([link.area1, link.area2]) try: file_study.tree.get(["input", "links", area_from, "properties", area_to]) except KeyError: raise LinkValidationError(f"The link {area_from} -> {area_to} is not present in the study") - if study_version < STUDY_VERSION_8_2: - if isinstance(link_creation_info, LinkInfoDTO820): - if ( - link_creation_info.filter_synthesis is not None - or link_creation_info.filter_year_by_year is not None - ): - raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") - - def get_updated_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: + def get_ini_link(self, study: RawStudy, link: LinkInternal) -> LinkInternal: file_study = self.storage_service.get_storage(study).get_raw(study) - area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) + area_from, area_to = sorted([link.area1, link.area2]) try: link_properties = file_study.tree.get(["input", "links", area_from, "properties", area_to]) except KeyError: raise LinkValidationError(f"The link {area_from} -> {area_to} is not present in the study") - updated_link = LinkInfoProperties.model_validate(link_properties) - return link_creation_info.model_copy(update=updated_link.model_dump()) + link_properties.update({"area1": area_from, "area2": area_to}) + updated_link = LinkInternal.model_validate(link_properties) + + return link.model_copy(update=updated_link.model_dump()) def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index fd6bbf9670..36ac06d4cb 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -32,7 +32,7 @@ class Area(AntaresBaseModel): @model_validator(mode="after") def validate_areas(self) -> t.Self: if self.area1 == self.area2: - raise LinkValidationError(f"Cannot create link on same area: {self.area1}") + raise LinkValidationError(f"Area 1 and Area 2 can not be the same") return self diff --git a/antarest/study/service.py b/antarest/study/service.py index f81c82cb27..22c4a6168c 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -1897,13 +1897,13 @@ def create_link( def update_link( self, uuid: str, - link_update_dto: LinkInfoDTO, + link_update_dto: LinkDTO, params: RequestParameters, - ) -> LinkInfoDTO: + ) -> LinkDTO: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.WRITE) self._assert_study_unarchived(study) - updated_link = self.links.update_link(study, link_update_dto) + updated_link = self.links_manager.update_link(study, link_update_dto) self.event_bus.push( Event( type=EventType.STUDY_DATA_EDITED, diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index f1377eae5b..e4bb5983a4 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -13,6 +13,7 @@ from abc import ABCMeta from typing import Any, Dict, List, Optional, Tuple, Union, cast +from antares.study.version import StudyVersion from pydantic import ValidationInfo, field_validator, model_validator from antarest.core.utils.utils import assert_this @@ -94,12 +95,13 @@ def _create_diff(self, other: "ICommand") -> List["ICommand"]: commands: List[ICommand] = [] area_from, area_to = sorted([self.area1, self.area2]) if self.parameters != other.parameters: - properties = LinkProperties.model_validate(other.parameters or {}) - link_property = properties.model_dump(mode="json", by_alias=True, exclude_none=True) + properties = LinkInternal.model_validate(other.parameters or {}).model_dump( + mode="json", by_alias=True, exclude_none=True, exclude={"area1", "area2"} + ) commands.append( UpdateConfig( target=f"input/links/{area_from}/properties/{area_to}", - data=link_property, + data=properties, command_context=self.command_context, ) ) @@ -263,7 +265,7 @@ def _apply(self, study_data: FileStudy, listener: Optional[ICommandListener] = N area_from = data["area_from"] area_to = data["area_to"] - study_data.tree.save(link_property, ["input", "links", area_from, "properties", area_to]) + study_data.tree.save(validated_properties, ["input", "links", area_from, "properties", area_to]) self.save_series(area_from, area_to, study_data, version) self.save_direct(area_from, area_to, study_data, version) diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index cf41068829..443e44e21f 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -11,21 +11,13 @@ # This file is part of the Antares project. import typing as t -from antares.study.version import StudyVersion -from pydantic import ValidationError - -from antarest.core.exceptions import LinkValidationError -from antarest.study.model import STUDY_VERSION_8_2 +from antarest.study.business.model.link_model import LinkInternal from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput -from antarest.study.storage.variantstudy.model.command.create_link import ( - AbstractLinkCommand, - LinkInfoProperties, - LinkInfoProperties820, - LinkProperties, -) +from antarest.study.storage.variantstudy.model.command.create_link import AbstractLinkCommand from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand, OutputTuple +from antarest.study.storage.variantstudy.model.command_listener.command_listener import ICommandListener from antarest.study.storage.variantstudy.model.model import CommandDTO @@ -51,16 +43,17 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> OutputTuple: {"area_from": area_from, "area_to": area_to}, ) - def _apply(self, study_data: FileStudy, *a: str) -> CommandOutput: + def _apply(self, study_data: FileStudy, listener: t.Optional[ICommandListener] = None) -> CommandOutput: version = study_data.config.version area_from, area_to = sorted([self.area1, self.area2]) - properties = LinkProperties.model_validate(self.parameters or {}).model_dump(exclude_unset=True, by_alias=True) + properties = study_data.tree.get(["input", "links", area_from, "properties", area_to]) + + new_properties = LinkInternal.model_validate(self.parameters).model_dump(include=self.parameters, by_alias=True) - current_parameters = study_data.tree.get(["input", "links", area_from, "properties", area_to]) - current_parameters.update(properties) + properties.update(new_properties) - study_data.tree.save(current_parameters, ["input", "links", area_from, "properties", area_to]) + study_data.tree.save(properties, ["input", "links", area_from, "properties", area_to]) output, _ = self._apply_config(study_data.config) diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 6fcb807569..2b98963a9a 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -201,11 +201,11 @@ def create_link( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Update a link", - response_model=LinkInfoDTO, + response_model=LinkDTO, ) def update_link( uuid: str, - link_creation_info: LinkInfoDTO, + link_creation_info: LinkDTO, current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Any: area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index d5256b66d8..a6b2b0219c 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -105,7 +105,7 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ ) assert res.status_code == 500 expected = { - "description": "Unexpected exception occurred when trying to apply command CommandName.UPDATE_LINK: 1 validation error for LinkProperties\nwrong\n Extra inputs are not permitted [type=extra_forbidden, input_value='parameter', input_type=str]\n For further information visit https://errors.pydantic.dev/2.8/v/extra_forbidden", + "description": "Unexpected exception occurred when trying to apply command CommandName.UPDATE_LINK: 1 validation error for LinkInternal\nwrong\n Extra inputs are not permitted [type=extra_forbidden, input_value='parameter', input_type=str]\n For further information visit https://errors.pydantic.dev/2.8/v/extra_forbidden", "exception": "CommandApplicationError", } assert expected == res.json() @@ -225,7 +225,7 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area1_id}) assert res.status_code == 422, res.json() - expected = {"description": "Cannot create link on same area: area 1", "exception": "LinkValidationError"} + expected = {"description": "Area 1 and Area 2 can not be the same", "exception": "LinkValidationError"} assert expected == res.json() # Test create link with wrong value for enum From 4ccf7153b80e54cd0aa2c42f560080b8dbcf7ad2 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 6 Nov 2024 10:45:51 +0100 Subject: [PATCH 067/103] feat: refactor link path handling in link management. --- antarest/study/business/link_management.py | 2 +- .../study/storage/variantstudy/model/command/create_link.py | 5 ++++- .../study/storage/variantstudy/model/command/update_link.py | 6 ++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 62857cdfaf..dd5d46c440 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -29,7 +29,7 @@ from antarest.study.storage.variantstudy.model.command.update_link import UpdateLink _ALL_LINKS_PATH = "input/links" - +LINK_PATH = "input/links/{area_from}/properties/{area_to}" @all_optional_model @camel_case_model diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index e4bb5983a4..fa80192857 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -18,6 +18,7 @@ from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData +from antarest.study.business.link_management import LINK_PATH from antarest.study.business.model.link_model import LinkInternal from antarest.study.model import STUDY_VERSION_8_2 from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link @@ -265,7 +266,9 @@ def _apply(self, study_data: FileStudy, listener: Optional[ICommandListener] = N area_from = data["area_from"] area_to = data["area_to"] - study_data.tree.save(validated_properties, ["input", "links", area_from, "properties", area_to]) + link_path = LINK_PATH.format(area_from=area_from, area_to=area_to).split("/") + + study_data.tree.save(validated_properties, link_path) self.save_series(area_from, area_to, study_data, version) self.save_direct(area_from, area_to, study_data, version) diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 443e44e21f..81774ae282 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -11,6 +11,7 @@ # This file is part of the Antares project. import typing as t +from antarest.study.business.link_management import LINK_PATH from antarest.study.business.model.link_model import LinkInternal from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy @@ -46,14 +47,15 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> OutputTuple: def _apply(self, study_data: FileStudy, listener: t.Optional[ICommandListener] = None) -> CommandOutput: version = study_data.config.version area_from, area_to = sorted([self.area1, self.area2]) + link_path = LINK_PATH.format(area_from=area_from, area_to=area_to).split("/") - properties = study_data.tree.get(["input", "links", area_from, "properties", area_to]) + properties = study_data.tree.get(link_path) new_properties = LinkInternal.model_validate(self.parameters).model_dump(include=self.parameters, by_alias=True) properties.update(new_properties) - study_data.tree.save(properties, ["input", "links", area_from, "properties", area_to]) + study_data.tree.save(properties, link_path) output, _ = self._apply_config(study_data.config) From 38a1eb05b92021308346c446f359cd96d1e5369d Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 7 Nov 2024 11:19:31 +0100 Subject: [PATCH 068/103] feat: remove unused LINK_PATH and inline its usage --- antarest/study/business/link_management.py | 2 +- .../storage/variantstudy/model/command/create_link.py | 7 +------ .../storage/variantstudy/model/command/update_link.py | 6 ++---- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index dd5d46c440..62857cdfaf 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -29,7 +29,7 @@ from antarest.study.storage.variantstudy.model.command.update_link import UpdateLink _ALL_LINKS_PATH = "input/links" -LINK_PATH = "input/links/{area_from}/properties/{area_to}" + @all_optional_model @camel_case_model diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index fa80192857..3d2ec6f7b7 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -18,7 +18,6 @@ from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData -from antarest.study.business.link_management import LINK_PATH from antarest.study.business.model.link_model import LinkInternal from antarest.study.model import STUDY_VERSION_8_2 from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link @@ -26,8 +25,6 @@ from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput, FilteringOptions from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand -from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix -from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig from antarest.study.storage.variantstudy.model.command_listener.command_listener import ICommandListener from antarest.study.storage.variantstudy.model.model import CommandDTO @@ -266,9 +263,7 @@ def _apply(self, study_data: FileStudy, listener: Optional[ICommandListener] = N area_from = data["area_from"] area_to = data["area_to"] - link_path = LINK_PATH.format(area_from=area_from, area_to=area_to).split("/") - - study_data.tree.save(validated_properties, link_path) + study_data.tree.save(validated_properties, ["input", "links", area_from, "properties", area_to]) self.save_series(area_from, area_to, study_data, version) self.save_direct(area_from, area_to, study_data, version) diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 81774ae282..443e44e21f 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -11,7 +11,6 @@ # This file is part of the Antares project. import typing as t -from antarest.study.business.link_management import LINK_PATH from antarest.study.business.model.link_model import LinkInternal from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy @@ -47,15 +46,14 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> OutputTuple: def _apply(self, study_data: FileStudy, listener: t.Optional[ICommandListener] = None) -> CommandOutput: version = study_data.config.version area_from, area_to = sorted([self.area1, self.area2]) - link_path = LINK_PATH.format(area_from=area_from, area_to=area_to).split("/") - properties = study_data.tree.get(link_path) + properties = study_data.tree.get(["input", "links", area_from, "properties", area_to]) new_properties = LinkInternal.model_validate(self.parameters).model_dump(include=self.parameters, by_alias=True) properties.update(new_properties) - study_data.tree.save(properties, link_path) + study_data.tree.save(properties, ["input", "links", area_from, "properties", area_to]) output, _ = self._apply_config(study_data.config) From a5d827ff99ff330c92086a96abed5a1fbc4ded5c Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 09:25:33 +0100 Subject: [PATCH 069/103] feat: add header --- antarest/study/business/model/link_model.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index 36ac06d4cb..9493a4a9a5 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -1,3 +1,14 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. import typing as t from antares.study.version import StudyVersion From 3bf3651cc2802142364b8838371b44f6781299bc Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 09:27:54 +0100 Subject: [PATCH 070/103] feat: add header --- antarest/study/business/model/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/antarest/study/business/model/__init__.py b/antarest/study/business/model/__init__.py index e69de29bb2..f1fa536634 100644 --- a/antarest/study/business/model/__init__.py +++ b/antarest/study/business/model/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. \ No newline at end of file From e5c7bc672b2e5a8ea217e4a8ce51b485b8b1f97d Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 13:37:38 +0100 Subject: [PATCH 071/103] feat: update return type of get_links function --- antarest/study/web/study_data_blueprint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 13f8fd48cc..6d94a48c54 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -152,7 +152,7 @@ def get_areas( def get_links( uuid: str, current_user: JWTUser = Depends(auth.get_current_user), - ) -> t.Any: + ) -> t.List[LinkDTO]: logger.info( f"Fetching link list for study {uuid}", extra={"user": current_user.id}, From c474e35cf98b3adc6e8b4372432ae1fa97c1ec5b Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 13:56:27 +0100 Subject: [PATCH 072/103] feat: update return type of get_links function --- antarest/study/business/model/link_model.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index fd6bbf9670..22f5da2e9d 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -45,9 +45,9 @@ class LinkDTO(Area): transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED asset_type: AssetType = AssetType.AC display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) - colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) - colorg: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) + colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) + colorg: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN @@ -90,9 +90,9 @@ class LinkInternal(AntaresBaseModel): transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED asset_type: AssetType = AssetType.AC display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) - colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) - colorg: int = Field(default=DEFAULT_COLOR, gt=0, lt=255) + colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) + colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) + colorg: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES From 3e73279bc5b0b97c8c52ee7edbed812698960e02 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:32:21 +0100 Subject: [PATCH 073/103] fix(ui-api): update links endpoints with new DTO format --- webapp/package-lock.json | 8 +++ webapp/package.json | 6 ++- webapp/src/common/types.ts | 15 ------ .../Xpansion/Candidates/CandidateForm.tsx | 4 +- .../Candidates/CreateCandidateDialog.tsx | 4 +- .../explore/Xpansion/Candidates/index.tsx | 4 +- webapp/src/redux/ducks/studyMaps.ts | 33 ++++++++---- .../studies/config/thematicTrimming/index.ts | 4 +- .../services/api/studies/links/constants.ts | 35 ++++++++++++ .../src/services/api/studies/links/index.ts | 50 +++++++++++++++++ .../src/services/api/studies/links/types.ts | 54 +++++++++++++++++++ webapp/src/services/api/studies/raw/index.ts | 10 ++-- .../services/api/studies/tableMode/index.ts | 6 +-- webapp/src/services/api/studies/timeseries.ts | 2 +- webapp/src/services/api/studydata.ts | 43 +-------------- webapp/src/services/api/tasks/index.ts | 8 +-- 16 files changed, 195 insertions(+), 91 deletions(-) create mode 100644 webapp/src/services/api/studies/links/constants.ts create mode 100644 webapp/src/services/api/studies/links/index.ts create mode 100644 webapp/src/services/api/studies/links/types.ts diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 809c088d3d..b4f6734d44 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -62,6 +62,7 @@ "redux": "4.2.1", "redux-thunk": "2.4.2", "swagger-ui-react": "5.17.14", + "tinycolor2": "1.6.0", "ts-toolbelt": "9.6.0", "use-undo": "1.1.1", "uuid": "10.0.0", @@ -94,6 +95,7 @@ "@types/react-window": "1.8.8", "@types/swagger-ui-react": "4.18.3", "@types/testing-library__jest-dom": "6.0.0", + "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", "@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/parser": "7.2.0", @@ -4054,6 +4056,12 @@ "@testing-library/jest-dom": "*" } }, + "node_modules/@types/tinycolor2": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", + "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==", + "dev": true + }, "node_modules/@types/unist": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", diff --git a/webapp/package.json b/webapp/package.json index 117add1312..113cdf9238 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -26,8 +26,8 @@ "axios": "1.7.7", "clsx": "2.1.1", "d3": "5.16.0", - "debug": "4.3.7", "date-fns": "4.1.0", + "debug": "4.3.7", "draft-convert": "2.1.13", "draft-js": "0.11.7", "draftjs-to-html": "0.9.1", @@ -68,6 +68,7 @@ "redux": "4.2.1", "redux-thunk": "2.4.2", "swagger-ui-react": "5.17.14", + "tinycolor2": "1.6.0", "ts-toolbelt": "9.6.0", "use-undo": "1.1.1", "uuid": "10.0.0", @@ -79,12 +80,12 @@ "@testing-library/user-event": "14.5.2", "@total-typescript/ts-reset": "0.6.1", "@types/d3": "5.16.0", + "@types/date-fns": "2.6.0", "@types/debug": "4.1.12", "@types/draft-convert": "2.1.8", "@types/draft-js": "0.11.18", "@types/draftjs-to-html": "0.8.4", "@types/js-cookie": "3.0.6", - "@types/date-fns": "2.6.0", "@types/jsoneditor": "9.9.5", "@types/lodash": "4.17.9", "@types/node": "22.7.3", @@ -100,6 +101,7 @@ "@types/react-window": "1.8.8", "@types/swagger-ui-react": "4.18.3", "@types/testing-library__jest-dom": "6.0.0", + "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", "@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/parser": "7.2.0", diff --git a/webapp/src/common/types.ts b/webapp/src/common/types.ts index 4e087569f7..0674b5031a 100644 --- a/webapp/src/common/types.ts +++ b/webapp/src/common/types.ts @@ -531,21 +531,6 @@ export interface UpdateAreaUi { layerColor: AreaLayerColor; } -export interface LinkUIInfoDTO { - color: string; - style: string; - width: number; -} - -export interface LinkCreationInfoDTO { - area1: string; - area2: string; -} - -export interface LinkInfoWithUI extends LinkCreationInfoDTO { - ui: LinkUIInfoDTO; -} - export interface AreaCreationDTO { name: string; type: object; diff --git a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx index bb74aac2ad..8f0be1caa7 100644 --- a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx @@ -35,14 +35,14 @@ import { StyledVisibilityIcon, StyledDeleteIcon, } from "../share/styles"; -import { LinkCreationInfoDTO } from "../../../../../../common/types"; import { XpansionCandidate } from "../types"; import SelectSingle from "../../../../../common/SelectSingle"; import SwitchFE from "../../../../../common/fieldEditors/SwitchFE"; +import type { LinkDTO } from "@/services/api/studies/links/types"; interface PropType { candidate: XpansionCandidate | undefined; - links: LinkCreationInfoDTO[]; + links: LinkDTO[]; capacities: string[]; deleteCandidate: (name: string | undefined) => Promise; updateCandidate: ( diff --git a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx index b4c09015e7..8e03d0c70d 100644 --- a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx @@ -17,7 +17,6 @@ import { Button, ButtonGroup } from "@mui/material"; import { useTranslation } from "react-i18next"; import AddCircleIcon from "@mui/icons-material/AddCircle"; import * as R from "ramda"; -import { LinkCreationInfoDTO } from "../../../../../../common/types"; import { XpansionCandidate } from "../types"; import FormDialog from "../../../../../common/dialogs/FormDialog"; import StringFE from "../../../../../common/fieldEditors/StringFE"; @@ -26,10 +25,11 @@ import SelectFE from "../../../../../common/fieldEditors/SelectFE"; import NumberFE from "../../../../../common/fieldEditors/NumberFE"; import { SubmitHandlerPlus } from "../../../../../common/Form/types"; import { validateString } from "@/utils/validation/string"; +import type { LinkDTO } from "@/services/api/studies/links/types"; interface PropType { open: boolean; - links: LinkCreationInfoDTO[]; + links: LinkDTO[]; onClose: () => void; onSave: (candidate: XpansionCandidate) => void; candidates: XpansionCandidate[]; diff --git a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx index 66d41de0c2..43cc113532 100644 --- a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx @@ -36,7 +36,6 @@ import { removeEmptyFields, } from "../../../../../../services/utils/index"; import useEnqueueErrorSnackbar from "../../../../../../hooks/useEnqueueErrorSnackbar"; -import { getAllLinks } from "../../../../../../services/api/studydata"; import XpansionPropsView from "./XpansionPropsView"; import CreateCandidateDialog from "./CreateCandidateDialog"; import CandidateForm from "./CandidateForm"; @@ -44,6 +43,7 @@ import usePromiseWithSnackbarError from "../../../../../../hooks/usePromiseWithS import DataViewerDialog from "../../../../../common/dialogs/DataViewerDialog"; import EmptyView from "../../../../../common/page/SimpleContent"; import SplitView from "../../../../../common/SplitView"; +import { getLinks } from "@/services/api/studies/links"; function Candidates() { const [t] = useTranslation(); @@ -104,7 +104,7 @@ function Candidates() { if (exist) { return { capacities: await getAllCapacities(study.id), - links: await getAllLinks({ uuid: study.id }), + links: await getLinks({ studyId: study.id }), }; } return {}; diff --git a/webapp/src/redux/ducks/studyMaps.ts b/webapp/src/redux/ducks/studyMaps.ts index 9b5d1303dd..04dce9a7cc 100644 --- a/webapp/src/redux/ducks/studyMaps.ts +++ b/webapp/src/redux/ducks/studyMaps.ts @@ -47,11 +47,14 @@ import { getStudySynthesis, } from "../selectors"; import * as studyDataApi from "../../services/api/studydata"; +import * as linksApi from "../../services/api/studies/links"; import { createStudyLink, deleteStudyLink, setCurrentArea, } from "./studySyntheses"; +import type { TLinkStyle } from "@/services/api/studies/links/types"; +import tinycolor from "tinycolor2"; export interface StudyMapNode { id: string; @@ -167,11 +170,11 @@ export const setLayers = createAction< type LinkStyle = [number[], string]; -const makeLinkStyle = R.cond<[string], LinkStyle>([ - [R.equals("dot"), (): LinkStyle => [[1, 5], "round"]], - [R.equals("dash"), (): LinkStyle => [[16, 8], "square"]], - [R.equals("dotdash"), (): LinkStyle => [[10, 6, 1, 6], "square"]], - [R.T, (): LinkStyle => [[0], "butt"]], +const makeLinkStyle = R.cond<[TLinkStyle], LinkStyle>([ + [(v) => v === "dot", () => [[1, 5], "round"]], + [(v) => v === "dash", () => [[16, 8], "square"]], + [(v) => v === "dotdash", () => [[10, 6, 1, 6], "square"]], + [R.T, () => [[0], "butt"]], ]); const initStudyMapLayers = ( @@ -217,17 +220,21 @@ export const fetchStudyMapLayers = createAsyncThunk< async function getLinks( studyId: StudyMap["studyId"], ): Promise { - const links = await studyDataApi.getAllLinks({ uuid: studyId, withUi: true }); + const links = await linksApi.getLinks({ studyId }); return links.reduce( (acc, link) => { - const [style, linecap] = makeLinkStyle(link.ui?.style); + const [style, linecap] = makeLinkStyle(link.linkStyle); const id = makeLinkId(link.area1, link.area2); acc[id] = { id, - color: `rgb(${link.ui?.color}`, + color: tinycolor({ + r: link.colorr, + g: link.colorg, + b: link.colorb, + }).toRgbString(), strokeDasharray: style, strokeLinecap: linecap, - strokeWidth: link.ui?.width < 2 ? 2 : link.ui?.width, + strokeWidth: link.linkWidth < 2 ? 2 : link.linkWidth, }; return acc; }, @@ -398,7 +405,7 @@ export const createStudyMapLink = createAsyncThunk< ); try { - await studyDataApi.createLink(studyId, { area1, area2 }); + await linksApi.createLink({ studyId, area1, area2 }); } catch (err) { dispatch(deleteStudyLink({ studyId, area1, area2 })); dispatch(deleteStudyMapLinkTemp({ studyId, linkId })); @@ -425,7 +432,11 @@ export const deleteStudyMapLink = createAsyncThunk< dispatch(deleteStudyLink({ studyId, area1, area2 })); try { - await studyDataApi.deleteLink(studyId, area1, area2); + await linksApi.deleteLink({ + studyId, + areaFrom: area1, + areaTo: area2, + }); } catch (err) { dispatch(createStudyLink({ ...link, studyId, area1, area2 })); diff --git a/webapp/src/services/api/studies/config/thematicTrimming/index.ts b/webapp/src/services/api/studies/config/thematicTrimming/index.ts index c6a8bc8992..3444abeacc 100644 --- a/webapp/src/services/api/studies/config/thematicTrimming/index.ts +++ b/webapp/src/services/api/studies/config/thematicTrimming/index.ts @@ -26,8 +26,8 @@ export async function getThematicTrimmingConfig({ studyId, }: GetThematicTrimmingConfigParams) { const url = format(URL, { studyId }); - const res = await client.get(url); - return res.data; + const { data } = await client.get(url); + return data; } export async function setThematicTrimmingConfig({ diff --git a/webapp/src/services/api/studies/links/constants.ts b/webapp/src/services/api/studies/links/constants.ts new file mode 100644 index 0000000000..0bd9627ff7 --- /dev/null +++ b/webapp/src/services/api/studies/links/constants.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +export const TransmissionCapacity = { + Infinite: "infinite", + Ignore: "ignore", + Enabled: "enabled", +} as const; + +export const AssetType = { + AC: "ac", + DC: "dc", + Gaz: "gaz", + Virt: "virt", + Other: "other", +} as const; + +export const LinkStyle = { + Dot: "dot", + Plain: "plain", + Dash: "dash", + DotDash: "dotdash", + Other: "other", +} as const; diff --git a/webapp/src/services/api/studies/links/index.ts b/webapp/src/services/api/studies/links/index.ts new file mode 100644 index 0000000000..0947827906 --- /dev/null +++ b/webapp/src/services/api/studies/links/index.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +import { StudyMetadata } from "@/common/types"; +import client from "../../client"; +import type { CreateLinkParams, DeleteLinkParams, LinkDTO } from "./types"; + +export async function createLink(params: CreateLinkParams) { + const { studyId, ...body } = params; + const { data } = await client.post( + `/v1/studies/${params.studyId}/links`, + body, + ); + return data; +} + +export async function getLinks(params: { studyId: StudyMetadata["id"] }) { + const { data } = await client.get( + `/v1/studies/${params.studyId}/links`, + ); + return data; +} + +/** + * Deletes the link between the two specified areas. + * + * @param params - The parameters. + * @param params.studyId - The study ID. + * @param params.areaFrom - The from area name. + * @param params.areaTo - The to area name. + * @returns The deleted link id (format: `${areaFromId}%${areaToId}`) + */ +export async function deleteLink(params: DeleteLinkParams) { + const { studyId, areaFrom, areaTo } = params; + const { data } = await client.delete( + `/v1/studies/${studyId}/links/${areaFrom}/${areaTo}`, + ); + return data; +} diff --git a/webapp/src/services/api/studies/links/types.ts b/webapp/src/services/api/studies/links/types.ts new file mode 100644 index 0000000000..e6718eaa59 --- /dev/null +++ b/webapp/src/services/api/studies/links/types.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +import { O } from "ts-toolbelt"; +import { AssetType, LinkStyle, TransmissionCapacity } from "./constants"; +import { StudyMetadata } from "@/common/types"; +import { PartialExceptFor } from "@/utils/tsUtils"; + +export type TTransmissionCapacity = O.UnionOf; + +export type TAssetType = O.UnionOf; + +export type TLinkStyle = O.UnionOf; + +export interface LinkDTO { + hurdlesCost: boolean; + loopFlow: boolean; + usePhaseShifter: boolean; + transmissionCapacities: TTransmissionCapacity; + assetType: TAssetType; + displayComments: boolean; + colorr: number; + colorb: number; + colorg: number; + linkWidth: number; + linkStyle: TLinkStyle; + area1: string; + area2: string; + // Since v8.2 + filterSynthesis?: string; + filterYearByYear?: string; +} + +export interface CreateLinkParams + extends PartialExceptFor { + studyId: StudyMetadata["id"]; +} + +export interface DeleteLinkParams { + studyId: StudyMetadata["id"]; + areaFrom: string; + areaTo: string; +} diff --git a/webapp/src/services/api/studies/raw/index.ts b/webapp/src/services/api/studies/raw/index.ts index 9a86d92164..85524560d5 100644 --- a/webapp/src/services/api/studies/raw/index.ts +++ b/webapp/src/services/api/studies/raw/index.ts @@ -21,19 +21,19 @@ import type { export async function downloadMatrix(params: DownloadMatrixParams) { const { studyId, ...queryParams } = params; - const url = `v1/studies/${studyId}/raw/download`; + const url = `/v1/studies/${studyId}/raw/download`; - const res = await client.get(url, { + const { data } = await client.get(url, { params: queryParams, responseType: "blob", }); - return res.data; + return data; } export async function importFile(params: ImportFileParams) { const { studyId, file, onUploadProgress, ...queryParams } = params; - const url = `v1/studies/${studyId}/raw`; + const url = `/v1/studies/${studyId}/raw`; const body = { file }; await client.putForm(url, body, { @@ -47,7 +47,7 @@ export async function importFile(params: ImportFileParams) { export async function deleteFile(params: DeleteFileParams) { const { studyId, path } = params; - const url = `v1/studies/${studyId}/raw`; + const url = `/v1/studies/${studyId}/raw`; await client.delete(url, { params: { path } }); } diff --git a/webapp/src/services/api/studies/tableMode/index.ts b/webapp/src/services/api/studies/tableMode/index.ts index c20d54dbeb..1fcb75ffa0 100644 --- a/webapp/src/services/api/studies/tableMode/index.ts +++ b/webapp/src/services/api/studies/tableMode/index.ts @@ -21,7 +21,7 @@ import type { TableModeType, } from "./types"; -const TABLE_MODE_API_URL = `v1/studies/{studyId}/table-mode/{tableType}`; +const TABLE_MODE_API_URL = `/v1/studies/{studyId}/table-mode/{tableType}`; export async function getTableMode( params: GetTableModeParams, @@ -29,11 +29,11 @@ export async function getTableMode( const { studyId, tableType, columns } = params; const url = format(TABLE_MODE_API_URL, { studyId, tableType }); - const res = await client.get(url, { + const { data } = await client.get(url, { params: columns.length > 0 ? { columns: columns.join(",") } : {}, }); - return res.data; + return data; } export async function setTableMode(params: SetTableModeParams) { diff --git a/webapp/src/services/api/studies/timeseries.ts b/webapp/src/services/api/studies/timeseries.ts index 0329c49761..e786f076ef 100644 --- a/webapp/src/services/api/studies/timeseries.ts +++ b/webapp/src/services/api/studies/timeseries.ts @@ -26,7 +26,7 @@ export async function generateTimeSeries(params: { studyId: StudyMetadata["id"]; }) { const { data } = await client.put( - `v1/studies/${params.studyId}/timeseries/generate`, + `/v1/studies/${params.studyId}/timeseries/generate`, ); return data; } diff --git a/webapp/src/services/api/studydata.ts b/webapp/src/services/api/studydata.ts index a230be85bf..a6a05d8030 100644 --- a/webapp/src/services/api/studydata.ts +++ b/webapp/src/services/api/studydata.ts @@ -12,11 +12,7 @@ * This file is part of the Antares project. */ -import { - LinkCreationInfoDTO, - LinkInfoWithUI, - UpdateAreaUi, -} from "../../common/types"; +import { UpdateAreaUi } from "../../common/types"; import { BindingConstraint, ConstraintTerm, @@ -36,14 +32,6 @@ export const createArea = async ( return res.data; }; -export const createLink = async ( - uuid: string, - linkCreationInfo: LinkCreationInfoDTO, -): Promise => { - const res = await client.post(`/v1/studies/${uuid}/links`, linkCreationInfo); - return res.data; -}; - export const updateAreaUI = async ( uuid: string, areaId: string, @@ -65,17 +53,6 @@ export const deleteArea = async ( return res.data; }; -export const deleteLink = async ( - uuid: string, - areaIdFrom: string, - areaIdTo: string, -): Promise => { - const res = await client.delete( - `/v1/studies/${uuid}/links/${areaIdFrom}/${areaIdTo}`, - ); - return res.data; -}; - export const updateConstraintTerm = async ( studyId: string, constraintId: string, @@ -163,21 +140,3 @@ export const createBindingConstraint = async ( ); return res.data; }; - -interface GetAllLinksParams { - uuid: string; - withUi?: boolean; -} - -type LinkTypeFromParams = T["withUi"] extends true - ? LinkInfoWithUI - : LinkCreationInfoDTO; - -export const getAllLinks = async ( - params: T, -): Promise>> => { - const { uuid, withUi } = params; - const withUiStr = withUi ? "with_ui=true" : ""; - const res = await client.get(`/v1/studies/${uuid}/links?${withUiStr}`); - return res.data; -}; diff --git a/webapp/src/services/api/tasks/index.ts b/webapp/src/services/api/tasks/index.ts index c9003eaae0..f1599fc97e 100644 --- a/webapp/src/services/api/tasks/index.ts +++ b/webapp/src/services/api/tasks/index.ts @@ -16,7 +16,7 @@ import client from "../client"; import type { GetTaskParams, GetTasksParams, TaskDTO } from "./types"; export async function getTasks(params: GetTasksParams) { - const res = await client.post("/v1/tasks", { + const { data } = await client.post("/v1/tasks", { status: params.status, type: params.type, name: params.name, @@ -27,13 +27,13 @@ export async function getTasks(params: GetTasksParams) { to_completion_date_utc: params.toCompletionDateUtc, }); - return res.data; + return data; } export async function getTask(params: GetTaskParams) { const { id, ...queryParams } = params; - const res = await client.get(`/v1/tasks/${id}`, { + const { data } = await client.get(`/v1/tasks/${id}`, { params: { wait_for_completion: queryParams.waitForCompletion, with_logs: queryParams.withLogs, @@ -41,5 +41,5 @@ export async function getTask(params: GetTaskParams) { }, }); - return res.data; + return data; } From 443105b02422838863ca4f6b90d6dfe36da9f9a7 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 14:05:14 +0100 Subject: [PATCH 074/103] fix: fix color field validation in link model --- antarest/study/business/model/link_model.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index 22f5da2e9d..e68cfc3898 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -45,9 +45,9 @@ class LinkDTO(Area): transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED asset_type: AssetType = AssetType.AC display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) - colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) - colorg: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) + colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN @@ -90,9 +90,9 @@ class LinkInternal(AntaresBaseModel): transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED asset_type: AssetType = AssetType.AC display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) - colorb: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) - colorg: int = Field(default=DEFAULT_COLOR, gt=0, lt=256) + colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES From 40e190cc11a507c457cee4cbc6633c5945089e14 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 14:24:16 +0100 Subject: [PATCH 075/103] fix: correct color value validation --- antarest/study/business/model/link_model.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index e68cfc3898..3402bc55b7 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -45,9 +45,9 @@ class LinkDTO(Area): transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED asset_type: AssetType = AssetType.AC display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorr: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, le=255) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN @@ -90,9 +90,9 @@ class LinkInternal(AntaresBaseModel): transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED asset_type: AssetType = AssetType.AC display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorr: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, le=255) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES From 7a77cb668bbbc05a228860fd8e0d39c51847f11c Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 14:31:35 +0100 Subject: [PATCH 076/103] fix: fix test assertion message for input length validation --- tests/integration/study_data_blueprint/test_link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index a6b2b0219c..99f978eec1 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -249,7 +249,7 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: assert res.status_code == 422, res.json() expected = { "body": {"area1": "area 1", "area2": "area 2", "colorr": 260}, - "description": "Input should be less than 255", + "description": "Input should be less than or equal to 255", "exception": "RequestValidationError", } assert expected == res.json() From 8d2d34c55a8d4e170bbcf5889e563f75e16ee929 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 14:32:10 +0100 Subject: [PATCH 077/103] fix: fix test assertion message for input length validation --- tests/integration/study_data_blueprint/test_link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index e68bfc9d5d..36131bc69d 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -140,7 +140,7 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: assert res.status_code == 422, res.json() expected = { "body": {"area1": "area 1", "area2": "area 2", "colorr": 260}, - "description": "Input should be less than 255", + "description": "Input should be less than or equal to 255", "exception": "RequestValidationError", } assert expected == res.json() From f7951996764ab7e363fb3825bba4980fa43368d7 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 14:26:12 +0100 Subject: [PATCH 078/103] refactor(link): update DTO format --- antarest/core/exceptions.py | 5 + antarest/core/utils/string.py | 4 + antarest/study/business/link_management.py | 59 +++--- antarest/study/business/model/__init__.py | 0 antarest/study/business/model/link_model.py | 118 +++++++++++ .../study/business/table_mode_management.py | 6 +- antarest/study/service.py | 20 +- .../rawstudy/model/filesystem/config/links.py | 76 ++++++- .../variantstudy/model/command/create_link.py | 93 ++------- antarest/study/web/study_data_blueprint.py | 15 +- .../study_data_blueprint/test_link.py | 179 +++++++++++++++++ .../study_data_blueprint/test_table_mode.py | 6 + tests/integration/test_integration.py | 16 +- .../storage/business/test_arealink_manager.py | 186 +++++++++++++++++- .../model/command/test_create_link.py | 80 ++++---- 15 files changed, 671 insertions(+), 192 deletions(-) create mode 100644 antarest/study/business/model/__init__.py create mode 100644 antarest/study/business/model/link_model.py create mode 100644 tests/integration/study_data_blueprint/test_link.py diff --git a/antarest/core/exceptions.py b/antarest/core/exceptions.py index f0818d559c..5d574421a6 100644 --- a/antarest/core/exceptions.py +++ b/antarest/core/exceptions.py @@ -295,6 +295,11 @@ def __init__(self, message: str) -> None: super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) +class LinkValidationError(HTTPException): + def __init__(self, message: str) -> None: + super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) + + class VariantStudyParentNotValid(HTTPException): def __init__(self, message: str) -> None: super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) diff --git a/antarest/core/utils/string.py b/antarest/core/utils/string.py index 35c5e75541..ab45405753 100644 --- a/antarest/core/utils/string.py +++ b/antarest/core/utils/string.py @@ -18,3 +18,7 @@ def to_pascal_case(value: str) -> str: def to_camel_case(value: str) -> str: v = to_pascal_case(value) return v[0].lower() + v[1:] if len(v) > 0 else "" + + +def to_kebab_case(string: str) -> str: + return string.replace("_", "-") diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 54831ad8ac..cca12fcc2c 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,10 +12,12 @@ import typing as t -from antarest.core.exceptions import ConfigFileNotFound +from antares.study.version import StudyVersion + +from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError from antarest.core.model import JSON -from antarest.core.serialization import AntaresBaseModel from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model +from antarest.study.business.model.link_model import LinkDTO, LinkInternal from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties @@ -27,18 +29,6 @@ _ALL_LINKS_PATH = "input/links" -class LinkUIDTO(AntaresBaseModel): - color: str - width: float - style: str - - -class LinkInfoDTO(AntaresBaseModel): - area1: str - area2: str - ui: t.Optional[LinkUIDTO] = None - - @all_optional_model @camel_case_model class LinkOutput(LinkProperties): @@ -51,38 +41,39 @@ class LinkManager: def __init__(self, storage_service: StudyStorageService) -> None: self.storage_service = storage_service - def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkInfoDTO]: + def get_all_links(self, study: RawStudy) -> t.List[LinkDTO]: file_study = self.storage_service.get_storage(study).get_raw(study) - result = [] + result: t.List[LinkDTO] = [] + for area_id, area in file_study.config.areas.items(): - links_config: t.Optional[t.Dict[str, t.Any]] = None - if with_ui: - links_config = file_study.tree.get(["input", "links", area_id, "properties"]) + links_config = file_study.tree.get(["input", "links", area_id, "properties"]) + for link in area.links: - ui_info: t.Optional[LinkUIDTO] = None - if with_ui and links_config and link in links_config: - ui_info = LinkUIDTO( - color=f"{links_config[link].get('colorr', '163')},{links_config[link].get('colorg', '163')},{links_config[link].get('colorb', '163')}", - width=links_config[link].get("link-width", 1), - style=links_config[link].get("link-style", "plain"), - ) - result.append(LinkInfoDTO(area1=area_id, area2=link, ui=ui_info)) + link_tree_config: t.Dict[str, t.Any] = links_config[link] + link_tree_config.update({"area1": area_id, "area2": link}) + + link_internal = LinkInternal.model_validate(link_tree_config) + + result.append(link_internal.to_dto()) return result - def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: + def create_link(self, study: RawStudy, link_creation_dto: LinkDTO) -> LinkDTO: + link = link_creation_dto.to_internal(StudyVersion.parse(study.version)) + storage_service = self.storage_service.get_storage(study) file_study = storage_service.get_raw(study) + command = CreateLink( - area1=link_creation_info.area1, - area2=link_creation_info.area2, + area1=link.area1, + area2=link.area2, + parameters=link.model_dump(exclude_none=True), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) + execute_or_add_commands(study, file_study, [command], self.storage_service) - return LinkInfoDTO( - area1=link_creation_info.area1, - area2=link_creation_info.area2, - ) + + return link_creation_dto def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) diff --git a/antarest/study/business/model/__init__.py b/antarest/study/business/model/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py new file mode 100644 index 0000000000..e68cfc3898 --- /dev/null +++ b/antarest/study/business/model/link_model.py @@ -0,0 +1,118 @@ +import typing as t + +from antares.study.version import StudyVersion +from pydantic import ConfigDict, Field, model_validator + +from antarest.core.exceptions import LinkValidationError +from antarest.core.serialization import AntaresBaseModel +from antarest.core.utils.string import to_camel_case, to_kebab_case +from antarest.study.model import STUDY_VERSION_8_2 +from antarest.study.storage.rawstudy.model.filesystem.config.links import ( + AssetType, + FilterOption, + LinkStyle, + TransmissionCapacity, + comma_separated_enum_list, +) + +DEFAULT_COLOR = 112 +FILTER_VALUES: t.List[FilterOption] = [ + FilterOption.HOURLY, + FilterOption.DAILY, + FilterOption.WEEKLY, + FilterOption.MONTHLY, + FilterOption.ANNUAL, +] + + +class Area(AntaresBaseModel): + area1: str + area2: str + + @model_validator(mode="after") + def validate_areas(self) -> t.Self: + if self.area1 == self.area2: + raise LinkValidationError(f"Cannot create link on same area: {self.area1}") + return self + + +class LinkDTO(Area): + model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") + + hurdles_cost: bool = False + loop_flow: bool = False + use_phase_shifter: bool = False + transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED + asset_type: AssetType = AssetType.AC + display_comments: bool = True + colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + link_width: float = 1 + link_style: LinkStyle = LinkStyle.PLAIN + + filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES + filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES + + def to_internal(self, version: StudyVersion) -> "LinkInternal": + if version < STUDY_VERSION_8_2 and ( + "filter_synthesis" in self.model_fields_set or "filter_year_by_year" in self.model_fields_set + ): + raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") + + return LinkInternal( + area1=self.area1, + area2=self.area2, + hurdles_cost=self.hurdles_cost, + loop_flow=self.loop_flow, + use_phase_shifter=self.use_phase_shifter, + transmission_capacities=self.transmission_capacities, + asset_type=self.asset_type, + display_comments=self.display_comments, + colorr=self.colorr, + colorb=self.colorb, + colorg=self.colorg, + link_width=self.link_width, + link_style=self.link_style, + filter_synthesis=self.filter_synthesis if version >= STUDY_VERSION_8_2 else None, + filter_year_by_year=self.filter_year_by_year if version >= STUDY_VERSION_8_2 else None, + ) + + +class LinkInternal(AntaresBaseModel): + model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True, extra="forbid") + + area1: str = "area1" + area2: str = "area2" + hurdles_cost: bool = False + loop_flow: bool = False + use_phase_shifter: bool = False + transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED + asset_type: AssetType = AssetType.AC + display_comments: bool = True + colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + link_width: float = 1 + link_style: LinkStyle = LinkStyle.PLAIN + filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES + filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES + + def to_dto(self) -> LinkDTO: + return LinkDTO( + area1=self.area1, + area2=self.area2, + hurdles_cost=self.hurdles_cost, + loop_flow=self.loop_flow, + use_phase_shifter=self.use_phase_shifter, + transmission_capacities=self.transmission_capacities, + asset_type=self.asset_type, + display_comments=self.display_comments, + colorr=self.colorr, + colorb=self.colorb, + colorg=self.colorg, + link_width=self.link_width, + link_style=self.link_style, + filter_synthesis=self.filter_synthesis, + filter_year_by_year=self.filter_year_by_year, + ) diff --git a/antarest/study/business/table_mode_management.py b/antarest/study/business/table_mode_management.py index b108c57cb8..9d62d17869 100644 --- a/antarest/study/business/table_mode_management.py +++ b/antarest/study/business/table_mode_management.py @@ -98,8 +98,9 @@ def _get_table_data_unsafe(self, study: RawStudy, table_type: TableModeType) -> data = {area_id: area.model_dump(mode="json", by_alias=True) for area_id, area in areas_map.items()} elif table_type == TableModeType.LINK: links_map = self._link_manager.get_all_links_props(study) + excludes = set() if int(study.version) >= 820 else {"filter_synthesis", "filter_year_by_year"} data = { - f"{area1_id} / {area2_id}": link.model_dump(mode="json", by_alias=True) + f"{area1_id} / {area2_id}": link.model_dump(mode="json", by_alias=True, exclude=excludes) for (area1_id, area2_id), link in links_map.items() } elif table_type == TableModeType.THERMAL: @@ -195,8 +196,9 @@ def update_table_data( elif table_type == TableModeType.LINK: links_map = {tuple(key.split(" / ")): LinkOutput(**values) for key, values in data.items()} updated_map = self._link_manager.update_links_props(study, links_map) # type: ignore + excludes = set() if int(study.version) >= 820 else {"filter_synthesis", "filter_year_by_year"} data = { - f"{area1_id} / {area2_id}": link.model_dump(by_alias=True) + f"{area1_id} / {area2_id}": link.model_dump(by_alias=True, exclude=excludes) for (area1_id, area2_id), link in updated_map.items() } return data diff --git a/antarest/study/service.py b/antarest/study/service.py index 19067b6f9a..3170e311d0 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -86,8 +86,9 @@ from antarest.study.business.correlation_management import CorrelationManager from antarest.study.business.district_manager import DistrictManager from antarest.study.business.general_management import GeneralManager -from antarest.study.business.link_management import LinkInfoDTO, LinkManager +from antarest.study.business.link_management import LinkManager from antarest.study.business.matrix_management import MatrixManager, MatrixManagerError +from antarest.study.business.model.link_model import LinkDTO from antarest.study.business.optimization_management import OptimizationManager from antarest.study.business.playlist_management import PlaylistManager from antarest.study.business.scenario_builder_management import ScenarioBuilderManager @@ -358,7 +359,7 @@ def __init__( self.task_service = task_service self.areas = AreaManager(self.storage_service, self.repository) self.district_manager = DistrictManager(self.storage_service) - self.links = LinkManager(self.storage_service) + self.links_manager = LinkManager(self.storage_service) self.config_manager = ConfigManager(self.storage_service) self.general_manager = GeneralManager(self.storage_service) self.thematic_trimming_manager = ThematicTrimmingManager(self.storage_service) @@ -380,7 +381,7 @@ def __init__( self.correlation_manager = CorrelationManager(self.storage_service) self.table_mode_manager = TableModeManager( self.areas, - self.links, + self.links_manager, self.thermal_manager, self.renewable_manager, self.st_storage_manager, @@ -1867,12 +1868,11 @@ def get_all_areas( def get_all_links( self, uuid: str, - with_ui: bool, params: RequestParameters, - ) -> t.List[LinkInfoDTO]: + ) -> t.List[LinkDTO]: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.READ) - return self.links.get_all_links(study, with_ui) + return self.links_manager.get_all_links(study) def create_area( self, @@ -1896,13 +1896,13 @@ def create_area( def create_link( self, uuid: str, - link_creation_dto: LinkInfoDTO, + link_creation_dto: LinkDTO, params: RequestParameters, - ) -> LinkInfoDTO: + ) -> LinkDTO: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.WRITE) self._assert_study_unarchived(study) - new_link = self.links.create_link(study, link_creation_dto) + new_link = self.links_manager.create_link(study, link_creation_dto) self.event_bus.push( Event( type=EventType.STUDY_DATA_EDITED, @@ -2018,7 +2018,7 @@ def delete_link( if referencing_binding_constraints: binding_ids = [bc.id for bc in referencing_binding_constraints] raise ReferencedObjectDeletionNotAllowed(link_id, binding_ids, object_type="Link") - self.links.delete_link(study, area_from, area_to) + self.links_manager.delete_link(study, area_from, area_to) self.event_bus.push( Event( type=EventType.STUDY_DATA_EDITED, diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/links.py b/antarest/study/storage/rawstudy/model/filesystem/config/links.py index 88059ef50e..f782cfb406 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/links.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/links.py @@ -16,8 +16,9 @@ import typing as t -from pydantic import Field, field_validator, model_validator +from pydantic import BeforeValidator, Field, PlainSerializer, field_validator, model_validator +from antarest.core.exceptions import LinkValidationError from antarest.study.business.enum_ignore_case import EnumIgnoreCase from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import ( validate_color_rgb, @@ -66,6 +67,74 @@ class TransmissionCapacity(EnumIgnoreCase): ENABLED = "enabled" +class LinkStyle(EnumIgnoreCase): + """ + Enum representing the style of a link in a network visualization. + + Attributes: + DOT: Represents a dotted line style. + PLAIN: Represents a solid line style. + DASH: Represents a dashed line style. + DOT_DASH: Represents a line style with alternating dots and dashes. + """ + + DOT = "dot" + PLAIN = "plain" + DASH = "dash" + DOT_DASH = "dotdash" + OTHER = "other" + + +class FilterOption(EnumIgnoreCase): + """ + Enum representing the time filter options for data visualization or analysis in Antares Web. + + Attributes: + HOURLY: Represents filtering data by the hour. + DAILY: Represents filtering data by the day. + WEEKLY: Represents filtering data by the week. + MONTHLY: Represents filtering data by the month. + ANNUAL: Represents filtering data by the year. + """ + + HOURLY = "hourly" + DAILY = "daily" + WEEKLY = "weekly" + MONTHLY = "monthly" + ANNUAL = "annual" + + +def validate_filters( + filter_value: t.Union[t.List[FilterOption], str], enum_cls: t.Type[FilterOption] +) -> t.List[FilterOption]: + if filter_value is not None and isinstance(filter_value, str): + filter_accepted_values = [e for e in enum_cls] + + options = filter_value.replace(" ", "").split(",") + + invalid_options = [opt for opt in options if opt not in filter_accepted_values] + if invalid_options: + raise LinkValidationError( + f"Invalid value(s) in filters: {', '.join(invalid_options)}. " + f"Allowed values are: {', '.join(filter_accepted_values)}." + ) + + return [enum_cls(opt) for opt in options] + + return filter_value + + +def join_with_comma(values: t.List[FilterOption]) -> str: + return ", ".join(value.name.lower() for value in values) + + +comma_separated_enum_list = t.Annotated[ + t.List[FilterOption], + BeforeValidator(lambda x: validate_filters(x, FilterOption)), + PlainSerializer(lambda x: join_with_comma(x)), +] + + class LinkProperties(IniProperties): """ Configuration read from a section in the `input/links//properties.ini` file. @@ -159,11 +228,12 @@ def _validate_colors(cls, values: t.MutableMapping[str, t.Any]) -> t.Mapping[str return validate_colors(values) # noinspection SpellCheckingInspection - def to_config(self) -> t.Dict[str, t.Any]: + def to_ini(self, version: int) -> t.Dict[str, t.Any]: """ Convert the object to a dictionary for writing to a configuration file. """ - obj = dict(super().to_config()) + excludes = set() if version >= 820 else {"filter_synthesis", "filter_year_by_year"} + obj = self.model_dump(mode="json", exclude_none=True, by_alias=True, exclude=excludes) color_rgb = obj.pop("colorRgb", "#707070") return { "colorr": int(color_rgb[1:3], 16), diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 7954981d99..bd93a8252f 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -9,42 +9,25 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - from typing import Any, Dict, List, Optional, Tuple, Union, cast from pydantic import ValidationInfo, field_validator, model_validator -from antarest.core.model import JSON from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData +from antarest.study.business.model.link_model import LinkInternal from antarest.study.model import STUDY_VERSION_8_2 from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput, FilteringOptions from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand +from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix +from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig from antarest.study.storage.variantstudy.model.command_listener.command_listener import ICommandListener from antarest.study.storage.variantstudy.model.model import CommandDTO -class LinkProperties: - HURDLES_COST: bool = False - LOOP_FLOW: bool = False - USE_PHASE_SHIFTER: bool = False - DISPLAY_COMMENTS: bool = True - TRANSMISSION_CAPACITIES: str = "enabled" - ASSET_TYPE: str = "ac" - LINK_STYLE: str = "plain" - LINK_WIDTH: int = 1 - COLORR: int = 112 - COLORG: int = 112 - COLORB: int = 112 - - -class LinkAlreadyExistError(Exception): - pass - - class CreateLink(ICommand): """ Command used to create a link between two areas. @@ -98,51 +81,6 @@ def _create_link_in_config(self, area_from: str, area_to: str, study_data: FileS ], ) - @staticmethod - def generate_link_properties(parameters: JSON) -> JSON: - return { - "hurdles-cost": parameters.get( - "hurdles-cost", - LinkProperties.HURDLES_COST, - ), - "loop-flow": parameters.get("loop-flow", LinkProperties.LOOP_FLOW), - "use-phase-shifter": parameters.get( - "use-phase-shifter", - LinkProperties.USE_PHASE_SHIFTER, - ), - "transmission-capacities": parameters.get( - "transmission-capacities", - LinkProperties.TRANSMISSION_CAPACITIES, - ), - "asset-type": parameters.get( - "asset-type", - LinkProperties.ASSET_TYPE, - ), - "link-style": parameters.get( - "link-style", - LinkProperties.LINK_STYLE, - ), - "link-width": parameters.get( - "link-width", - LinkProperties.LINK_WIDTH, - ), - "colorr": parameters.get("colorr", LinkProperties.COLORR), - "colorg": parameters.get("colorg", LinkProperties.COLORG), - "colorb": parameters.get("colorb", LinkProperties.COLORB), - "display-comments": parameters.get( - "display-comments", - LinkProperties.DISPLAY_COMMENTS, - ), - "filter-synthesis": parameters.get( - "filter-synthesis", - FilteringOptions.FILTER_SYNTHESIS, - ), - "filter-year-by-year": parameters.get( - "filter-year-by-year", - FilteringOptions.FILTER_YEAR_BY_YEAR, - ), - } - def _apply_config(self, study_data: FileStudyTreeConfig) -> Tuple[CommandOutput, Dict[str, Any]]: if self.area1 not in study_data.areas: return ( @@ -161,15 +99,6 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> Tuple[CommandOutput, {}, ) - if self.area1 == self.area2: - return ( - CommandOutput( - status=False, - message="Cannot create link between the same node", - ), - {}, - ) - # Link parameters between two areas are stored in only one of the two # areas in the "input/links" tree. One area acts as source (`area_from`) # and the other as target (`area_to`). @@ -211,13 +140,16 @@ def _apply(self, study_data: FileStudy, listener: Optional[ICommandListener] = N output, data = self._apply_config(study_data.config) if not output.status: return output + + validated_properties = LinkInternal.model_validate(self.parameters).model_dump( + by_alias=True, exclude={"area1", "area2"} + ) + area_from = data["area_from"] area_to = data["area_to"] - self.parameters = self.parameters or {} - link_property = CreateLink.generate_link_properties(self.parameters) + study_data.tree.save(validated_properties, ["input", "links", area_from, "properties", area_to]) - study_data.tree.save(link_property, ["input", "links", area_from, "properties", area_to]) self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) @@ -298,13 +230,14 @@ def match(self, other: ICommand, equal: bool = False) -> bool: def _create_diff(self, other: "ICommand") -> List["ICommand"]: other = cast(CreateLink, other) - from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix - from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig commands: List[ICommand] = [] area_from, area_to = sorted([self.area1, self.area2]) if self.parameters != other.parameters: - link_property = CreateLink.generate_link_properties(other.parameters or {}) + properties = LinkInternal.model_validate(other.parameters or {}) + link_property = properties.model_dump( + mode="json", by_alias=True, exclude_none=True, exclude={"area1", "area2"} + ) commands.append( UpdateConfig( target=f"input/links/{area_from}/properties/{area_to}", diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index a6d39c7164..6d94a48c54 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -68,7 +68,7 @@ ) from antarest.study.business.district_manager import DistrictCreationDTO, DistrictInfoDTO, DistrictUpdateDTO from antarest.study.business.general_management import GeneralFormFields -from antarest.study.business.link_management import LinkInfoDTO +from antarest.study.business.model.link_model import LinkDTO from antarest.study.business.optimization_management import OptimizationFormFields from antarest.study.business.playlist_management import PlaylistColumns from antarest.study.business.scenario_builder_management import Rulesets, ScenarioType @@ -147,19 +147,18 @@ def get_areas( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Get all links", - response_model=t.List[LinkInfoDTO], + response_model=t.List[LinkDTO], ) def get_links( uuid: str, - with_ui: bool = False, current_user: JWTUser = Depends(auth.get_current_user), - ) -> t.Any: + ) -> t.List[LinkDTO]: logger.info( f"Fetching link list for study {uuid}", extra={"user": current_user.id}, ) params = RequestParameters(user=current_user) - areas_list = study_service.get_all_links(uuid, with_ui, params) + areas_list = study_service.get_all_links(uuid, params) return areas_list @bp.post( @@ -184,13 +183,13 @@ def create_area( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Create a link", - response_model=LinkInfoDTO, + response_model=LinkDTO, ) def create_link( uuid: str, - link_creation_info: LinkInfoDTO, + link_creation_info: LinkDTO, current_user: JWTUser = Depends(auth.get_current_user), - ) -> t.Any: + ) -> LinkDTO: logger.info( f"Creating new link for study {uuid}", extra={"user": current_user.id}, diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py new file mode 100644 index 0000000000..e68bfc9d5d --- /dev/null +++ b/tests/integration/study_data_blueprint/test_link.py @@ -0,0 +1,179 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import pytest +from starlette.testclient import TestClient + +from antarest.study.storage.rawstudy.model.filesystem.config.links import TransmissionCapacity +from tests.integration.prepare_proxy import PreparerProxy + + +@pytest.mark.unit_test +class TestLink: + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_link_820(self, client: TestClient, user_access_token: str, study_type: str) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=820) + if study_type == "variant": + study_id = preparer.create_variant(study_id, name="Variant 1") + + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + area3_id = preparer.create_area(study_id, name="Area 3")["id"] + + # Test create link with default values + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) + + assert res.status_code == 200, res.json() + + expected = { + "area1": "area 1", + "area2": "area 2", + "assetType": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "displayComments": True, + "filterSynthesis": "hourly, daily, weekly, monthly, annual", + "filterYearByYear": "hourly, daily, weekly, monthly, annual", + "hurdlesCost": False, + "linkStyle": "plain", + "linkWidth": 1.0, + "loopFlow": False, + "transmissionCapacities": "enabled", + "usePhaseShifter": False, + } + assert expected == res.json() + res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") + res.raise_for_status() + + # Test create link with parameters + + parameters = { + "area1": "area 1", + "area2": "area 2", + "assetType": "ac", + "colorb": 160, + "colorg": 170, + "colorr": 180, + "displayComments": True, + "filterSynthesis": "hourly, daily, weekly, monthly, annual", + "filterYearByYear": "hourly, daily, weekly, monthly, annual", + "hurdlesCost": False, + "linkStyle": "plain", + "linkWidth": 1.0, + "loopFlow": False, + "transmissionCapacities": "enabled", + "usePhaseShifter": False, + } + res = client.post( + f"/v1/studies/{study_id}/links", + json=parameters, + ) + + assert res.status_code == 200, res.json() + + assert parameters == res.json() + res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") + res.raise_for_status() + + # Create two links, count them, then delete one + + res1 = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) + res2 = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area3_id}) + + assert res1.status_code == 200, res1.json() + assert res2.status_code == 200, res2.json() + + res = client.get(f"/v1/studies/{study_id}/links") + + assert res.status_code == 200, res.json() + assert 2 == len(res.json()) + + res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area3_id}") + res.raise_for_status() + + res = client.get(f"/v1/studies/{study_id}/links") + + assert res.status_code == 200, res.json() + assert 1 == len(res.json()) + client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") + res.raise_for_status() + + # Test create link with same area + + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area1_id}) + + assert res.status_code == 422, res.json() + expected = {"description": "Cannot create link on same area: area 1", "exception": "LinkValidationError"} + assert expected == res.json() + + # Test create link with wrong value for enum + + res = client.post( + f"/v1/studies/{study_id}/links", + json={"area1": area1_id, "area2": area2_id, "assetType": TransmissionCapacity.ENABLED}, + ) + assert res.status_code == 422, res.json() + expected = { + "body": {"area1": "area 1", "area2": "area 2", "assetType": "enabled"}, + "description": "Input should be 'ac', 'dc', 'gaz', 'virt' or 'other'", + "exception": "RequestValidationError", + } + assert expected == res.json() + + # Test create link with wrong color parameter + + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "colorr": 260}) + + assert res.status_code == 422, res.json() + expected = { + "body": {"area1": "area 1", "area2": "area 2", "colorr": 260}, + "description": "Input should be less than 255", + "exception": "RequestValidationError", + } + assert expected == res.json() + + # Test create link with wrong filter parameter + + res = client.post( + f"/v1/studies/{study_id}/links", + json={"area1": area1_id, "area2": area2_id, "filterSynthesis": "centurial"}, + ) + + assert res.status_code == 422, res.json() + expected = { + "description": "Invalid value(s) in filters: centurial. Allowed values are: hourly, daily, weekly, monthly, annual.", + "exception": "LinkValidationError", + } + assert expected == res.json() + + def test_create_link_810(self, client: TestClient, user_access_token: str) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=810) + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + + res = client.post( + f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filterSynthesis": "hourly"} + ) + + assert res.status_code == 422, res.json() + expected = { + "description": "Cannot specify a filter value for study's version earlier than v8.2", + "exception": "LinkValidationError", + } + assert expected == res.json() diff --git a/tests/integration/study_data_blueprint/test_table_mode.py b/tests/integration/study_data_blueprint/test_table_mode.py index 98d7f03393..a7b1878020 100644 --- a/tests/integration/study_data_blueprint/test_table_mode.py +++ b/tests/integration/study_data_blueprint/test_table_mode.py @@ -293,6 +293,12 @@ def test_lifecycle__nominal( "usePhaseShifter": False, }, } + # removes filter fields for study version prior to v8.2 + if study_version < 820: + for key in expected_links: + del expected_links[key]["filterSynthesis"] + del expected_links[key]["filterYearByYear"] + # asserts actual equals expected without the non-updated link. actual = res.json() expected_result = copy.deepcopy(expected_links) diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index a9fa59088f..37f34f5273 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -593,12 +593,24 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: }, ) res.raise_for_status() - res_links = client.get(f"/v1/studies/{study_id}/links?with_ui=true") + res_links = client.get(f"/v1/studies/{study_id}/links") assert res_links.json() == [ { "area1": "area 1", "area2": "area 2", - "ui": {"color": "112,112,112", "style": "plain", "width": 1.0}, + "assetType": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "displayComments": True, + "filterSynthesis": "hourly, daily, weekly, monthly, annual", + "filterYearByYear": "hourly, daily, weekly, monthly, annual", + "hurdlesCost": False, + "linkStyle": "plain", + "linkWidth": 1.0, + "loopFlow": False, + "transmissionCapacities": "enabled", + "usePhaseShifter": False, } ] diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index cb1d29c971..582c6067c1 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -24,11 +24,12 @@ from antarest.matrixstore.repository import MatrixContentRepository from antarest.matrixstore.service import SimpleMatrixService from antarest.study.business.area_management import AreaCreationDTO, AreaManager, AreaType, UpdateAreaUi -from antarest.study.business.link_management import LinkInfoDTO, LinkManager +from antarest.study.business.link_management import LinkDTO, LinkManager from antarest.study.model import Patch, PatchArea, PatchCluster, RawStudy, StudyAdditionalData from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService from antarest.study.storage.rawstudy.model.filesystem.config.files import build +from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, LinkStyle, TransmissionCapacity from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, DistrictSet, FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.config.thermal import ThermalConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy @@ -37,7 +38,7 @@ from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.business.matrix_constants_generator import GeneratorMatrixConstants from antarest.study.storage.variantstudy.command_factory import CommandFactory -from antarest.study.storage.variantstudy.model.command.common import CommandName +from antarest.study.storage.variantstudy.model.command.common import CommandName, FilteringOptions from antarest.study.storage.variantstudy.model.dbmodel import VariantStudy from antarest.study.storage.variantstudy.model.model import CommandDTO from antarest.study.storage.variantstudy.variant_study_service import VariantStudyService @@ -102,6 +103,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = RawStudy( id=study_id, + version="-1", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -134,8 +136,16 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): } area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) - link_manager.create_link(study, LinkInfoDTO(area1="test", area2="test2")) + study.version = 820 + link_manager.create_link( + study, + LinkDTO( + area1="test", + area2="test2", + ), + ) assert empty_study.config.areas["test"].links.get("test2") is not None + study.version = -1 link_manager.delete_link(study, "test", "test2") assert empty_study.config.areas["test"].links.get("test2") is None @@ -148,6 +158,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = VariantStudy( id=variant_id, + version="-1", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -216,7 +227,52 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ) area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) - link_manager.create_link(study, LinkInfoDTO(area1="test", area2="test2")) + study.version = 880 + link_manager.create_link( + study, + LinkDTO( + area1="test", + area2="test2", + ), + ) + variant_study_service.append_commands.assert_called_with( + variant_id, + [ + CommandDTO( + action=CommandName.CREATE_LINK.value, + args={ + "area1": "test", + "area2": "test2", + "parameters": { + "area1": "test", + "area2": "test2", + "hurdles_cost": False, + "loop_flow": False, + "use_phase_shifter": False, + "transmission_capacities": TransmissionCapacity.ENABLED, + "asset_type": AssetType.AC, + "display_comments": True, + "colorr": 112, + "colorg": 112, + "colorb": 112, + "link_width": 1.0, + "link_style": LinkStyle.PLAIN, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + }, + }, + ), + ], + RequestParameters(DEFAULT_ADMIN_USER), + ) + study.version = 810 + link_manager.create_link( + study, + LinkDTO( + area1="test", + area2="test2", + ), + ) variant_study_service.append_commands.assert_called_with( variant_id, [ @@ -225,7 +281,21 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): args={ "area1": "test", "area2": "test2", - "parameters": None, + "parameters": { + "area1": "test", + "area2": "test2", + "hurdles_cost": False, + "loop_flow": False, + "use_phase_shifter": False, + "transmission_capacities": TransmissionCapacity.ENABLED, + "asset_type": AssetType.AC, + "display_comments": True, + "colorr": 112, + "colorg": 112, + "colorb": 112, + "link_width": 1.0, + "link_style": LinkStyle.PLAIN, + }, }, ), ], @@ -260,7 +330,7 @@ def test_get_all_area(): ) link_manager = LinkManager(storage_service=StudyStorageService(raw_study_service, Mock())) - study = RawStudy() + study = RawStudy(version="900") config = FileStudyTreeConfig( study_path=Path("somepath"), path=Path("somepath"), @@ -416,12 +486,108 @@ def test_get_all_area(): all_areas = area_manager.get_all_areas(study) assert expected_all == [area.model_dump() for area in all_areas] + file_tree_mock.get.side_effect = [ + { + "a2": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": False, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, + }, + "a3": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": False, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, + }, + }, + { + "a3": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": False, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, + } + }, + { + "a3": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": False, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, + } + }, + ] links = link_manager.get_all_links(study) assert [ - {"area1": "a1", "area2": "a2", "ui": None}, - {"area1": "a1", "area2": "a3", "ui": None}, - {"area1": "a2", "area2": "a3", "ui": None}, - ] == [link.model_dump() for link in links] + { + "area1": "a1", + "area2": "a2", + "asset_type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "display_comments": False, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": False, + "link_style": "plain", + "link_width": 1.0, + "loop_flow": False, + "transmission_capacities": "enabled", + "use_phase_shifter": False, + }, + { + "area1": "a1", + "area2": "a3", + "asset_type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "display_comments": False, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": False, + "link_style": "plain", + "link_width": 1.0, + "loop_flow": False, + "transmission_capacities": "enabled", + "use_phase_shifter": False, + }, + { + "area1": "a2", + "area2": "a3", + "asset_type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "display_comments": False, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": False, + "link_style": "plain", + "link_width": 1.0, + "loop_flow": False, + "transmission_capacities": "enabled", + "use_phase_shifter": False, + }, + ] == [link.model_dump(mode="json") for link in links] def test_update_area(): diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index 9d847f0f24..33a536b514 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -10,19 +10,17 @@ # # This file is part of the Antares project. -import configparser - import numpy as np import pytest from pydantic import ValidationError +from antarest.study.business.link_management import LinkInternal from antarest.study.storage.rawstudy.ini_reader import IniReader from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter -from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_area import CreateArea -from antarest.study.storage.variantstudy.model.command.create_link import CreateLink, LinkProperties +from antarest.study.storage.variantstudy.model.command.create_link import CreateLink from antarest.study.storage.variantstudy.model.command.icommand import ICommand from antarest.study.storage.variantstudy.model.command.remove_area import RemoveArea from antarest.study.storage.variantstudy.model.command.remove_link import RemoveLink @@ -34,8 +32,6 @@ class TestCreateLink: def test_validation(self, empty_study: FileStudy, command_context: CommandContext): area1 = "Area1" - area1_id = transform_name_to_id(area1) - area2 = "Area2" CreateArea.model_validate( @@ -54,8 +50,8 @@ def test_validation(self, empty_study: FileStudy, command_context: CommandContex with pytest.raises(ValidationError): CreateLink( - area1=area1_id, - area2=area1_id, + area1=area1, + area2=area1, parameters={}, command_context=command_context, series=[[0]], @@ -110,19 +106,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): link = IniReader() link_data = link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert link_data[area2_id]["hurdles-cost"] == LinkProperties.HURDLES_COST - assert link_data[area2_id]["loop-flow"] == LinkProperties.LOOP_FLOW - assert link_data[area2_id]["use-phase-shifter"] == LinkProperties.USE_PHASE_SHIFTER - assert str(link_data[area2_id]["transmission-capacities"]) == LinkProperties.TRANSMISSION_CAPACITIES - assert str(link_data[area2_id]["asset-type"]) == LinkProperties.ASSET_TYPE - assert str(link_data[area2_id]["link-style"]) == LinkProperties.LINK_STYLE - assert int(link_data[area2_id]["link-width"]) == LinkProperties.LINK_WIDTH - assert int(link_data[area2_id]["colorr"]) == LinkProperties.COLORR - assert int(link_data[area2_id]["colorg"]) == LinkProperties.COLORG - assert int(link_data[area2_id]["colorb"]) == LinkProperties.COLORB - assert link_data[area2_id]["display-comments"] == LinkProperties.DISPLAY_COMMENTS - assert str(link_data[area2_id]["filter-synthesis"]) == FilteringOptions.FILTER_SYNTHESIS - assert str(link_data[area2_id]["filter-year-by-year"]) == FilteringOptions.FILTER_YEAR_BY_YEAR + assert link_data[area2_id]["hurdles-cost"] is False + assert link_data[area2_id]["loop-flow"] is False + assert link_data[area2_id]["use-phase-shifter"] is False + assert link_data[area2_id]["transmission-capacities"] == "enabled" + assert link_data[area2_id]["asset-type"] == "ac" + assert link_data[area2_id]["link-style"] == "plain" + assert int(link_data[area2_id]["link-width"]) == 1 + assert int(link_data[area2_id]["colorr"]) == 112 + assert int(link_data[area2_id]["colorg"]) == 112 + assert int(link_data[area2_id]["colorb"]) == 112 + assert link_data[area2_id]["display-comments"] is True empty_study.config.version = 820 create_link_command: ICommand = CreateLink( @@ -157,17 +151,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): assert not output.status parameters = { - "hurdles-cost": "true", - "loop-flow": "true", - "use-phase-shifter": "true", - "transmission-capacities": "disabled", + "hurdles-cost": True, + "loop-flow": True, + "use-phase-shifter": True, + "transmission-capacities": "ignore", "asset-type": "dc", "link-style": "other", "link-width": 12, "colorr": 120, "colorg": 120, "colorb": 120, - "display-comments": "true", + "display-comments": True, "filter-synthesis": "hourly", "filter-year-by-year": "hourly", } @@ -189,21 +183,19 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): assert (study_path / "input" / "links" / area1_id / f"{area3_id}.txt.link").exists() - link = configparser.ConfigParser() - link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert str(link[area3_id]["hurdles-cost"]) == parameters["hurdles-cost"] - assert str(link[area3_id]["loop-flow"]) == parameters["loop-flow"] - assert str(link[area3_id]["use-phase-shifter"]) == parameters["use-phase-shifter"] - assert str(link[area3_id]["transmission-capacities"]) == parameters["transmission-capacities"] - assert str(link[area3_id]["asset-type"]) == parameters["asset-type"] - assert str(link[area3_id]["link-style"]) == parameters["link-style"] - assert int(link[area3_id]["link-width"]) == parameters["link-width"] - assert int(link[area3_id]["colorr"]) == parameters["colorr"] - assert int(link[area3_id]["colorg"]) == parameters["colorg"] - assert int(link[area3_id]["colorb"]) == parameters["colorb"] - assert str(link[area3_id]["display-comments"]) == parameters["display-comments"] - assert str(link[area3_id]["filter-synthesis"]) == parameters["filter-synthesis"] - assert str(link[area3_id]["filter-year-by-year"]) == parameters["filter-year-by-year"] + link = IniReader() + link_data = link.read(study_path / "input" / "links" / area1_id / "properties.ini") + assert link_data[area3_id]["hurdles-cost"] == parameters["hurdles-cost"] + assert link_data[area3_id]["loop-flow"] == parameters["loop-flow"] + assert link_data[area3_id]["use-phase-shifter"] == parameters["use-phase-shifter"] + assert link_data[area3_id]["transmission-capacities"] == parameters["transmission-capacities"] + assert link_data[area3_id]["asset-type"] == parameters["asset-type"] + assert link_data[area3_id]["link-style"] == parameters["link-style"] + assert int(link_data[area3_id]["link-width"]) == parameters["link-width"] + assert int(link_data[area3_id]["colorr"]) == parameters["colorr"] + assert int(link_data[area3_id]["colorg"]) == parameters["colorg"] + assert int(link_data[area3_id]["colorb"]) == parameters["colorb"] + assert link_data[area3_id]["display-comments"] == parameters["display-comments"] output = create_link_command.apply( study_data=empty_study, @@ -258,7 +250,7 @@ def test_create_diff(command_context: CommandContext): other_match = CreateLink( area1="foo", area2="bar", - parameters={"hurdles-cost": "true"}, + parameters={"hurdles_cost": "true"}, series=series_b, command_context=command_context, ) @@ -266,7 +258,9 @@ def test_create_diff(command_context: CommandContext): assert base.create_diff(other_match) == [ UpdateConfig( target="input/links/bar/properties/foo", - data=CreateLink.generate_link_properties({"hurdles-cost": "true"}), + data=LinkInternal.model_validate({"area1": "bar", "area2": "foo", "hurdles_cost": "true"}).model_dump( + by_alias=True, exclude_none=True, exclude={"area1", "area2"} + ), command_context=command_context, ), ReplaceMatrix( From 2d07e2110852579494c218f266b23d2822c70144 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:32:21 +0100 Subject: [PATCH 079/103] refactor(ui-api): update links endpoints with new DTO format --- webapp/package-lock.json | 8 +++ webapp/package.json | 6 ++- webapp/src/common/types.ts | 15 ------ .../Xpansion/Candidates/CandidateForm.tsx | 4 +- .../Candidates/CreateCandidateDialog.tsx | 4 +- .../explore/Xpansion/Candidates/index.tsx | 4 +- webapp/src/redux/ducks/studyMaps.ts | 33 ++++++++---- .../studies/config/thematicTrimming/index.ts | 4 +- .../services/api/studies/links/constants.ts | 35 ++++++++++++ .../src/services/api/studies/links/index.ts | 50 +++++++++++++++++ .../src/services/api/studies/links/types.ts | 54 +++++++++++++++++++ webapp/src/services/api/studies/raw/index.ts | 10 ++-- .../services/api/studies/tableMode/index.ts | 6 +-- webapp/src/services/api/studies/timeseries.ts | 2 +- webapp/src/services/api/studydata.ts | 43 +-------------- webapp/src/services/api/tasks/index.ts | 8 +-- 16 files changed, 195 insertions(+), 91 deletions(-) create mode 100644 webapp/src/services/api/studies/links/constants.ts create mode 100644 webapp/src/services/api/studies/links/index.ts create mode 100644 webapp/src/services/api/studies/links/types.ts diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 809c088d3d..b4f6734d44 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -62,6 +62,7 @@ "redux": "4.2.1", "redux-thunk": "2.4.2", "swagger-ui-react": "5.17.14", + "tinycolor2": "1.6.0", "ts-toolbelt": "9.6.0", "use-undo": "1.1.1", "uuid": "10.0.0", @@ -94,6 +95,7 @@ "@types/react-window": "1.8.8", "@types/swagger-ui-react": "4.18.3", "@types/testing-library__jest-dom": "6.0.0", + "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", "@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/parser": "7.2.0", @@ -4054,6 +4056,12 @@ "@testing-library/jest-dom": "*" } }, + "node_modules/@types/tinycolor2": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", + "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==", + "dev": true + }, "node_modules/@types/unist": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", diff --git a/webapp/package.json b/webapp/package.json index 117add1312..113cdf9238 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -26,8 +26,8 @@ "axios": "1.7.7", "clsx": "2.1.1", "d3": "5.16.0", - "debug": "4.3.7", "date-fns": "4.1.0", + "debug": "4.3.7", "draft-convert": "2.1.13", "draft-js": "0.11.7", "draftjs-to-html": "0.9.1", @@ -68,6 +68,7 @@ "redux": "4.2.1", "redux-thunk": "2.4.2", "swagger-ui-react": "5.17.14", + "tinycolor2": "1.6.0", "ts-toolbelt": "9.6.0", "use-undo": "1.1.1", "uuid": "10.0.0", @@ -79,12 +80,12 @@ "@testing-library/user-event": "14.5.2", "@total-typescript/ts-reset": "0.6.1", "@types/d3": "5.16.0", + "@types/date-fns": "2.6.0", "@types/debug": "4.1.12", "@types/draft-convert": "2.1.8", "@types/draft-js": "0.11.18", "@types/draftjs-to-html": "0.8.4", "@types/js-cookie": "3.0.6", - "@types/date-fns": "2.6.0", "@types/jsoneditor": "9.9.5", "@types/lodash": "4.17.9", "@types/node": "22.7.3", @@ -100,6 +101,7 @@ "@types/react-window": "1.8.8", "@types/swagger-ui-react": "4.18.3", "@types/testing-library__jest-dom": "6.0.0", + "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", "@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/parser": "7.2.0", diff --git a/webapp/src/common/types.ts b/webapp/src/common/types.ts index 4e087569f7..0674b5031a 100644 --- a/webapp/src/common/types.ts +++ b/webapp/src/common/types.ts @@ -531,21 +531,6 @@ export interface UpdateAreaUi { layerColor: AreaLayerColor; } -export interface LinkUIInfoDTO { - color: string; - style: string; - width: number; -} - -export interface LinkCreationInfoDTO { - area1: string; - area2: string; -} - -export interface LinkInfoWithUI extends LinkCreationInfoDTO { - ui: LinkUIInfoDTO; -} - export interface AreaCreationDTO { name: string; type: object; diff --git a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx index bb74aac2ad..8f0be1caa7 100644 --- a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx @@ -35,14 +35,14 @@ import { StyledVisibilityIcon, StyledDeleteIcon, } from "../share/styles"; -import { LinkCreationInfoDTO } from "../../../../../../common/types"; import { XpansionCandidate } from "../types"; import SelectSingle from "../../../../../common/SelectSingle"; import SwitchFE from "../../../../../common/fieldEditors/SwitchFE"; +import type { LinkDTO } from "@/services/api/studies/links/types"; interface PropType { candidate: XpansionCandidate | undefined; - links: LinkCreationInfoDTO[]; + links: LinkDTO[]; capacities: string[]; deleteCandidate: (name: string | undefined) => Promise; updateCandidate: ( diff --git a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx index b4c09015e7..8e03d0c70d 100644 --- a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx @@ -17,7 +17,6 @@ import { Button, ButtonGroup } from "@mui/material"; import { useTranslation } from "react-i18next"; import AddCircleIcon from "@mui/icons-material/AddCircle"; import * as R from "ramda"; -import { LinkCreationInfoDTO } from "../../../../../../common/types"; import { XpansionCandidate } from "../types"; import FormDialog from "../../../../../common/dialogs/FormDialog"; import StringFE from "../../../../../common/fieldEditors/StringFE"; @@ -26,10 +25,11 @@ import SelectFE from "../../../../../common/fieldEditors/SelectFE"; import NumberFE from "../../../../../common/fieldEditors/NumberFE"; import { SubmitHandlerPlus } from "../../../../../common/Form/types"; import { validateString } from "@/utils/validation/string"; +import type { LinkDTO } from "@/services/api/studies/links/types"; interface PropType { open: boolean; - links: LinkCreationInfoDTO[]; + links: LinkDTO[]; onClose: () => void; onSave: (candidate: XpansionCandidate) => void; candidates: XpansionCandidate[]; diff --git a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx index 66d41de0c2..43cc113532 100644 --- a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx @@ -36,7 +36,6 @@ import { removeEmptyFields, } from "../../../../../../services/utils/index"; import useEnqueueErrorSnackbar from "../../../../../../hooks/useEnqueueErrorSnackbar"; -import { getAllLinks } from "../../../../../../services/api/studydata"; import XpansionPropsView from "./XpansionPropsView"; import CreateCandidateDialog from "./CreateCandidateDialog"; import CandidateForm from "./CandidateForm"; @@ -44,6 +43,7 @@ import usePromiseWithSnackbarError from "../../../../../../hooks/usePromiseWithS import DataViewerDialog from "../../../../../common/dialogs/DataViewerDialog"; import EmptyView from "../../../../../common/page/SimpleContent"; import SplitView from "../../../../../common/SplitView"; +import { getLinks } from "@/services/api/studies/links"; function Candidates() { const [t] = useTranslation(); @@ -104,7 +104,7 @@ function Candidates() { if (exist) { return { capacities: await getAllCapacities(study.id), - links: await getAllLinks({ uuid: study.id }), + links: await getLinks({ studyId: study.id }), }; } return {}; diff --git a/webapp/src/redux/ducks/studyMaps.ts b/webapp/src/redux/ducks/studyMaps.ts index 9b5d1303dd..04dce9a7cc 100644 --- a/webapp/src/redux/ducks/studyMaps.ts +++ b/webapp/src/redux/ducks/studyMaps.ts @@ -47,11 +47,14 @@ import { getStudySynthesis, } from "../selectors"; import * as studyDataApi from "../../services/api/studydata"; +import * as linksApi from "../../services/api/studies/links"; import { createStudyLink, deleteStudyLink, setCurrentArea, } from "./studySyntheses"; +import type { TLinkStyle } from "@/services/api/studies/links/types"; +import tinycolor from "tinycolor2"; export interface StudyMapNode { id: string; @@ -167,11 +170,11 @@ export const setLayers = createAction< type LinkStyle = [number[], string]; -const makeLinkStyle = R.cond<[string], LinkStyle>([ - [R.equals("dot"), (): LinkStyle => [[1, 5], "round"]], - [R.equals("dash"), (): LinkStyle => [[16, 8], "square"]], - [R.equals("dotdash"), (): LinkStyle => [[10, 6, 1, 6], "square"]], - [R.T, (): LinkStyle => [[0], "butt"]], +const makeLinkStyle = R.cond<[TLinkStyle], LinkStyle>([ + [(v) => v === "dot", () => [[1, 5], "round"]], + [(v) => v === "dash", () => [[16, 8], "square"]], + [(v) => v === "dotdash", () => [[10, 6, 1, 6], "square"]], + [R.T, () => [[0], "butt"]], ]); const initStudyMapLayers = ( @@ -217,17 +220,21 @@ export const fetchStudyMapLayers = createAsyncThunk< async function getLinks( studyId: StudyMap["studyId"], ): Promise { - const links = await studyDataApi.getAllLinks({ uuid: studyId, withUi: true }); + const links = await linksApi.getLinks({ studyId }); return links.reduce( (acc, link) => { - const [style, linecap] = makeLinkStyle(link.ui?.style); + const [style, linecap] = makeLinkStyle(link.linkStyle); const id = makeLinkId(link.area1, link.area2); acc[id] = { id, - color: `rgb(${link.ui?.color}`, + color: tinycolor({ + r: link.colorr, + g: link.colorg, + b: link.colorb, + }).toRgbString(), strokeDasharray: style, strokeLinecap: linecap, - strokeWidth: link.ui?.width < 2 ? 2 : link.ui?.width, + strokeWidth: link.linkWidth < 2 ? 2 : link.linkWidth, }; return acc; }, @@ -398,7 +405,7 @@ export const createStudyMapLink = createAsyncThunk< ); try { - await studyDataApi.createLink(studyId, { area1, area2 }); + await linksApi.createLink({ studyId, area1, area2 }); } catch (err) { dispatch(deleteStudyLink({ studyId, area1, area2 })); dispatch(deleteStudyMapLinkTemp({ studyId, linkId })); @@ -425,7 +432,11 @@ export const deleteStudyMapLink = createAsyncThunk< dispatch(deleteStudyLink({ studyId, area1, area2 })); try { - await studyDataApi.deleteLink(studyId, area1, area2); + await linksApi.deleteLink({ + studyId, + areaFrom: area1, + areaTo: area2, + }); } catch (err) { dispatch(createStudyLink({ ...link, studyId, area1, area2 })); diff --git a/webapp/src/services/api/studies/config/thematicTrimming/index.ts b/webapp/src/services/api/studies/config/thematicTrimming/index.ts index c6a8bc8992..3444abeacc 100644 --- a/webapp/src/services/api/studies/config/thematicTrimming/index.ts +++ b/webapp/src/services/api/studies/config/thematicTrimming/index.ts @@ -26,8 +26,8 @@ export async function getThematicTrimmingConfig({ studyId, }: GetThematicTrimmingConfigParams) { const url = format(URL, { studyId }); - const res = await client.get(url); - return res.data; + const { data } = await client.get(url); + return data; } export async function setThematicTrimmingConfig({ diff --git a/webapp/src/services/api/studies/links/constants.ts b/webapp/src/services/api/studies/links/constants.ts new file mode 100644 index 0000000000..0bd9627ff7 --- /dev/null +++ b/webapp/src/services/api/studies/links/constants.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +export const TransmissionCapacity = { + Infinite: "infinite", + Ignore: "ignore", + Enabled: "enabled", +} as const; + +export const AssetType = { + AC: "ac", + DC: "dc", + Gaz: "gaz", + Virt: "virt", + Other: "other", +} as const; + +export const LinkStyle = { + Dot: "dot", + Plain: "plain", + Dash: "dash", + DotDash: "dotdash", + Other: "other", +} as const; diff --git a/webapp/src/services/api/studies/links/index.ts b/webapp/src/services/api/studies/links/index.ts new file mode 100644 index 0000000000..0947827906 --- /dev/null +++ b/webapp/src/services/api/studies/links/index.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +import { StudyMetadata } from "@/common/types"; +import client from "../../client"; +import type { CreateLinkParams, DeleteLinkParams, LinkDTO } from "./types"; + +export async function createLink(params: CreateLinkParams) { + const { studyId, ...body } = params; + const { data } = await client.post( + `/v1/studies/${params.studyId}/links`, + body, + ); + return data; +} + +export async function getLinks(params: { studyId: StudyMetadata["id"] }) { + const { data } = await client.get( + `/v1/studies/${params.studyId}/links`, + ); + return data; +} + +/** + * Deletes the link between the two specified areas. + * + * @param params - The parameters. + * @param params.studyId - The study ID. + * @param params.areaFrom - The from area name. + * @param params.areaTo - The to area name. + * @returns The deleted link id (format: `${areaFromId}%${areaToId}`) + */ +export async function deleteLink(params: DeleteLinkParams) { + const { studyId, areaFrom, areaTo } = params; + const { data } = await client.delete( + `/v1/studies/${studyId}/links/${areaFrom}/${areaTo}`, + ); + return data; +} diff --git a/webapp/src/services/api/studies/links/types.ts b/webapp/src/services/api/studies/links/types.ts new file mode 100644 index 0000000000..e6718eaa59 --- /dev/null +++ b/webapp/src/services/api/studies/links/types.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +import { O } from "ts-toolbelt"; +import { AssetType, LinkStyle, TransmissionCapacity } from "./constants"; +import { StudyMetadata } from "@/common/types"; +import { PartialExceptFor } from "@/utils/tsUtils"; + +export type TTransmissionCapacity = O.UnionOf; + +export type TAssetType = O.UnionOf; + +export type TLinkStyle = O.UnionOf; + +export interface LinkDTO { + hurdlesCost: boolean; + loopFlow: boolean; + usePhaseShifter: boolean; + transmissionCapacities: TTransmissionCapacity; + assetType: TAssetType; + displayComments: boolean; + colorr: number; + colorb: number; + colorg: number; + linkWidth: number; + linkStyle: TLinkStyle; + area1: string; + area2: string; + // Since v8.2 + filterSynthesis?: string; + filterYearByYear?: string; +} + +export interface CreateLinkParams + extends PartialExceptFor { + studyId: StudyMetadata["id"]; +} + +export interface DeleteLinkParams { + studyId: StudyMetadata["id"]; + areaFrom: string; + areaTo: string; +} diff --git a/webapp/src/services/api/studies/raw/index.ts b/webapp/src/services/api/studies/raw/index.ts index 9a86d92164..85524560d5 100644 --- a/webapp/src/services/api/studies/raw/index.ts +++ b/webapp/src/services/api/studies/raw/index.ts @@ -21,19 +21,19 @@ import type { export async function downloadMatrix(params: DownloadMatrixParams) { const { studyId, ...queryParams } = params; - const url = `v1/studies/${studyId}/raw/download`; + const url = `/v1/studies/${studyId}/raw/download`; - const res = await client.get(url, { + const { data } = await client.get(url, { params: queryParams, responseType: "blob", }); - return res.data; + return data; } export async function importFile(params: ImportFileParams) { const { studyId, file, onUploadProgress, ...queryParams } = params; - const url = `v1/studies/${studyId}/raw`; + const url = `/v1/studies/${studyId}/raw`; const body = { file }; await client.putForm(url, body, { @@ -47,7 +47,7 @@ export async function importFile(params: ImportFileParams) { export async function deleteFile(params: DeleteFileParams) { const { studyId, path } = params; - const url = `v1/studies/${studyId}/raw`; + const url = `/v1/studies/${studyId}/raw`; await client.delete(url, { params: { path } }); } diff --git a/webapp/src/services/api/studies/tableMode/index.ts b/webapp/src/services/api/studies/tableMode/index.ts index c20d54dbeb..1fcb75ffa0 100644 --- a/webapp/src/services/api/studies/tableMode/index.ts +++ b/webapp/src/services/api/studies/tableMode/index.ts @@ -21,7 +21,7 @@ import type { TableModeType, } from "./types"; -const TABLE_MODE_API_URL = `v1/studies/{studyId}/table-mode/{tableType}`; +const TABLE_MODE_API_URL = `/v1/studies/{studyId}/table-mode/{tableType}`; export async function getTableMode( params: GetTableModeParams, @@ -29,11 +29,11 @@ export async function getTableMode( const { studyId, tableType, columns } = params; const url = format(TABLE_MODE_API_URL, { studyId, tableType }); - const res = await client.get(url, { + const { data } = await client.get(url, { params: columns.length > 0 ? { columns: columns.join(",") } : {}, }); - return res.data; + return data; } export async function setTableMode(params: SetTableModeParams) { diff --git a/webapp/src/services/api/studies/timeseries.ts b/webapp/src/services/api/studies/timeseries.ts index 0329c49761..e786f076ef 100644 --- a/webapp/src/services/api/studies/timeseries.ts +++ b/webapp/src/services/api/studies/timeseries.ts @@ -26,7 +26,7 @@ export async function generateTimeSeries(params: { studyId: StudyMetadata["id"]; }) { const { data } = await client.put( - `v1/studies/${params.studyId}/timeseries/generate`, + `/v1/studies/${params.studyId}/timeseries/generate`, ); return data; } diff --git a/webapp/src/services/api/studydata.ts b/webapp/src/services/api/studydata.ts index a230be85bf..a6a05d8030 100644 --- a/webapp/src/services/api/studydata.ts +++ b/webapp/src/services/api/studydata.ts @@ -12,11 +12,7 @@ * This file is part of the Antares project. */ -import { - LinkCreationInfoDTO, - LinkInfoWithUI, - UpdateAreaUi, -} from "../../common/types"; +import { UpdateAreaUi } from "../../common/types"; import { BindingConstraint, ConstraintTerm, @@ -36,14 +32,6 @@ export const createArea = async ( return res.data; }; -export const createLink = async ( - uuid: string, - linkCreationInfo: LinkCreationInfoDTO, -): Promise => { - const res = await client.post(`/v1/studies/${uuid}/links`, linkCreationInfo); - return res.data; -}; - export const updateAreaUI = async ( uuid: string, areaId: string, @@ -65,17 +53,6 @@ export const deleteArea = async ( return res.data; }; -export const deleteLink = async ( - uuid: string, - areaIdFrom: string, - areaIdTo: string, -): Promise => { - const res = await client.delete( - `/v1/studies/${uuid}/links/${areaIdFrom}/${areaIdTo}`, - ); - return res.data; -}; - export const updateConstraintTerm = async ( studyId: string, constraintId: string, @@ -163,21 +140,3 @@ export const createBindingConstraint = async ( ); return res.data; }; - -interface GetAllLinksParams { - uuid: string; - withUi?: boolean; -} - -type LinkTypeFromParams = T["withUi"] extends true - ? LinkInfoWithUI - : LinkCreationInfoDTO; - -export const getAllLinks = async ( - params: T, -): Promise>> => { - const { uuid, withUi } = params; - const withUiStr = withUi ? "with_ui=true" : ""; - const res = await client.get(`/v1/studies/${uuid}/links?${withUiStr}`); - return res.data; -}; diff --git a/webapp/src/services/api/tasks/index.ts b/webapp/src/services/api/tasks/index.ts index c9003eaae0..f1599fc97e 100644 --- a/webapp/src/services/api/tasks/index.ts +++ b/webapp/src/services/api/tasks/index.ts @@ -16,7 +16,7 @@ import client from "../client"; import type { GetTaskParams, GetTasksParams, TaskDTO } from "./types"; export async function getTasks(params: GetTasksParams) { - const res = await client.post("/v1/tasks", { + const { data } = await client.post("/v1/tasks", { status: params.status, type: params.type, name: params.name, @@ -27,13 +27,13 @@ export async function getTasks(params: GetTasksParams) { to_completion_date_utc: params.toCompletionDateUtc, }); - return res.data; + return data; } export async function getTask(params: GetTaskParams) { const { id, ...queryParams } = params; - const res = await client.get(`/v1/tasks/${id}`, { + const { data } = await client.get(`/v1/tasks/${id}`, { params: { wait_for_completion: queryParams.waitForCompletion, with_logs: queryParams.withLogs, @@ -41,5 +41,5 @@ export async function getTask(params: GetTaskParams) { }, }); - return res.data; + return data; } From b6f317e3bc5375635921bc9409a7a5aa6126671e Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 14:43:49 +0100 Subject: [PATCH 080/103] fix: add copyright and license headers --- antarest/study/business/model/__init__.py | 11 +++++++++++ antarest/study/business/model/link_model.py | 11 +++++++++++ tests/integration/study_data_blueprint/test_link.py | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/antarest/study/business/model/__init__.py b/antarest/study/business/model/__init__.py index e69de29bb2..f1fa536634 100644 --- a/antarest/study/business/model/__init__.py +++ b/antarest/study/business/model/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. \ No newline at end of file diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index e68cfc3898..c577a73f0d 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -1,3 +1,14 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. import typing as t from antares.study.version import StudyVersion diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index e68bfc9d5d..36131bc69d 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -140,7 +140,7 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: assert res.status_code == 422, res.json() expected = { "body": {"area1": "area 1", "area2": "area 2", "colorr": 260}, - "description": "Input should be less than 255", + "description": "Input should be less than or equal to 255", "exception": "RequestValidationError", } assert expected == res.json() From e6c3a3e9e31fe0fb305d995128d19937555700fc Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 14:47:28 +0100 Subject: [PATCH 081/103] fix: add copyright and license headers --- antarest/study/business/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antarest/study/business/model/__init__.py b/antarest/study/business/model/__init__.py index f1fa536634..058c6b221a 100644 --- a/antarest/study/business/model/__init__.py +++ b/antarest/study/business/model/__init__.py @@ -8,4 +8,4 @@ # # SPDX-License-Identifier: MPL-2.0 # -# This file is part of the Antares project. \ No newline at end of file +# This file is part of the Antares project. From 913178e339e0c5d01ca4fb3cd39c2f4dd7340d76 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 15:00:07 +0100 Subject: [PATCH 082/103] fix: adjust color field validation limits --- antarest/study/business/model/link_model.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index c577a73f0d..b516481164 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -56,9 +56,9 @@ class LinkDTO(Area): transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED asset_type: AssetType = AssetType.AC display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorr: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, le=255) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN @@ -101,9 +101,9 @@ class LinkInternal(AntaresBaseModel): transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED asset_type: AssetType = AssetType.AC display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorr: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, le=255) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES From 738683fa36e9a37f89c7ed98d6aa715363f1286f Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 14 Nov 2024 10:57:45 +0100 Subject: [PATCH 083/103] fix: refactor and simplify link validation and creation following github review --- antarest/study/business/link_management.py | 6 +-- antarest/study/business/model/link_model.py | 49 +++++-------------- .../study/business/table_mode_management.py | 6 +-- .../rawstudy/model/filesystem/config/links.py | 17 +------ .../variantstudy/model/command/create_link.py | 6 ++- .../study_data_blueprint/test_link.py | 5 +- .../storage/business/test_arealink_manager.py | 9 ++-- 7 files changed, 31 insertions(+), 67 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index cca12fcc2c..6b22278293 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -14,12 +14,12 @@ from antares.study.version import StudyVersion -from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError +from antarest.core.exceptions import ConfigFileNotFound from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.model.link_model import LinkDTO, LinkInternal from antarest.study.business.utils import execute_or_add_commands -from antarest.study.model import RawStudy +from antarest.study.model import RawStudy, Study from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.create_link import CreateLink @@ -58,7 +58,7 @@ def get_all_links(self, study: RawStudy) -> t.List[LinkDTO]: return result - def create_link(self, study: RawStudy, link_creation_dto: LinkDTO) -> LinkDTO: + def create_link(self, study: Study, link_creation_dto: LinkDTO) -> LinkDTO: link = link_creation_dto.to_internal(StudyVersion.parse(study.version)) storage_service = self.storage_service.get_storage(study) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index b516481164..d43a9e6e0b 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -43,7 +43,7 @@ class Area(AntaresBaseModel): @model_validator(mode="after") def validate_areas(self) -> t.Self: if self.area1 == self.area2: - raise LinkValidationError(f"Cannot create link on same area: {self.area1}") + raise LinkValidationError(f"Cannot create a link that goes from and to the same single area: {self.area1}") return self @@ -66,28 +66,16 @@ class LinkDTO(Area): filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES def to_internal(self, version: StudyVersion) -> "LinkInternal": - if version < STUDY_VERSION_8_2 and ( - "filter_synthesis" in self.model_fields_set or "filter_year_by_year" in self.model_fields_set - ): + if version < STUDY_VERSION_8_2 and {"filter_synthesis", "filter_year_by_year"} & self.model_fields_set: raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") - return LinkInternal( - area1=self.area1, - area2=self.area2, - hurdles_cost=self.hurdles_cost, - loop_flow=self.loop_flow, - use_phase_shifter=self.use_phase_shifter, - transmission_capacities=self.transmission_capacities, - asset_type=self.asset_type, - display_comments=self.display_comments, - colorr=self.colorr, - colorb=self.colorb, - colorg=self.colorg, - link_width=self.link_width, - link_style=self.link_style, - filter_synthesis=self.filter_synthesis if version >= STUDY_VERSION_8_2 else None, - filter_year_by_year=self.filter_year_by_year if version >= STUDY_VERSION_8_2 else None, - ) + data = self.model_dump() + + if version < STUDY_VERSION_8_2: + data["filter_synthesis"] = None + data["filter_year_by_year"] = None + + return LinkInternal(**data) class LinkInternal(AntaresBaseModel): @@ -110,20 +98,5 @@ class LinkInternal(AntaresBaseModel): filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES def to_dto(self) -> LinkDTO: - return LinkDTO( - area1=self.area1, - area2=self.area2, - hurdles_cost=self.hurdles_cost, - loop_flow=self.loop_flow, - use_phase_shifter=self.use_phase_shifter, - transmission_capacities=self.transmission_capacities, - asset_type=self.asset_type, - display_comments=self.display_comments, - colorr=self.colorr, - colorb=self.colorb, - colorg=self.colorg, - link_width=self.link_width, - link_style=self.link_style, - filter_synthesis=self.filter_synthesis, - filter_year_by_year=self.filter_year_by_year, - ) + data = self.model_dump() + return LinkDTO(**data) diff --git a/antarest/study/business/table_mode_management.py b/antarest/study/business/table_mode_management.py index 9d62d17869..a353213cda 100644 --- a/antarest/study/business/table_mode_management.py +++ b/antarest/study/business/table_mode_management.py @@ -25,7 +25,7 @@ from antarest.study.business.binding_constraint_management import BindingConstraintManager, ConstraintInput from antarest.study.business.enum_ignore_case import EnumIgnoreCase from antarest.study.business.link_management import LinkManager, LinkOutput -from antarest.study.model import RawStudy +from antarest.study.model import STUDY_VERSION_8_2, RawStudy _TableIndex = str # row name _TableColumn = str # column name @@ -98,7 +98,7 @@ def _get_table_data_unsafe(self, study: RawStudy, table_type: TableModeType) -> data = {area_id: area.model_dump(mode="json", by_alias=True) for area_id, area in areas_map.items()} elif table_type == TableModeType.LINK: links_map = self._link_manager.get_all_links_props(study) - excludes = set() if int(study.version) >= 820 else {"filter_synthesis", "filter_year_by_year"} + excludes = set() if int(study.version) >= STUDY_VERSION_8_2 else {"filter_synthesis", "filter_year_by_year"} data = { f"{area1_id} / {area2_id}": link.model_dump(mode="json", by_alias=True, exclude=excludes) for (area1_id, area2_id), link in links_map.items() @@ -196,7 +196,7 @@ def update_table_data( elif table_type == TableModeType.LINK: links_map = {tuple(key.split(" / ")): LinkOutput(**values) for key, values in data.items()} updated_map = self._link_manager.update_links_props(study, links_map) # type: ignore - excludes = set() if int(study.version) >= 820 else {"filter_synthesis", "filter_year_by_year"} + excludes = set() if int(study.version) >= STUDY_VERSION_8_2 else {"filter_synthesis", "filter_year_by_year"} data = { f"{area1_id} / {area2_id}": link.model_dump(by_alias=True, exclude=excludes) for (area1_id, area2_id), link in updated_map.items() diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/links.py b/antarest/study/storage/rawstudy/model/filesystem/config/links.py index f782cfb406..726fe72e93 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/links.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/links.py @@ -107,7 +107,7 @@ class FilterOption(EnumIgnoreCase): def validate_filters( filter_value: t.Union[t.List[FilterOption], str], enum_cls: t.Type[FilterOption] ) -> t.List[FilterOption]: - if filter_value is not None and isinstance(filter_value, str): + if isinstance(filter_value, str): filter_accepted_values = [e for e in enum_cls] options = filter_value.replace(" ", "").split(",") @@ -226,18 +226,3 @@ def _validate_color_rgb(cls, v: t.Any) -> str: @model_validator(mode="before") def _validate_colors(cls, values: t.MutableMapping[str, t.Any]) -> t.Mapping[str, t.Any]: return validate_colors(values) - - # noinspection SpellCheckingInspection - def to_ini(self, version: int) -> t.Dict[str, t.Any]: - """ - Convert the object to a dictionary for writing to a configuration file. - """ - excludes = set() if version >= 820 else {"filter_synthesis", "filter_year_by_year"} - obj = self.model_dump(mode="json", exclude_none=True, by_alias=True, exclude=excludes) - color_rgb = obj.pop("colorRgb", "#707070") - return { - "colorr": int(color_rgb[1:3], 16), - "colorg": int(color_rgb[3:5], 16), - "colorb": int(color_rgb[5:7], 16), - **obj, - } diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index bd93a8252f..dd62fc349c 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -141,8 +141,12 @@ def _apply(self, study_data: FileStudy, listener: Optional[ICommandListener] = N if not output.status: return output + to_exclude = {"area1", "area2"} + if version < STUDY_VERSION_8_2: + to_exclude.update("filter-synthesis", "filter-year-by-year") + validated_properties = LinkInternal.model_validate(self.parameters).model_dump( - by_alias=True, exclude={"area1", "area2"} + by_alias=True, exclude=to_exclude ) area_from = data["area_from"] diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 36131bc69d..9459426728 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -116,7 +116,10 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area1_id}) assert res.status_code == 422, res.json() - expected = {"description": "Cannot create link on same area: area 1", "exception": "LinkValidationError"} + expected = { + "description": "Cannot create a link that goes from and to the same single area: area 1", + "exception": "LinkValidationError", + } assert expected == res.json() # Test create link with wrong value for enum diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 582c6067c1..456be82a5b 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -103,7 +103,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = RawStudy( id=study_id, - version="-1", + version="820", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -136,7 +136,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): } area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) - study.version = 820 + link_manager.create_link( study, LinkDTO( @@ -145,7 +145,6 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ), ) assert empty_study.config.areas["test"].links.get("test2") is not None - study.version = -1 link_manager.delete_link(study, "test", "test2") assert empty_study.config.areas["test"].links.get("test2") is None @@ -158,7 +157,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = VariantStudy( id=variant_id, - version="-1", + version="820", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -227,7 +226,6 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ) area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) - study.version = 880 link_manager.create_link( study, LinkDTO( @@ -265,6 +263,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ], RequestParameters(DEFAULT_ADMIN_USER), ) + study.version = 810 link_manager.create_link( study, From f93ad5f429bbab386213f3f35b4d7f77bd90cf22 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 14:26:12 +0100 Subject: [PATCH 084/103] refactor(link): update DTO format --- antarest/core/exceptions.py | 5 + antarest/core/utils/string.py | 4 + antarest/study/business/link_management.py | 59 +++--- antarest/study/business/model/__init__.py | 0 antarest/study/business/model/link_model.py | 118 +++++++++++ .../study/business/table_mode_management.py | 6 +- antarest/study/service.py | 20 +- .../rawstudy/model/filesystem/config/links.py | 76 ++++++- .../variantstudy/model/command/create_link.py | 93 ++------- antarest/study/web/study_data_blueprint.py | 15 +- .../study_data_blueprint/test_link.py | 179 +++++++++++++++++ .../study_data_blueprint/test_table_mode.py | 6 + tests/integration/test_integration.py | 16 +- .../storage/business/test_arealink_manager.py | 186 +++++++++++++++++- .../model/command/test_create_link.py | 80 ++++---- 15 files changed, 671 insertions(+), 192 deletions(-) create mode 100644 antarest/study/business/model/__init__.py create mode 100644 antarest/study/business/model/link_model.py create mode 100644 tests/integration/study_data_blueprint/test_link.py diff --git a/antarest/core/exceptions.py b/antarest/core/exceptions.py index f0818d559c..5d574421a6 100644 --- a/antarest/core/exceptions.py +++ b/antarest/core/exceptions.py @@ -295,6 +295,11 @@ def __init__(self, message: str) -> None: super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) +class LinkValidationError(HTTPException): + def __init__(self, message: str) -> None: + super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) + + class VariantStudyParentNotValid(HTTPException): def __init__(self, message: str) -> None: super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) diff --git a/antarest/core/utils/string.py b/antarest/core/utils/string.py index 35c5e75541..ab45405753 100644 --- a/antarest/core/utils/string.py +++ b/antarest/core/utils/string.py @@ -18,3 +18,7 @@ def to_pascal_case(value: str) -> str: def to_camel_case(value: str) -> str: v = to_pascal_case(value) return v[0].lower() + v[1:] if len(v) > 0 else "" + + +def to_kebab_case(string: str) -> str: + return string.replace("_", "-") diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 54831ad8ac..cca12fcc2c 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -12,10 +12,12 @@ import typing as t -from antarest.core.exceptions import ConfigFileNotFound +from antares.study.version import StudyVersion + +from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError from antarest.core.model import JSON -from antarest.core.serialization import AntaresBaseModel from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model +from antarest.study.business.model.link_model import LinkDTO, LinkInternal from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties @@ -27,18 +29,6 @@ _ALL_LINKS_PATH = "input/links" -class LinkUIDTO(AntaresBaseModel): - color: str - width: float - style: str - - -class LinkInfoDTO(AntaresBaseModel): - area1: str - area2: str - ui: t.Optional[LinkUIDTO] = None - - @all_optional_model @camel_case_model class LinkOutput(LinkProperties): @@ -51,38 +41,39 @@ class LinkManager: def __init__(self, storage_service: StudyStorageService) -> None: self.storage_service = storage_service - def get_all_links(self, study: RawStudy, with_ui: bool = False) -> t.List[LinkInfoDTO]: + def get_all_links(self, study: RawStudy) -> t.List[LinkDTO]: file_study = self.storage_service.get_storage(study).get_raw(study) - result = [] + result: t.List[LinkDTO] = [] + for area_id, area in file_study.config.areas.items(): - links_config: t.Optional[t.Dict[str, t.Any]] = None - if with_ui: - links_config = file_study.tree.get(["input", "links", area_id, "properties"]) + links_config = file_study.tree.get(["input", "links", area_id, "properties"]) + for link in area.links: - ui_info: t.Optional[LinkUIDTO] = None - if with_ui and links_config and link in links_config: - ui_info = LinkUIDTO( - color=f"{links_config[link].get('colorr', '163')},{links_config[link].get('colorg', '163')},{links_config[link].get('colorb', '163')}", - width=links_config[link].get("link-width", 1), - style=links_config[link].get("link-style", "plain"), - ) - result.append(LinkInfoDTO(area1=area_id, area2=link, ui=ui_info)) + link_tree_config: t.Dict[str, t.Any] = links_config[link] + link_tree_config.update({"area1": area_id, "area2": link}) + + link_internal = LinkInternal.model_validate(link_tree_config) + + result.append(link_internal.to_dto()) return result - def create_link(self, study: RawStudy, link_creation_info: LinkInfoDTO) -> LinkInfoDTO: + def create_link(self, study: RawStudy, link_creation_dto: LinkDTO) -> LinkDTO: + link = link_creation_dto.to_internal(StudyVersion.parse(study.version)) + storage_service = self.storage_service.get_storage(study) file_study = storage_service.get_raw(study) + command = CreateLink( - area1=link_creation_info.area1, - area2=link_creation_info.area2, + area1=link.area1, + area2=link.area2, + parameters=link.model_dump(exclude_none=True), command_context=self.storage_service.variant_study_service.command_factory.command_context, ) + execute_or_add_commands(study, file_study, [command], self.storage_service) - return LinkInfoDTO( - area1=link_creation_info.area1, - area2=link_creation_info.area2, - ) + + return link_creation_dto def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) diff --git a/antarest/study/business/model/__init__.py b/antarest/study/business/model/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py new file mode 100644 index 0000000000..e68cfc3898 --- /dev/null +++ b/antarest/study/business/model/link_model.py @@ -0,0 +1,118 @@ +import typing as t + +from antares.study.version import StudyVersion +from pydantic import ConfigDict, Field, model_validator + +from antarest.core.exceptions import LinkValidationError +from antarest.core.serialization import AntaresBaseModel +from antarest.core.utils.string import to_camel_case, to_kebab_case +from antarest.study.model import STUDY_VERSION_8_2 +from antarest.study.storage.rawstudy.model.filesystem.config.links import ( + AssetType, + FilterOption, + LinkStyle, + TransmissionCapacity, + comma_separated_enum_list, +) + +DEFAULT_COLOR = 112 +FILTER_VALUES: t.List[FilterOption] = [ + FilterOption.HOURLY, + FilterOption.DAILY, + FilterOption.WEEKLY, + FilterOption.MONTHLY, + FilterOption.ANNUAL, +] + + +class Area(AntaresBaseModel): + area1: str + area2: str + + @model_validator(mode="after") + def validate_areas(self) -> t.Self: + if self.area1 == self.area2: + raise LinkValidationError(f"Cannot create link on same area: {self.area1}") + return self + + +class LinkDTO(Area): + model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") + + hurdles_cost: bool = False + loop_flow: bool = False + use_phase_shifter: bool = False + transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED + asset_type: AssetType = AssetType.AC + display_comments: bool = True + colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + link_width: float = 1 + link_style: LinkStyle = LinkStyle.PLAIN + + filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES + filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES + + def to_internal(self, version: StudyVersion) -> "LinkInternal": + if version < STUDY_VERSION_8_2 and ( + "filter_synthesis" in self.model_fields_set or "filter_year_by_year" in self.model_fields_set + ): + raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") + + return LinkInternal( + area1=self.area1, + area2=self.area2, + hurdles_cost=self.hurdles_cost, + loop_flow=self.loop_flow, + use_phase_shifter=self.use_phase_shifter, + transmission_capacities=self.transmission_capacities, + asset_type=self.asset_type, + display_comments=self.display_comments, + colorr=self.colorr, + colorb=self.colorb, + colorg=self.colorg, + link_width=self.link_width, + link_style=self.link_style, + filter_synthesis=self.filter_synthesis if version >= STUDY_VERSION_8_2 else None, + filter_year_by_year=self.filter_year_by_year if version >= STUDY_VERSION_8_2 else None, + ) + + +class LinkInternal(AntaresBaseModel): + model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True, extra="forbid") + + area1: str = "area1" + area2: str = "area2" + hurdles_cost: bool = False + loop_flow: bool = False + use_phase_shifter: bool = False + transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED + asset_type: AssetType = AssetType.AC + display_comments: bool = True + colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + link_width: float = 1 + link_style: LinkStyle = LinkStyle.PLAIN + filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES + filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES + + def to_dto(self) -> LinkDTO: + return LinkDTO( + area1=self.area1, + area2=self.area2, + hurdles_cost=self.hurdles_cost, + loop_flow=self.loop_flow, + use_phase_shifter=self.use_phase_shifter, + transmission_capacities=self.transmission_capacities, + asset_type=self.asset_type, + display_comments=self.display_comments, + colorr=self.colorr, + colorb=self.colorb, + colorg=self.colorg, + link_width=self.link_width, + link_style=self.link_style, + filter_synthesis=self.filter_synthesis, + filter_year_by_year=self.filter_year_by_year, + ) diff --git a/antarest/study/business/table_mode_management.py b/antarest/study/business/table_mode_management.py index b108c57cb8..9d62d17869 100644 --- a/antarest/study/business/table_mode_management.py +++ b/antarest/study/business/table_mode_management.py @@ -98,8 +98,9 @@ def _get_table_data_unsafe(self, study: RawStudy, table_type: TableModeType) -> data = {area_id: area.model_dump(mode="json", by_alias=True) for area_id, area in areas_map.items()} elif table_type == TableModeType.LINK: links_map = self._link_manager.get_all_links_props(study) + excludes = set() if int(study.version) >= 820 else {"filter_synthesis", "filter_year_by_year"} data = { - f"{area1_id} / {area2_id}": link.model_dump(mode="json", by_alias=True) + f"{area1_id} / {area2_id}": link.model_dump(mode="json", by_alias=True, exclude=excludes) for (area1_id, area2_id), link in links_map.items() } elif table_type == TableModeType.THERMAL: @@ -195,8 +196,9 @@ def update_table_data( elif table_type == TableModeType.LINK: links_map = {tuple(key.split(" / ")): LinkOutput(**values) for key, values in data.items()} updated_map = self._link_manager.update_links_props(study, links_map) # type: ignore + excludes = set() if int(study.version) >= 820 else {"filter_synthesis", "filter_year_by_year"} data = { - f"{area1_id} / {area2_id}": link.model_dump(by_alias=True) + f"{area1_id} / {area2_id}": link.model_dump(by_alias=True, exclude=excludes) for (area1_id, area2_id), link in updated_map.items() } return data diff --git a/antarest/study/service.py b/antarest/study/service.py index 19067b6f9a..3170e311d0 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -86,8 +86,9 @@ from antarest.study.business.correlation_management import CorrelationManager from antarest.study.business.district_manager import DistrictManager from antarest.study.business.general_management import GeneralManager -from antarest.study.business.link_management import LinkInfoDTO, LinkManager +from antarest.study.business.link_management import LinkManager from antarest.study.business.matrix_management import MatrixManager, MatrixManagerError +from antarest.study.business.model.link_model import LinkDTO from antarest.study.business.optimization_management import OptimizationManager from antarest.study.business.playlist_management import PlaylistManager from antarest.study.business.scenario_builder_management import ScenarioBuilderManager @@ -358,7 +359,7 @@ def __init__( self.task_service = task_service self.areas = AreaManager(self.storage_service, self.repository) self.district_manager = DistrictManager(self.storage_service) - self.links = LinkManager(self.storage_service) + self.links_manager = LinkManager(self.storage_service) self.config_manager = ConfigManager(self.storage_service) self.general_manager = GeneralManager(self.storage_service) self.thematic_trimming_manager = ThematicTrimmingManager(self.storage_service) @@ -380,7 +381,7 @@ def __init__( self.correlation_manager = CorrelationManager(self.storage_service) self.table_mode_manager = TableModeManager( self.areas, - self.links, + self.links_manager, self.thermal_manager, self.renewable_manager, self.st_storage_manager, @@ -1867,12 +1868,11 @@ def get_all_areas( def get_all_links( self, uuid: str, - with_ui: bool, params: RequestParameters, - ) -> t.List[LinkInfoDTO]: + ) -> t.List[LinkDTO]: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.READ) - return self.links.get_all_links(study, with_ui) + return self.links_manager.get_all_links(study) def create_area( self, @@ -1896,13 +1896,13 @@ def create_area( def create_link( self, uuid: str, - link_creation_dto: LinkInfoDTO, + link_creation_dto: LinkDTO, params: RequestParameters, - ) -> LinkInfoDTO: + ) -> LinkDTO: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.WRITE) self._assert_study_unarchived(study) - new_link = self.links.create_link(study, link_creation_dto) + new_link = self.links_manager.create_link(study, link_creation_dto) self.event_bus.push( Event( type=EventType.STUDY_DATA_EDITED, @@ -2018,7 +2018,7 @@ def delete_link( if referencing_binding_constraints: binding_ids = [bc.id for bc in referencing_binding_constraints] raise ReferencedObjectDeletionNotAllowed(link_id, binding_ids, object_type="Link") - self.links.delete_link(study, area_from, area_to) + self.links_manager.delete_link(study, area_from, area_to) self.event_bus.push( Event( type=EventType.STUDY_DATA_EDITED, diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/links.py b/antarest/study/storage/rawstudy/model/filesystem/config/links.py index 88059ef50e..f782cfb406 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/links.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/links.py @@ -16,8 +16,9 @@ import typing as t -from pydantic import Field, field_validator, model_validator +from pydantic import BeforeValidator, Field, PlainSerializer, field_validator, model_validator +from antarest.core.exceptions import LinkValidationError from antarest.study.business.enum_ignore_case import EnumIgnoreCase from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import ( validate_color_rgb, @@ -66,6 +67,74 @@ class TransmissionCapacity(EnumIgnoreCase): ENABLED = "enabled" +class LinkStyle(EnumIgnoreCase): + """ + Enum representing the style of a link in a network visualization. + + Attributes: + DOT: Represents a dotted line style. + PLAIN: Represents a solid line style. + DASH: Represents a dashed line style. + DOT_DASH: Represents a line style with alternating dots and dashes. + """ + + DOT = "dot" + PLAIN = "plain" + DASH = "dash" + DOT_DASH = "dotdash" + OTHER = "other" + + +class FilterOption(EnumIgnoreCase): + """ + Enum representing the time filter options for data visualization or analysis in Antares Web. + + Attributes: + HOURLY: Represents filtering data by the hour. + DAILY: Represents filtering data by the day. + WEEKLY: Represents filtering data by the week. + MONTHLY: Represents filtering data by the month. + ANNUAL: Represents filtering data by the year. + """ + + HOURLY = "hourly" + DAILY = "daily" + WEEKLY = "weekly" + MONTHLY = "monthly" + ANNUAL = "annual" + + +def validate_filters( + filter_value: t.Union[t.List[FilterOption], str], enum_cls: t.Type[FilterOption] +) -> t.List[FilterOption]: + if filter_value is not None and isinstance(filter_value, str): + filter_accepted_values = [e for e in enum_cls] + + options = filter_value.replace(" ", "").split(",") + + invalid_options = [opt for opt in options if opt not in filter_accepted_values] + if invalid_options: + raise LinkValidationError( + f"Invalid value(s) in filters: {', '.join(invalid_options)}. " + f"Allowed values are: {', '.join(filter_accepted_values)}." + ) + + return [enum_cls(opt) for opt in options] + + return filter_value + + +def join_with_comma(values: t.List[FilterOption]) -> str: + return ", ".join(value.name.lower() for value in values) + + +comma_separated_enum_list = t.Annotated[ + t.List[FilterOption], + BeforeValidator(lambda x: validate_filters(x, FilterOption)), + PlainSerializer(lambda x: join_with_comma(x)), +] + + class LinkProperties(IniProperties): """ Configuration read from a section in the `input/links//properties.ini` file. @@ -159,11 +228,12 @@ def _validate_colors(cls, values: t.MutableMapping[str, t.Any]) -> t.Mapping[str return validate_colors(values) # noinspection SpellCheckingInspection - def to_config(self) -> t.Dict[str, t.Any]: + def to_ini(self, version: int) -> t.Dict[str, t.Any]: """ Convert the object to a dictionary for writing to a configuration file. """ - obj = dict(super().to_config()) + excludes = set() if version >= 820 else {"filter_synthesis", "filter_year_by_year"} + obj = self.model_dump(mode="json", exclude_none=True, by_alias=True, exclude=excludes) color_rgb = obj.pop("colorRgb", "#707070") return { "colorr": int(color_rgb[1:3], 16), diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 7954981d99..bd93a8252f 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -9,42 +9,25 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - from typing import Any, Dict, List, Optional, Tuple, Union, cast from pydantic import ValidationInfo, field_validator, model_validator -from antarest.core.model import JSON from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData +from antarest.study.business.model.link_model import LinkInternal from antarest.study.model import STUDY_VERSION_8_2 from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput, FilteringOptions from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand +from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix +from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig from antarest.study.storage.variantstudy.model.command_listener.command_listener import ICommandListener from antarest.study.storage.variantstudy.model.model import CommandDTO -class LinkProperties: - HURDLES_COST: bool = False - LOOP_FLOW: bool = False - USE_PHASE_SHIFTER: bool = False - DISPLAY_COMMENTS: bool = True - TRANSMISSION_CAPACITIES: str = "enabled" - ASSET_TYPE: str = "ac" - LINK_STYLE: str = "plain" - LINK_WIDTH: int = 1 - COLORR: int = 112 - COLORG: int = 112 - COLORB: int = 112 - - -class LinkAlreadyExistError(Exception): - pass - - class CreateLink(ICommand): """ Command used to create a link between two areas. @@ -98,51 +81,6 @@ def _create_link_in_config(self, area_from: str, area_to: str, study_data: FileS ], ) - @staticmethod - def generate_link_properties(parameters: JSON) -> JSON: - return { - "hurdles-cost": parameters.get( - "hurdles-cost", - LinkProperties.HURDLES_COST, - ), - "loop-flow": parameters.get("loop-flow", LinkProperties.LOOP_FLOW), - "use-phase-shifter": parameters.get( - "use-phase-shifter", - LinkProperties.USE_PHASE_SHIFTER, - ), - "transmission-capacities": parameters.get( - "transmission-capacities", - LinkProperties.TRANSMISSION_CAPACITIES, - ), - "asset-type": parameters.get( - "asset-type", - LinkProperties.ASSET_TYPE, - ), - "link-style": parameters.get( - "link-style", - LinkProperties.LINK_STYLE, - ), - "link-width": parameters.get( - "link-width", - LinkProperties.LINK_WIDTH, - ), - "colorr": parameters.get("colorr", LinkProperties.COLORR), - "colorg": parameters.get("colorg", LinkProperties.COLORG), - "colorb": parameters.get("colorb", LinkProperties.COLORB), - "display-comments": parameters.get( - "display-comments", - LinkProperties.DISPLAY_COMMENTS, - ), - "filter-synthesis": parameters.get( - "filter-synthesis", - FilteringOptions.FILTER_SYNTHESIS, - ), - "filter-year-by-year": parameters.get( - "filter-year-by-year", - FilteringOptions.FILTER_YEAR_BY_YEAR, - ), - } - def _apply_config(self, study_data: FileStudyTreeConfig) -> Tuple[CommandOutput, Dict[str, Any]]: if self.area1 not in study_data.areas: return ( @@ -161,15 +99,6 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> Tuple[CommandOutput, {}, ) - if self.area1 == self.area2: - return ( - CommandOutput( - status=False, - message="Cannot create link between the same node", - ), - {}, - ) - # Link parameters between two areas are stored in only one of the two # areas in the "input/links" tree. One area acts as source (`area_from`) # and the other as target (`area_to`). @@ -211,13 +140,16 @@ def _apply(self, study_data: FileStudy, listener: Optional[ICommandListener] = N output, data = self._apply_config(study_data.config) if not output.status: return output + + validated_properties = LinkInternal.model_validate(self.parameters).model_dump( + by_alias=True, exclude={"area1", "area2"} + ) + area_from = data["area_from"] area_to = data["area_to"] - self.parameters = self.parameters or {} - link_property = CreateLink.generate_link_properties(self.parameters) + study_data.tree.save(validated_properties, ["input", "links", area_from, "properties", area_to]) - study_data.tree.save(link_property, ["input", "links", area_from, "properties", area_to]) self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) @@ -298,13 +230,14 @@ def match(self, other: ICommand, equal: bool = False) -> bool: def _create_diff(self, other: "ICommand") -> List["ICommand"]: other = cast(CreateLink, other) - from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix - from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig commands: List[ICommand] = [] area_from, area_to = sorted([self.area1, self.area2]) if self.parameters != other.parameters: - link_property = CreateLink.generate_link_properties(other.parameters or {}) + properties = LinkInternal.model_validate(other.parameters or {}) + link_property = properties.model_dump( + mode="json", by_alias=True, exclude_none=True, exclude={"area1", "area2"} + ) commands.append( UpdateConfig( target=f"input/links/{area_from}/properties/{area_to}", diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index a6d39c7164..6d94a48c54 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -68,7 +68,7 @@ ) from antarest.study.business.district_manager import DistrictCreationDTO, DistrictInfoDTO, DistrictUpdateDTO from antarest.study.business.general_management import GeneralFormFields -from antarest.study.business.link_management import LinkInfoDTO +from antarest.study.business.model.link_model import LinkDTO from antarest.study.business.optimization_management import OptimizationFormFields from antarest.study.business.playlist_management import PlaylistColumns from antarest.study.business.scenario_builder_management import Rulesets, ScenarioType @@ -147,19 +147,18 @@ def get_areas( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Get all links", - response_model=t.List[LinkInfoDTO], + response_model=t.List[LinkDTO], ) def get_links( uuid: str, - with_ui: bool = False, current_user: JWTUser = Depends(auth.get_current_user), - ) -> t.Any: + ) -> t.List[LinkDTO]: logger.info( f"Fetching link list for study {uuid}", extra={"user": current_user.id}, ) params = RequestParameters(user=current_user) - areas_list = study_service.get_all_links(uuid, with_ui, params) + areas_list = study_service.get_all_links(uuid, params) return areas_list @bp.post( @@ -184,13 +183,13 @@ def create_area( "/studies/{uuid}/links", tags=[APITag.study_data], summary="Create a link", - response_model=LinkInfoDTO, + response_model=LinkDTO, ) def create_link( uuid: str, - link_creation_info: LinkInfoDTO, + link_creation_info: LinkDTO, current_user: JWTUser = Depends(auth.get_current_user), - ) -> t.Any: + ) -> LinkDTO: logger.info( f"Creating new link for study {uuid}", extra={"user": current_user.id}, diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py new file mode 100644 index 0000000000..e68bfc9d5d --- /dev/null +++ b/tests/integration/study_data_blueprint/test_link.py @@ -0,0 +1,179 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import pytest +from starlette.testclient import TestClient + +from antarest.study.storage.rawstudy.model.filesystem.config.links import TransmissionCapacity +from tests.integration.prepare_proxy import PreparerProxy + + +@pytest.mark.unit_test +class TestLink: + @pytest.mark.parametrize("study_type", ["raw", "variant"]) + def test_link_820(self, client: TestClient, user_access_token: str, study_type: str) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=820) + if study_type == "variant": + study_id = preparer.create_variant(study_id, name="Variant 1") + + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + area3_id = preparer.create_area(study_id, name="Area 3")["id"] + + # Test create link with default values + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) + + assert res.status_code == 200, res.json() + + expected = { + "area1": "area 1", + "area2": "area 2", + "assetType": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "displayComments": True, + "filterSynthesis": "hourly, daily, weekly, monthly, annual", + "filterYearByYear": "hourly, daily, weekly, monthly, annual", + "hurdlesCost": False, + "linkStyle": "plain", + "linkWidth": 1.0, + "loopFlow": False, + "transmissionCapacities": "enabled", + "usePhaseShifter": False, + } + assert expected == res.json() + res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") + res.raise_for_status() + + # Test create link with parameters + + parameters = { + "area1": "area 1", + "area2": "area 2", + "assetType": "ac", + "colorb": 160, + "colorg": 170, + "colorr": 180, + "displayComments": True, + "filterSynthesis": "hourly, daily, weekly, monthly, annual", + "filterYearByYear": "hourly, daily, weekly, monthly, annual", + "hurdlesCost": False, + "linkStyle": "plain", + "linkWidth": 1.0, + "loopFlow": False, + "transmissionCapacities": "enabled", + "usePhaseShifter": False, + } + res = client.post( + f"/v1/studies/{study_id}/links", + json=parameters, + ) + + assert res.status_code == 200, res.json() + + assert parameters == res.json() + res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") + res.raise_for_status() + + # Create two links, count them, then delete one + + res1 = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id}) + res2 = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area3_id}) + + assert res1.status_code == 200, res1.json() + assert res2.status_code == 200, res2.json() + + res = client.get(f"/v1/studies/{study_id}/links") + + assert res.status_code == 200, res.json() + assert 2 == len(res.json()) + + res = client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area3_id}") + res.raise_for_status() + + res = client.get(f"/v1/studies/{study_id}/links") + + assert res.status_code == 200, res.json() + assert 1 == len(res.json()) + client.delete(f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}") + res.raise_for_status() + + # Test create link with same area + + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area1_id}) + + assert res.status_code == 422, res.json() + expected = {"description": "Cannot create link on same area: area 1", "exception": "LinkValidationError"} + assert expected == res.json() + + # Test create link with wrong value for enum + + res = client.post( + f"/v1/studies/{study_id}/links", + json={"area1": area1_id, "area2": area2_id, "assetType": TransmissionCapacity.ENABLED}, + ) + assert res.status_code == 422, res.json() + expected = { + "body": {"area1": "area 1", "area2": "area 2", "assetType": "enabled"}, + "description": "Input should be 'ac', 'dc', 'gaz', 'virt' or 'other'", + "exception": "RequestValidationError", + } + assert expected == res.json() + + # Test create link with wrong color parameter + + res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "colorr": 260}) + + assert res.status_code == 422, res.json() + expected = { + "body": {"area1": "area 1", "area2": "area 2", "colorr": 260}, + "description": "Input should be less than 255", + "exception": "RequestValidationError", + } + assert expected == res.json() + + # Test create link with wrong filter parameter + + res = client.post( + f"/v1/studies/{study_id}/links", + json={"area1": area1_id, "area2": area2_id, "filterSynthesis": "centurial"}, + ) + + assert res.status_code == 422, res.json() + expected = { + "description": "Invalid value(s) in filters: centurial. Allowed values are: hourly, daily, weekly, monthly, annual.", + "exception": "LinkValidationError", + } + assert expected == res.json() + + def test_create_link_810(self, client: TestClient, user_access_token: str) -> None: + client.headers = {"Authorization": f"Bearer {user_access_token}"} # type: ignore + + preparer = PreparerProxy(client, user_access_token) + study_id = preparer.create_study("foo", version=810) + area1_id = preparer.create_area(study_id, name="Area 1")["id"] + area2_id = preparer.create_area(study_id, name="Area 2")["id"] + + res = client.post( + f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "filterSynthesis": "hourly"} + ) + + assert res.status_code == 422, res.json() + expected = { + "description": "Cannot specify a filter value for study's version earlier than v8.2", + "exception": "LinkValidationError", + } + assert expected == res.json() diff --git a/tests/integration/study_data_blueprint/test_table_mode.py b/tests/integration/study_data_blueprint/test_table_mode.py index 98d7f03393..a7b1878020 100644 --- a/tests/integration/study_data_blueprint/test_table_mode.py +++ b/tests/integration/study_data_blueprint/test_table_mode.py @@ -293,6 +293,12 @@ def test_lifecycle__nominal( "usePhaseShifter": False, }, } + # removes filter fields for study version prior to v8.2 + if study_version < 820: + for key in expected_links: + del expected_links[key]["filterSynthesis"] + del expected_links[key]["filterYearByYear"] + # asserts actual equals expected without the non-updated link. actual = res.json() expected_result = copy.deepcopy(expected_links) diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index a9fa59088f..37f34f5273 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -593,12 +593,24 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: }, ) res.raise_for_status() - res_links = client.get(f"/v1/studies/{study_id}/links?with_ui=true") + res_links = client.get(f"/v1/studies/{study_id}/links") assert res_links.json() == [ { "area1": "area 1", "area2": "area 2", - "ui": {"color": "112,112,112", "style": "plain", "width": 1.0}, + "assetType": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "displayComments": True, + "filterSynthesis": "hourly, daily, weekly, monthly, annual", + "filterYearByYear": "hourly, daily, weekly, monthly, annual", + "hurdlesCost": False, + "linkStyle": "plain", + "linkWidth": 1.0, + "loopFlow": False, + "transmissionCapacities": "enabled", + "usePhaseShifter": False, } ] diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index cb1d29c971..582c6067c1 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -24,11 +24,12 @@ from antarest.matrixstore.repository import MatrixContentRepository from antarest.matrixstore.service import SimpleMatrixService from antarest.study.business.area_management import AreaCreationDTO, AreaManager, AreaType, UpdateAreaUi -from antarest.study.business.link_management import LinkInfoDTO, LinkManager +from antarest.study.business.link_management import LinkDTO, LinkManager from antarest.study.model import Patch, PatchArea, PatchCluster, RawStudy, StudyAdditionalData from antarest.study.repository import StudyMetadataRepository from antarest.study.storage.patch_service import PatchService from antarest.study.storage.rawstudy.model.filesystem.config.files import build +from antarest.study.storage.rawstudy.model.filesystem.config.links import AssetType, LinkStyle, TransmissionCapacity from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, DistrictSet, FileStudyTreeConfig, Link from antarest.study.storage.rawstudy.model.filesystem.config.thermal import ThermalConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy @@ -37,7 +38,7 @@ from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.business.matrix_constants_generator import GeneratorMatrixConstants from antarest.study.storage.variantstudy.command_factory import CommandFactory -from antarest.study.storage.variantstudy.model.command.common import CommandName +from antarest.study.storage.variantstudy.model.command.common import CommandName, FilteringOptions from antarest.study.storage.variantstudy.model.dbmodel import VariantStudy from antarest.study.storage.variantstudy.model.model import CommandDTO from antarest.study.storage.variantstudy.variant_study_service import VariantStudyService @@ -102,6 +103,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = RawStudy( id=study_id, + version="-1", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -134,8 +136,16 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): } area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) - link_manager.create_link(study, LinkInfoDTO(area1="test", area2="test2")) + study.version = 820 + link_manager.create_link( + study, + LinkDTO( + area1="test", + area2="test2", + ), + ) assert empty_study.config.areas["test"].links.get("test2") is not None + study.version = -1 link_manager.delete_link(study, "test", "test2") assert empty_study.config.areas["test"].links.get("test2") is None @@ -148,6 +158,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = VariantStudy( id=variant_id, + version="-1", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -216,7 +227,52 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ) area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) - link_manager.create_link(study, LinkInfoDTO(area1="test", area2="test2")) + study.version = 880 + link_manager.create_link( + study, + LinkDTO( + area1="test", + area2="test2", + ), + ) + variant_study_service.append_commands.assert_called_with( + variant_id, + [ + CommandDTO( + action=CommandName.CREATE_LINK.value, + args={ + "area1": "test", + "area2": "test2", + "parameters": { + "area1": "test", + "area2": "test2", + "hurdles_cost": False, + "loop_flow": False, + "use_phase_shifter": False, + "transmission_capacities": TransmissionCapacity.ENABLED, + "asset_type": AssetType.AC, + "display_comments": True, + "colorr": 112, + "colorg": 112, + "colorb": 112, + "link_width": 1.0, + "link_style": LinkStyle.PLAIN, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + }, + }, + ), + ], + RequestParameters(DEFAULT_ADMIN_USER), + ) + study.version = 810 + link_manager.create_link( + study, + LinkDTO( + area1="test", + area2="test2", + ), + ) variant_study_service.append_commands.assert_called_with( variant_id, [ @@ -225,7 +281,21 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): args={ "area1": "test", "area2": "test2", - "parameters": None, + "parameters": { + "area1": "test", + "area2": "test2", + "hurdles_cost": False, + "loop_flow": False, + "use_phase_shifter": False, + "transmission_capacities": TransmissionCapacity.ENABLED, + "asset_type": AssetType.AC, + "display_comments": True, + "colorr": 112, + "colorg": 112, + "colorb": 112, + "link_width": 1.0, + "link_style": LinkStyle.PLAIN, + }, }, ), ], @@ -260,7 +330,7 @@ def test_get_all_area(): ) link_manager = LinkManager(storage_service=StudyStorageService(raw_study_service, Mock())) - study = RawStudy() + study = RawStudy(version="900") config = FileStudyTreeConfig( study_path=Path("somepath"), path=Path("somepath"), @@ -416,12 +486,108 @@ def test_get_all_area(): all_areas = area_manager.get_all_areas(study) assert expected_all == [area.model_dump() for area in all_areas] + file_tree_mock.get.side_effect = [ + { + "a2": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": False, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, + }, + "a3": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": False, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, + }, + }, + { + "a3": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": False, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, + } + }, + { + "a3": { + "hurdles-cost": False, + "loop-flow": False, + "use-phase-shifter": False, + "transmission-capacities": TransmissionCapacity.ENABLED, + "asset-type": AssetType.AC, + "display-comments": False, + "filter-synthesis": FilteringOptions.FILTER_SYNTHESIS, + "filter-year-by-year": FilteringOptions.FILTER_YEAR_BY_YEAR, + } + }, + ] links = link_manager.get_all_links(study) assert [ - {"area1": "a1", "area2": "a2", "ui": None}, - {"area1": "a1", "area2": "a3", "ui": None}, - {"area1": "a2", "area2": "a3", "ui": None}, - ] == [link.model_dump() for link in links] + { + "area1": "a1", + "area2": "a2", + "asset_type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "display_comments": False, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": False, + "link_style": "plain", + "link_width": 1.0, + "loop_flow": False, + "transmission_capacities": "enabled", + "use_phase_shifter": False, + }, + { + "area1": "a1", + "area2": "a3", + "asset_type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "display_comments": False, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": False, + "link_style": "plain", + "link_width": 1.0, + "loop_flow": False, + "transmission_capacities": "enabled", + "use_phase_shifter": False, + }, + { + "area1": "a2", + "area2": "a3", + "asset_type": "ac", + "colorb": 112, + "colorg": 112, + "colorr": 112, + "display_comments": False, + "filter_synthesis": "hourly, daily, weekly, monthly, annual", + "filter_year_by_year": "hourly, daily, weekly, monthly, annual", + "hurdles_cost": False, + "link_style": "plain", + "link_width": 1.0, + "loop_flow": False, + "transmission_capacities": "enabled", + "use_phase_shifter": False, + }, + ] == [link.model_dump(mode="json") for link in links] def test_update_area(): diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index 9d847f0f24..33a536b514 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -10,19 +10,17 @@ # # This file is part of the Antares project. -import configparser - import numpy as np import pytest from pydantic import ValidationError +from antarest.study.business.link_management import LinkInternal from antarest.study.storage.rawstudy.ini_reader import IniReader from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter -from antarest.study.storage.variantstudy.model.command.common import FilteringOptions from antarest.study.storage.variantstudy.model.command.create_area import CreateArea -from antarest.study.storage.variantstudy.model.command.create_link import CreateLink, LinkProperties +from antarest.study.storage.variantstudy.model.command.create_link import CreateLink from antarest.study.storage.variantstudy.model.command.icommand import ICommand from antarest.study.storage.variantstudy.model.command.remove_area import RemoveArea from antarest.study.storage.variantstudy.model.command.remove_link import RemoveLink @@ -34,8 +32,6 @@ class TestCreateLink: def test_validation(self, empty_study: FileStudy, command_context: CommandContext): area1 = "Area1" - area1_id = transform_name_to_id(area1) - area2 = "Area2" CreateArea.model_validate( @@ -54,8 +50,8 @@ def test_validation(self, empty_study: FileStudy, command_context: CommandContex with pytest.raises(ValidationError): CreateLink( - area1=area1_id, - area2=area1_id, + area1=area1, + area2=area1, parameters={}, command_context=command_context, series=[[0]], @@ -110,19 +106,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): link = IniReader() link_data = link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert link_data[area2_id]["hurdles-cost"] == LinkProperties.HURDLES_COST - assert link_data[area2_id]["loop-flow"] == LinkProperties.LOOP_FLOW - assert link_data[area2_id]["use-phase-shifter"] == LinkProperties.USE_PHASE_SHIFTER - assert str(link_data[area2_id]["transmission-capacities"]) == LinkProperties.TRANSMISSION_CAPACITIES - assert str(link_data[area2_id]["asset-type"]) == LinkProperties.ASSET_TYPE - assert str(link_data[area2_id]["link-style"]) == LinkProperties.LINK_STYLE - assert int(link_data[area2_id]["link-width"]) == LinkProperties.LINK_WIDTH - assert int(link_data[area2_id]["colorr"]) == LinkProperties.COLORR - assert int(link_data[area2_id]["colorg"]) == LinkProperties.COLORG - assert int(link_data[area2_id]["colorb"]) == LinkProperties.COLORB - assert link_data[area2_id]["display-comments"] == LinkProperties.DISPLAY_COMMENTS - assert str(link_data[area2_id]["filter-synthesis"]) == FilteringOptions.FILTER_SYNTHESIS - assert str(link_data[area2_id]["filter-year-by-year"]) == FilteringOptions.FILTER_YEAR_BY_YEAR + assert link_data[area2_id]["hurdles-cost"] is False + assert link_data[area2_id]["loop-flow"] is False + assert link_data[area2_id]["use-phase-shifter"] is False + assert link_data[area2_id]["transmission-capacities"] == "enabled" + assert link_data[area2_id]["asset-type"] == "ac" + assert link_data[area2_id]["link-style"] == "plain" + assert int(link_data[area2_id]["link-width"]) == 1 + assert int(link_data[area2_id]["colorr"]) == 112 + assert int(link_data[area2_id]["colorg"]) == 112 + assert int(link_data[area2_id]["colorb"]) == 112 + assert link_data[area2_id]["display-comments"] is True empty_study.config.version = 820 create_link_command: ICommand = CreateLink( @@ -157,17 +151,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): assert not output.status parameters = { - "hurdles-cost": "true", - "loop-flow": "true", - "use-phase-shifter": "true", - "transmission-capacities": "disabled", + "hurdles-cost": True, + "loop-flow": True, + "use-phase-shifter": True, + "transmission-capacities": "ignore", "asset-type": "dc", "link-style": "other", "link-width": 12, "colorr": 120, "colorg": 120, "colorb": 120, - "display-comments": "true", + "display-comments": True, "filter-synthesis": "hourly", "filter-year-by-year": "hourly", } @@ -189,21 +183,19 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): assert (study_path / "input" / "links" / area1_id / f"{area3_id}.txt.link").exists() - link = configparser.ConfigParser() - link.read(study_path / "input" / "links" / area1_id / "properties.ini") - assert str(link[area3_id]["hurdles-cost"]) == parameters["hurdles-cost"] - assert str(link[area3_id]["loop-flow"]) == parameters["loop-flow"] - assert str(link[area3_id]["use-phase-shifter"]) == parameters["use-phase-shifter"] - assert str(link[area3_id]["transmission-capacities"]) == parameters["transmission-capacities"] - assert str(link[area3_id]["asset-type"]) == parameters["asset-type"] - assert str(link[area3_id]["link-style"]) == parameters["link-style"] - assert int(link[area3_id]["link-width"]) == parameters["link-width"] - assert int(link[area3_id]["colorr"]) == parameters["colorr"] - assert int(link[area3_id]["colorg"]) == parameters["colorg"] - assert int(link[area3_id]["colorb"]) == parameters["colorb"] - assert str(link[area3_id]["display-comments"]) == parameters["display-comments"] - assert str(link[area3_id]["filter-synthesis"]) == parameters["filter-synthesis"] - assert str(link[area3_id]["filter-year-by-year"]) == parameters["filter-year-by-year"] + link = IniReader() + link_data = link.read(study_path / "input" / "links" / area1_id / "properties.ini") + assert link_data[area3_id]["hurdles-cost"] == parameters["hurdles-cost"] + assert link_data[area3_id]["loop-flow"] == parameters["loop-flow"] + assert link_data[area3_id]["use-phase-shifter"] == parameters["use-phase-shifter"] + assert link_data[area3_id]["transmission-capacities"] == parameters["transmission-capacities"] + assert link_data[area3_id]["asset-type"] == parameters["asset-type"] + assert link_data[area3_id]["link-style"] == parameters["link-style"] + assert int(link_data[area3_id]["link-width"]) == parameters["link-width"] + assert int(link_data[area3_id]["colorr"]) == parameters["colorr"] + assert int(link_data[area3_id]["colorg"]) == parameters["colorg"] + assert int(link_data[area3_id]["colorb"]) == parameters["colorb"] + assert link_data[area3_id]["display-comments"] == parameters["display-comments"] output = create_link_command.apply( study_data=empty_study, @@ -258,7 +250,7 @@ def test_create_diff(command_context: CommandContext): other_match = CreateLink( area1="foo", area2="bar", - parameters={"hurdles-cost": "true"}, + parameters={"hurdles_cost": "true"}, series=series_b, command_context=command_context, ) @@ -266,7 +258,9 @@ def test_create_diff(command_context: CommandContext): assert base.create_diff(other_match) == [ UpdateConfig( target="input/links/bar/properties/foo", - data=CreateLink.generate_link_properties({"hurdles-cost": "true"}), + data=LinkInternal.model_validate({"area1": "bar", "area2": "foo", "hurdles_cost": "true"}).model_dump( + by_alias=True, exclude_none=True, exclude={"area1", "area2"} + ), command_context=command_context, ), ReplaceMatrix( From f0717ee724389a945aa19eb2e4a4d8df91cb7358 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:32:21 +0100 Subject: [PATCH 085/103] refactor(ui-api): update links endpoints with new DTO format --- webapp/package-lock.json | 8 +++ webapp/package.json | 6 ++- webapp/src/common/types.ts | 15 ------ .../Xpansion/Candidates/CandidateForm.tsx | 4 +- .../Candidates/CreateCandidateDialog.tsx | 4 +- .../explore/Xpansion/Candidates/index.tsx | 4 +- webapp/src/redux/ducks/studyMaps.ts | 33 ++++++++---- .../studies/config/thematicTrimming/index.ts | 4 +- .../services/api/studies/links/constants.ts | 35 ++++++++++++ .../src/services/api/studies/links/index.ts | 50 +++++++++++++++++ .../src/services/api/studies/links/types.ts | 54 +++++++++++++++++++ webapp/src/services/api/studies/raw/index.ts | 10 ++-- .../services/api/studies/tableMode/index.ts | 6 +-- webapp/src/services/api/studies/timeseries.ts | 2 +- webapp/src/services/api/studydata.ts | 43 +-------------- webapp/src/services/api/tasks/index.ts | 8 +-- 16 files changed, 195 insertions(+), 91 deletions(-) create mode 100644 webapp/src/services/api/studies/links/constants.ts create mode 100644 webapp/src/services/api/studies/links/index.ts create mode 100644 webapp/src/services/api/studies/links/types.ts diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 809c088d3d..b4f6734d44 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -62,6 +62,7 @@ "redux": "4.2.1", "redux-thunk": "2.4.2", "swagger-ui-react": "5.17.14", + "tinycolor2": "1.6.0", "ts-toolbelt": "9.6.0", "use-undo": "1.1.1", "uuid": "10.0.0", @@ -94,6 +95,7 @@ "@types/react-window": "1.8.8", "@types/swagger-ui-react": "4.18.3", "@types/testing-library__jest-dom": "6.0.0", + "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", "@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/parser": "7.2.0", @@ -4054,6 +4056,12 @@ "@testing-library/jest-dom": "*" } }, + "node_modules/@types/tinycolor2": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", + "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==", + "dev": true + }, "node_modules/@types/unist": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", diff --git a/webapp/package.json b/webapp/package.json index 117add1312..113cdf9238 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -26,8 +26,8 @@ "axios": "1.7.7", "clsx": "2.1.1", "d3": "5.16.0", - "debug": "4.3.7", "date-fns": "4.1.0", + "debug": "4.3.7", "draft-convert": "2.1.13", "draft-js": "0.11.7", "draftjs-to-html": "0.9.1", @@ -68,6 +68,7 @@ "redux": "4.2.1", "redux-thunk": "2.4.2", "swagger-ui-react": "5.17.14", + "tinycolor2": "1.6.0", "ts-toolbelt": "9.6.0", "use-undo": "1.1.1", "uuid": "10.0.0", @@ -79,12 +80,12 @@ "@testing-library/user-event": "14.5.2", "@total-typescript/ts-reset": "0.6.1", "@types/d3": "5.16.0", + "@types/date-fns": "2.6.0", "@types/debug": "4.1.12", "@types/draft-convert": "2.1.8", "@types/draft-js": "0.11.18", "@types/draftjs-to-html": "0.8.4", "@types/js-cookie": "3.0.6", - "@types/date-fns": "2.6.0", "@types/jsoneditor": "9.9.5", "@types/lodash": "4.17.9", "@types/node": "22.7.3", @@ -100,6 +101,7 @@ "@types/react-window": "1.8.8", "@types/swagger-ui-react": "4.18.3", "@types/testing-library__jest-dom": "6.0.0", + "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", "@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/parser": "7.2.0", diff --git a/webapp/src/common/types.ts b/webapp/src/common/types.ts index 4e087569f7..0674b5031a 100644 --- a/webapp/src/common/types.ts +++ b/webapp/src/common/types.ts @@ -531,21 +531,6 @@ export interface UpdateAreaUi { layerColor: AreaLayerColor; } -export interface LinkUIInfoDTO { - color: string; - style: string; - width: number; -} - -export interface LinkCreationInfoDTO { - area1: string; - area2: string; -} - -export interface LinkInfoWithUI extends LinkCreationInfoDTO { - ui: LinkUIInfoDTO; -} - export interface AreaCreationDTO { name: string; type: object; diff --git a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx index bb74aac2ad..8f0be1caa7 100644 --- a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CandidateForm.tsx @@ -35,14 +35,14 @@ import { StyledVisibilityIcon, StyledDeleteIcon, } from "../share/styles"; -import { LinkCreationInfoDTO } from "../../../../../../common/types"; import { XpansionCandidate } from "../types"; import SelectSingle from "../../../../../common/SelectSingle"; import SwitchFE from "../../../../../common/fieldEditors/SwitchFE"; +import type { LinkDTO } from "@/services/api/studies/links/types"; interface PropType { candidate: XpansionCandidate | undefined; - links: LinkCreationInfoDTO[]; + links: LinkDTO[]; capacities: string[]; deleteCandidate: (name: string | undefined) => Promise; updateCandidate: ( diff --git a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx index b4c09015e7..8e03d0c70d 100644 --- a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx @@ -17,7 +17,6 @@ import { Button, ButtonGroup } from "@mui/material"; import { useTranslation } from "react-i18next"; import AddCircleIcon from "@mui/icons-material/AddCircle"; import * as R from "ramda"; -import { LinkCreationInfoDTO } from "../../../../../../common/types"; import { XpansionCandidate } from "../types"; import FormDialog from "../../../../../common/dialogs/FormDialog"; import StringFE from "../../../../../common/fieldEditors/StringFE"; @@ -26,10 +25,11 @@ import SelectFE from "../../../../../common/fieldEditors/SelectFE"; import NumberFE from "../../../../../common/fieldEditors/NumberFE"; import { SubmitHandlerPlus } from "../../../../../common/Form/types"; import { validateString } from "@/utils/validation/string"; +import type { LinkDTO } from "@/services/api/studies/links/types"; interface PropType { open: boolean; - links: LinkCreationInfoDTO[]; + links: LinkDTO[]; onClose: () => void; onSave: (candidate: XpansionCandidate) => void; candidates: XpansionCandidate[]; diff --git a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx index 66d41de0c2..43cc113532 100644 --- a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/index.tsx @@ -36,7 +36,6 @@ import { removeEmptyFields, } from "../../../../../../services/utils/index"; import useEnqueueErrorSnackbar from "../../../../../../hooks/useEnqueueErrorSnackbar"; -import { getAllLinks } from "../../../../../../services/api/studydata"; import XpansionPropsView from "./XpansionPropsView"; import CreateCandidateDialog from "./CreateCandidateDialog"; import CandidateForm from "./CandidateForm"; @@ -44,6 +43,7 @@ import usePromiseWithSnackbarError from "../../../../../../hooks/usePromiseWithS import DataViewerDialog from "../../../../../common/dialogs/DataViewerDialog"; import EmptyView from "../../../../../common/page/SimpleContent"; import SplitView from "../../../../../common/SplitView"; +import { getLinks } from "@/services/api/studies/links"; function Candidates() { const [t] = useTranslation(); @@ -104,7 +104,7 @@ function Candidates() { if (exist) { return { capacities: await getAllCapacities(study.id), - links: await getAllLinks({ uuid: study.id }), + links: await getLinks({ studyId: study.id }), }; } return {}; diff --git a/webapp/src/redux/ducks/studyMaps.ts b/webapp/src/redux/ducks/studyMaps.ts index 9b5d1303dd..04dce9a7cc 100644 --- a/webapp/src/redux/ducks/studyMaps.ts +++ b/webapp/src/redux/ducks/studyMaps.ts @@ -47,11 +47,14 @@ import { getStudySynthesis, } from "../selectors"; import * as studyDataApi from "../../services/api/studydata"; +import * as linksApi from "../../services/api/studies/links"; import { createStudyLink, deleteStudyLink, setCurrentArea, } from "./studySyntheses"; +import type { TLinkStyle } from "@/services/api/studies/links/types"; +import tinycolor from "tinycolor2"; export interface StudyMapNode { id: string; @@ -167,11 +170,11 @@ export const setLayers = createAction< type LinkStyle = [number[], string]; -const makeLinkStyle = R.cond<[string], LinkStyle>([ - [R.equals("dot"), (): LinkStyle => [[1, 5], "round"]], - [R.equals("dash"), (): LinkStyle => [[16, 8], "square"]], - [R.equals("dotdash"), (): LinkStyle => [[10, 6, 1, 6], "square"]], - [R.T, (): LinkStyle => [[0], "butt"]], +const makeLinkStyle = R.cond<[TLinkStyle], LinkStyle>([ + [(v) => v === "dot", () => [[1, 5], "round"]], + [(v) => v === "dash", () => [[16, 8], "square"]], + [(v) => v === "dotdash", () => [[10, 6, 1, 6], "square"]], + [R.T, () => [[0], "butt"]], ]); const initStudyMapLayers = ( @@ -217,17 +220,21 @@ export const fetchStudyMapLayers = createAsyncThunk< async function getLinks( studyId: StudyMap["studyId"], ): Promise { - const links = await studyDataApi.getAllLinks({ uuid: studyId, withUi: true }); + const links = await linksApi.getLinks({ studyId }); return links.reduce( (acc, link) => { - const [style, linecap] = makeLinkStyle(link.ui?.style); + const [style, linecap] = makeLinkStyle(link.linkStyle); const id = makeLinkId(link.area1, link.area2); acc[id] = { id, - color: `rgb(${link.ui?.color}`, + color: tinycolor({ + r: link.colorr, + g: link.colorg, + b: link.colorb, + }).toRgbString(), strokeDasharray: style, strokeLinecap: linecap, - strokeWidth: link.ui?.width < 2 ? 2 : link.ui?.width, + strokeWidth: link.linkWidth < 2 ? 2 : link.linkWidth, }; return acc; }, @@ -398,7 +405,7 @@ export const createStudyMapLink = createAsyncThunk< ); try { - await studyDataApi.createLink(studyId, { area1, area2 }); + await linksApi.createLink({ studyId, area1, area2 }); } catch (err) { dispatch(deleteStudyLink({ studyId, area1, area2 })); dispatch(deleteStudyMapLinkTemp({ studyId, linkId })); @@ -425,7 +432,11 @@ export const deleteStudyMapLink = createAsyncThunk< dispatch(deleteStudyLink({ studyId, area1, area2 })); try { - await studyDataApi.deleteLink(studyId, area1, area2); + await linksApi.deleteLink({ + studyId, + areaFrom: area1, + areaTo: area2, + }); } catch (err) { dispatch(createStudyLink({ ...link, studyId, area1, area2 })); diff --git a/webapp/src/services/api/studies/config/thematicTrimming/index.ts b/webapp/src/services/api/studies/config/thematicTrimming/index.ts index c6a8bc8992..3444abeacc 100644 --- a/webapp/src/services/api/studies/config/thematicTrimming/index.ts +++ b/webapp/src/services/api/studies/config/thematicTrimming/index.ts @@ -26,8 +26,8 @@ export async function getThematicTrimmingConfig({ studyId, }: GetThematicTrimmingConfigParams) { const url = format(URL, { studyId }); - const res = await client.get(url); - return res.data; + const { data } = await client.get(url); + return data; } export async function setThematicTrimmingConfig({ diff --git a/webapp/src/services/api/studies/links/constants.ts b/webapp/src/services/api/studies/links/constants.ts new file mode 100644 index 0000000000..0bd9627ff7 --- /dev/null +++ b/webapp/src/services/api/studies/links/constants.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +export const TransmissionCapacity = { + Infinite: "infinite", + Ignore: "ignore", + Enabled: "enabled", +} as const; + +export const AssetType = { + AC: "ac", + DC: "dc", + Gaz: "gaz", + Virt: "virt", + Other: "other", +} as const; + +export const LinkStyle = { + Dot: "dot", + Plain: "plain", + Dash: "dash", + DotDash: "dotdash", + Other: "other", +} as const; diff --git a/webapp/src/services/api/studies/links/index.ts b/webapp/src/services/api/studies/links/index.ts new file mode 100644 index 0000000000..0947827906 --- /dev/null +++ b/webapp/src/services/api/studies/links/index.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +import { StudyMetadata } from "@/common/types"; +import client from "../../client"; +import type { CreateLinkParams, DeleteLinkParams, LinkDTO } from "./types"; + +export async function createLink(params: CreateLinkParams) { + const { studyId, ...body } = params; + const { data } = await client.post( + `/v1/studies/${params.studyId}/links`, + body, + ); + return data; +} + +export async function getLinks(params: { studyId: StudyMetadata["id"] }) { + const { data } = await client.get( + `/v1/studies/${params.studyId}/links`, + ); + return data; +} + +/** + * Deletes the link between the two specified areas. + * + * @param params - The parameters. + * @param params.studyId - The study ID. + * @param params.areaFrom - The from area name. + * @param params.areaTo - The to area name. + * @returns The deleted link id (format: `${areaFromId}%${areaToId}`) + */ +export async function deleteLink(params: DeleteLinkParams) { + const { studyId, areaFrom, areaTo } = params; + const { data } = await client.delete( + `/v1/studies/${studyId}/links/${areaFrom}/${areaTo}`, + ); + return data; +} diff --git a/webapp/src/services/api/studies/links/types.ts b/webapp/src/services/api/studies/links/types.ts new file mode 100644 index 0000000000..e6718eaa59 --- /dev/null +++ b/webapp/src/services/api/studies/links/types.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +import { O } from "ts-toolbelt"; +import { AssetType, LinkStyle, TransmissionCapacity } from "./constants"; +import { StudyMetadata } from "@/common/types"; +import { PartialExceptFor } from "@/utils/tsUtils"; + +export type TTransmissionCapacity = O.UnionOf; + +export type TAssetType = O.UnionOf; + +export type TLinkStyle = O.UnionOf; + +export interface LinkDTO { + hurdlesCost: boolean; + loopFlow: boolean; + usePhaseShifter: boolean; + transmissionCapacities: TTransmissionCapacity; + assetType: TAssetType; + displayComments: boolean; + colorr: number; + colorb: number; + colorg: number; + linkWidth: number; + linkStyle: TLinkStyle; + area1: string; + area2: string; + // Since v8.2 + filterSynthesis?: string; + filterYearByYear?: string; +} + +export interface CreateLinkParams + extends PartialExceptFor { + studyId: StudyMetadata["id"]; +} + +export interface DeleteLinkParams { + studyId: StudyMetadata["id"]; + areaFrom: string; + areaTo: string; +} diff --git a/webapp/src/services/api/studies/raw/index.ts b/webapp/src/services/api/studies/raw/index.ts index 9a86d92164..85524560d5 100644 --- a/webapp/src/services/api/studies/raw/index.ts +++ b/webapp/src/services/api/studies/raw/index.ts @@ -21,19 +21,19 @@ import type { export async function downloadMatrix(params: DownloadMatrixParams) { const { studyId, ...queryParams } = params; - const url = `v1/studies/${studyId}/raw/download`; + const url = `/v1/studies/${studyId}/raw/download`; - const res = await client.get(url, { + const { data } = await client.get(url, { params: queryParams, responseType: "blob", }); - return res.data; + return data; } export async function importFile(params: ImportFileParams) { const { studyId, file, onUploadProgress, ...queryParams } = params; - const url = `v1/studies/${studyId}/raw`; + const url = `/v1/studies/${studyId}/raw`; const body = { file }; await client.putForm(url, body, { @@ -47,7 +47,7 @@ export async function importFile(params: ImportFileParams) { export async function deleteFile(params: DeleteFileParams) { const { studyId, path } = params; - const url = `v1/studies/${studyId}/raw`; + const url = `/v1/studies/${studyId}/raw`; await client.delete(url, { params: { path } }); } diff --git a/webapp/src/services/api/studies/tableMode/index.ts b/webapp/src/services/api/studies/tableMode/index.ts index c20d54dbeb..1fcb75ffa0 100644 --- a/webapp/src/services/api/studies/tableMode/index.ts +++ b/webapp/src/services/api/studies/tableMode/index.ts @@ -21,7 +21,7 @@ import type { TableModeType, } from "./types"; -const TABLE_MODE_API_URL = `v1/studies/{studyId}/table-mode/{tableType}`; +const TABLE_MODE_API_URL = `/v1/studies/{studyId}/table-mode/{tableType}`; export async function getTableMode( params: GetTableModeParams, @@ -29,11 +29,11 @@ export async function getTableMode( const { studyId, tableType, columns } = params; const url = format(TABLE_MODE_API_URL, { studyId, tableType }); - const res = await client.get(url, { + const { data } = await client.get(url, { params: columns.length > 0 ? { columns: columns.join(",") } : {}, }); - return res.data; + return data; } export async function setTableMode(params: SetTableModeParams) { diff --git a/webapp/src/services/api/studies/timeseries.ts b/webapp/src/services/api/studies/timeseries.ts index 0329c49761..e786f076ef 100644 --- a/webapp/src/services/api/studies/timeseries.ts +++ b/webapp/src/services/api/studies/timeseries.ts @@ -26,7 +26,7 @@ export async function generateTimeSeries(params: { studyId: StudyMetadata["id"]; }) { const { data } = await client.put( - `v1/studies/${params.studyId}/timeseries/generate`, + `/v1/studies/${params.studyId}/timeseries/generate`, ); return data; } diff --git a/webapp/src/services/api/studydata.ts b/webapp/src/services/api/studydata.ts index a230be85bf..a6a05d8030 100644 --- a/webapp/src/services/api/studydata.ts +++ b/webapp/src/services/api/studydata.ts @@ -12,11 +12,7 @@ * This file is part of the Antares project. */ -import { - LinkCreationInfoDTO, - LinkInfoWithUI, - UpdateAreaUi, -} from "../../common/types"; +import { UpdateAreaUi } from "../../common/types"; import { BindingConstraint, ConstraintTerm, @@ -36,14 +32,6 @@ export const createArea = async ( return res.data; }; -export const createLink = async ( - uuid: string, - linkCreationInfo: LinkCreationInfoDTO, -): Promise => { - const res = await client.post(`/v1/studies/${uuid}/links`, linkCreationInfo); - return res.data; -}; - export const updateAreaUI = async ( uuid: string, areaId: string, @@ -65,17 +53,6 @@ export const deleteArea = async ( return res.data; }; -export const deleteLink = async ( - uuid: string, - areaIdFrom: string, - areaIdTo: string, -): Promise => { - const res = await client.delete( - `/v1/studies/${uuid}/links/${areaIdFrom}/${areaIdTo}`, - ); - return res.data; -}; - export const updateConstraintTerm = async ( studyId: string, constraintId: string, @@ -163,21 +140,3 @@ export const createBindingConstraint = async ( ); return res.data; }; - -interface GetAllLinksParams { - uuid: string; - withUi?: boolean; -} - -type LinkTypeFromParams = T["withUi"] extends true - ? LinkInfoWithUI - : LinkCreationInfoDTO; - -export const getAllLinks = async ( - params: T, -): Promise>> => { - const { uuid, withUi } = params; - const withUiStr = withUi ? "with_ui=true" : ""; - const res = await client.get(`/v1/studies/${uuid}/links?${withUiStr}`); - return res.data; -}; diff --git a/webapp/src/services/api/tasks/index.ts b/webapp/src/services/api/tasks/index.ts index c9003eaae0..f1599fc97e 100644 --- a/webapp/src/services/api/tasks/index.ts +++ b/webapp/src/services/api/tasks/index.ts @@ -16,7 +16,7 @@ import client from "../client"; import type { GetTaskParams, GetTasksParams, TaskDTO } from "./types"; export async function getTasks(params: GetTasksParams) { - const res = await client.post("/v1/tasks", { + const { data } = await client.post("/v1/tasks", { status: params.status, type: params.type, name: params.name, @@ -27,13 +27,13 @@ export async function getTasks(params: GetTasksParams) { to_completion_date_utc: params.toCompletionDateUtc, }); - return res.data; + return data; } export async function getTask(params: GetTaskParams) { const { id, ...queryParams } = params; - const res = await client.get(`/v1/tasks/${id}`, { + const { data } = await client.get(`/v1/tasks/${id}`, { params: { wait_for_completion: queryParams.waitForCompletion, with_logs: queryParams.withLogs, @@ -41,5 +41,5 @@ export async function getTask(params: GetTaskParams) { }, }); - return res.data; + return data; } From 204d67bdd5017c2893b60842551943be57b95188 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 14:43:49 +0100 Subject: [PATCH 086/103] fix: add copyright and license headers --- antarest/study/business/model/__init__.py | 11 +++++++++++ antarest/study/business/model/link_model.py | 11 +++++++++++ tests/integration/study_data_blueprint/test_link.py | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/antarest/study/business/model/__init__.py b/antarest/study/business/model/__init__.py index e69de29bb2..f1fa536634 100644 --- a/antarest/study/business/model/__init__.py +++ b/antarest/study/business/model/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. \ No newline at end of file diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index e68cfc3898..c577a73f0d 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -1,3 +1,14 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. import typing as t from antares.study.version import StudyVersion diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index e68bfc9d5d..36131bc69d 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -140,7 +140,7 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: assert res.status_code == 422, res.json() expected = { "body": {"area1": "area 1", "area2": "area 2", "colorr": 260}, - "description": "Input should be less than 255", + "description": "Input should be less than or equal to 255", "exception": "RequestValidationError", } assert expected == res.json() From 8a7a8e40efaf58eddf73cdf97bae8ce71a749bb1 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 14:47:28 +0100 Subject: [PATCH 087/103] fix: add copyright and license headers --- antarest/study/business/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antarest/study/business/model/__init__.py b/antarest/study/business/model/__init__.py index f1fa536634..058c6b221a 100644 --- a/antarest/study/business/model/__init__.py +++ b/antarest/study/business/model/__init__.py @@ -8,4 +8,4 @@ # # SPDX-License-Identifier: MPL-2.0 # -# This file is part of the Antares project. \ No newline at end of file +# This file is part of the Antares project. From eefd3913db31984938d392ca77bc810d82c72fc1 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 13 Nov 2024 15:00:07 +0100 Subject: [PATCH 088/103] fix: adjust color field validation limits --- antarest/study/business/model/link_model.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index c577a73f0d..b516481164 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -56,9 +56,9 @@ class LinkDTO(Area): transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED asset_type: AssetType = AssetType.AC display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorr: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, le=255) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN @@ -101,9 +101,9 @@ class LinkInternal(AntaresBaseModel): transmission_capacities: TransmissionCapacity = TransmissionCapacity.ENABLED asset_type: AssetType = AssetType.AC display_comments: bool = True - colorr: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorb: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) - colorg: int = Field(default=DEFAULT_COLOR, ge=0, lt=256) + colorr: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorb: int = Field(default=DEFAULT_COLOR, ge=0, le=255) + colorg: int = Field(default=DEFAULT_COLOR, ge=0, le=255) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES From 12493cde2f358fe912839178568dd3024e8f10cb Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 14 Nov 2024 10:57:45 +0100 Subject: [PATCH 089/103] fix: refactor and simplify link validation and creation following github review --- antarest/study/business/link_management.py | 6 +-- antarest/study/business/model/link_model.py | 49 +++++-------------- .../study/business/table_mode_management.py | 6 +-- .../rawstudy/model/filesystem/config/links.py | 17 +------ .../variantstudy/model/command/create_link.py | 6 ++- .../study_data_blueprint/test_link.py | 5 +- .../storage/business/test_arealink_manager.py | 9 ++-- 7 files changed, 31 insertions(+), 67 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index cca12fcc2c..6b22278293 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -14,12 +14,12 @@ from antares.study.version import StudyVersion -from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError +from antarest.core.exceptions import ConfigFileNotFound from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.model.link_model import LinkDTO, LinkInternal from antarest.study.business.utils import execute_or_add_commands -from antarest.study.model import RawStudy +from antarest.study.model import RawStudy, Study from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.create_link import CreateLink @@ -58,7 +58,7 @@ def get_all_links(self, study: RawStudy) -> t.List[LinkDTO]: return result - def create_link(self, study: RawStudy, link_creation_dto: LinkDTO) -> LinkDTO: + def create_link(self, study: Study, link_creation_dto: LinkDTO) -> LinkDTO: link = link_creation_dto.to_internal(StudyVersion.parse(study.version)) storage_service = self.storage_service.get_storage(study) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index b516481164..d43a9e6e0b 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -43,7 +43,7 @@ class Area(AntaresBaseModel): @model_validator(mode="after") def validate_areas(self) -> t.Self: if self.area1 == self.area2: - raise LinkValidationError(f"Cannot create link on same area: {self.area1}") + raise LinkValidationError(f"Cannot create a link that goes from and to the same single area: {self.area1}") return self @@ -66,28 +66,16 @@ class LinkDTO(Area): filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES def to_internal(self, version: StudyVersion) -> "LinkInternal": - if version < STUDY_VERSION_8_2 and ( - "filter_synthesis" in self.model_fields_set or "filter_year_by_year" in self.model_fields_set - ): + if version < STUDY_VERSION_8_2 and {"filter_synthesis", "filter_year_by_year"} & self.model_fields_set: raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") - return LinkInternal( - area1=self.area1, - area2=self.area2, - hurdles_cost=self.hurdles_cost, - loop_flow=self.loop_flow, - use_phase_shifter=self.use_phase_shifter, - transmission_capacities=self.transmission_capacities, - asset_type=self.asset_type, - display_comments=self.display_comments, - colorr=self.colorr, - colorb=self.colorb, - colorg=self.colorg, - link_width=self.link_width, - link_style=self.link_style, - filter_synthesis=self.filter_synthesis if version >= STUDY_VERSION_8_2 else None, - filter_year_by_year=self.filter_year_by_year if version >= STUDY_VERSION_8_2 else None, - ) + data = self.model_dump() + + if version < STUDY_VERSION_8_2: + data["filter_synthesis"] = None + data["filter_year_by_year"] = None + + return LinkInternal(**data) class LinkInternal(AntaresBaseModel): @@ -110,20 +98,5 @@ class LinkInternal(AntaresBaseModel): filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES def to_dto(self) -> LinkDTO: - return LinkDTO( - area1=self.area1, - area2=self.area2, - hurdles_cost=self.hurdles_cost, - loop_flow=self.loop_flow, - use_phase_shifter=self.use_phase_shifter, - transmission_capacities=self.transmission_capacities, - asset_type=self.asset_type, - display_comments=self.display_comments, - colorr=self.colorr, - colorb=self.colorb, - colorg=self.colorg, - link_width=self.link_width, - link_style=self.link_style, - filter_synthesis=self.filter_synthesis, - filter_year_by_year=self.filter_year_by_year, - ) + data = self.model_dump() + return LinkDTO(**data) diff --git a/antarest/study/business/table_mode_management.py b/antarest/study/business/table_mode_management.py index 9d62d17869..a353213cda 100644 --- a/antarest/study/business/table_mode_management.py +++ b/antarest/study/business/table_mode_management.py @@ -25,7 +25,7 @@ from antarest.study.business.binding_constraint_management import BindingConstraintManager, ConstraintInput from antarest.study.business.enum_ignore_case import EnumIgnoreCase from antarest.study.business.link_management import LinkManager, LinkOutput -from antarest.study.model import RawStudy +from antarest.study.model import STUDY_VERSION_8_2, RawStudy _TableIndex = str # row name _TableColumn = str # column name @@ -98,7 +98,7 @@ def _get_table_data_unsafe(self, study: RawStudy, table_type: TableModeType) -> data = {area_id: area.model_dump(mode="json", by_alias=True) for area_id, area in areas_map.items()} elif table_type == TableModeType.LINK: links_map = self._link_manager.get_all_links_props(study) - excludes = set() if int(study.version) >= 820 else {"filter_synthesis", "filter_year_by_year"} + excludes = set() if int(study.version) >= STUDY_VERSION_8_2 else {"filter_synthesis", "filter_year_by_year"} data = { f"{area1_id} / {area2_id}": link.model_dump(mode="json", by_alias=True, exclude=excludes) for (area1_id, area2_id), link in links_map.items() @@ -196,7 +196,7 @@ def update_table_data( elif table_type == TableModeType.LINK: links_map = {tuple(key.split(" / ")): LinkOutput(**values) for key, values in data.items()} updated_map = self._link_manager.update_links_props(study, links_map) # type: ignore - excludes = set() if int(study.version) >= 820 else {"filter_synthesis", "filter_year_by_year"} + excludes = set() if int(study.version) >= STUDY_VERSION_8_2 else {"filter_synthesis", "filter_year_by_year"} data = { f"{area1_id} / {area2_id}": link.model_dump(by_alias=True, exclude=excludes) for (area1_id, area2_id), link in updated_map.items() diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/links.py b/antarest/study/storage/rawstudy/model/filesystem/config/links.py index f782cfb406..726fe72e93 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/links.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/links.py @@ -107,7 +107,7 @@ class FilterOption(EnumIgnoreCase): def validate_filters( filter_value: t.Union[t.List[FilterOption], str], enum_cls: t.Type[FilterOption] ) -> t.List[FilterOption]: - if filter_value is not None and isinstance(filter_value, str): + if isinstance(filter_value, str): filter_accepted_values = [e for e in enum_cls] options = filter_value.replace(" ", "").split(",") @@ -226,18 +226,3 @@ def _validate_color_rgb(cls, v: t.Any) -> str: @model_validator(mode="before") def _validate_colors(cls, values: t.MutableMapping[str, t.Any]) -> t.Mapping[str, t.Any]: return validate_colors(values) - - # noinspection SpellCheckingInspection - def to_ini(self, version: int) -> t.Dict[str, t.Any]: - """ - Convert the object to a dictionary for writing to a configuration file. - """ - excludes = set() if version >= 820 else {"filter_synthesis", "filter_year_by_year"} - obj = self.model_dump(mode="json", exclude_none=True, by_alias=True, exclude=excludes) - color_rgb = obj.pop("colorRgb", "#707070") - return { - "colorr": int(color_rgb[1:3], 16), - "colorg": int(color_rgb[3:5], 16), - "colorb": int(color_rgb[5:7], 16), - **obj, - } diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index bd93a8252f..dd62fc349c 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -141,8 +141,12 @@ def _apply(self, study_data: FileStudy, listener: Optional[ICommandListener] = N if not output.status: return output + to_exclude = {"area1", "area2"} + if version < STUDY_VERSION_8_2: + to_exclude.update("filter-synthesis", "filter-year-by-year") + validated_properties = LinkInternal.model_validate(self.parameters).model_dump( - by_alias=True, exclude={"area1", "area2"} + by_alias=True, exclude=to_exclude ) area_from = data["area_from"] diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 36131bc69d..9459426728 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -116,7 +116,10 @@ def test_link_820(self, client: TestClient, user_access_token: str, study_type: res = client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area1_id}) assert res.status_code == 422, res.json() - expected = {"description": "Cannot create link on same area: area 1", "exception": "LinkValidationError"} + expected = { + "description": "Cannot create a link that goes from and to the same single area: area 1", + "exception": "LinkValidationError", + } assert expected == res.json() # Test create link with wrong value for enum diff --git a/tests/storage/business/test_arealink_manager.py b/tests/storage/business/test_arealink_manager.py index 582c6067c1..456be82a5b 100644 --- a/tests/storage/business/test_arealink_manager.py +++ b/tests/storage/business/test_arealink_manager.py @@ -103,7 +103,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = RawStudy( id=study_id, - version="-1", + version="820", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -136,7 +136,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): } area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) - study.version = 820 + link_manager.create_link( study, LinkDTO( @@ -145,7 +145,6 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ), ) assert empty_study.config.areas["test"].links.get("test2") is not None - study.version = -1 link_manager.delete_link(study, "test", "test2") assert empty_study.config.areas["test"].links.get("test2") is None @@ -158,7 +157,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): # noinspection PyArgumentList study = VariantStudy( id=variant_id, - version="-1", + version="820", path=str(empty_study.config.study_path), additional_data=StudyAdditionalData(), ) @@ -227,7 +226,6 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ) area_manager.create_area(study, AreaCreationDTO(name="test2", type=AreaType.AREA)) - study.version = 880 link_manager.create_link( study, LinkDTO( @@ -265,6 +263,7 @@ def test_area_crud(empty_study: FileStudy, matrix_service: SimpleMatrixService): ], RequestParameters(DEFAULT_ADMIN_USER), ) + study.version = 810 link_manager.create_link( study, From 0095873006b2d0c46f84d28de39425162dc3162e Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 14 Nov 2024 19:28:19 +0100 Subject: [PATCH 090/103] feat: refactor type annotations and version parsing --- antarest/study/business/link_management.py | 2 +- antarest/study/business/table_mode_management.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 6b22278293..6715c20cff 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -41,7 +41,7 @@ class LinkManager: def __init__(self, storage_service: StudyStorageService) -> None: self.storage_service = storage_service - def get_all_links(self, study: RawStudy) -> t.List[LinkDTO]: + def get_all_links(self, study: Study) -> t.List[LinkDTO]: file_study = self.storage_service.get_storage(study).get_raw(study) result: t.List[LinkDTO] = [] diff --git a/antarest/study/business/table_mode_management.py b/antarest/study/business/table_mode_management.py index a353213cda..58ee2f34e0 100644 --- a/antarest/study/business/table_mode_management.py +++ b/antarest/study/business/table_mode_management.py @@ -15,6 +15,7 @@ import numpy as np import pandas as pd +from antares.study.version import StudyVersion from antarest.core.exceptions import ChildNotFoundError from antarest.core.model import JSON @@ -98,7 +99,7 @@ def _get_table_data_unsafe(self, study: RawStudy, table_type: TableModeType) -> data = {area_id: area.model_dump(mode="json", by_alias=True) for area_id, area in areas_map.items()} elif table_type == TableModeType.LINK: links_map = self._link_manager.get_all_links_props(study) - excludes = set() if int(study.version) >= STUDY_VERSION_8_2 else {"filter_synthesis", "filter_year_by_year"} + excludes = set() if StudyVersion.parse(study.version) >= STUDY_VERSION_8_2 else {"filter_synthesis", "filter_year_by_year"} data = { f"{area1_id} / {area2_id}": link.model_dump(mode="json", by_alias=True, exclude=excludes) for (area1_id, area2_id), link in links_map.items() From abf0bbf7fc9f00f79cfc531d5ca33c59d6db0340 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 14 Nov 2024 19:44:33 +0100 Subject: [PATCH 091/103] feat: refactor condition handling in table_mode_management --- antarest/study/business/table_mode_management.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/antarest/study/business/table_mode_management.py b/antarest/study/business/table_mode_management.py index 58ee2f34e0..ded9133a46 100644 --- a/antarest/study/business/table_mode_management.py +++ b/antarest/study/business/table_mode_management.py @@ -99,7 +99,11 @@ def _get_table_data_unsafe(self, study: RawStudy, table_type: TableModeType) -> data = {area_id: area.model_dump(mode="json", by_alias=True) for area_id, area in areas_map.items()} elif table_type == TableModeType.LINK: links_map = self._link_manager.get_all_links_props(study) - excludes = set() if StudyVersion.parse(study.version) >= STUDY_VERSION_8_2 else {"filter_synthesis", "filter_year_by_year"} + excludes = ( + set() + if StudyVersion.parse(study.version) >= STUDY_VERSION_8_2 + else {"filter_synthesis", "filter_year_by_year"} + ) data = { f"{area1_id} / {area2_id}": link.model_dump(mode="json", by_alias=True, exclude=excludes) for (area1_id, area2_id), link in links_map.items() From a7d46c89a851985b01eb294a2d8f3267ccbd3b13 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Fri, 15 Nov 2024 10:54:32 +0100 Subject: [PATCH 092/103] fix: missing newline at end of file in model init --- antarest/study/business/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antarest/study/business/model/__init__.py b/antarest/study/business/model/__init__.py index f1fa536634..058c6b221a 100644 --- a/antarest/study/business/model/__init__.py +++ b/antarest/study/business/model/__init__.py @@ -8,4 +8,4 @@ # # SPDX-License-Identifier: MPL-2.0 # -# This file is part of the Antares project. \ No newline at end of file +# This file is part of the Antares project. From 658d249653244ac364c515784e00be600d1defca Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Fri, 15 Nov 2024 14:59:37 +0100 Subject: [PATCH 093/103] fix: refactor link model and update related tests --- antarest/study/business/link_management.py | 4 ---- antarest/study/business/model/link_model.py | 19 +++++++++++++++++-- .../study/business/table_mode_management.py | 6 +++++- .../variantstudy/model/command/create_link.py | 3 --- .../study_data_blueprint/test_link.py | 5 ++++- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 107f4dcbe5..93d5489e57 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -15,9 +15,6 @@ from antares.study.version import StudyVersion from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError -from antares.study.version import StudyVersion - -from antarest.core.exceptions import ConfigFileNotFound from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.model.link_model import LinkDTO, LinkInternal @@ -122,7 +119,6 @@ def get_ini_link(self, study: RawStudy, link: LinkInternal) -> LinkInternal: return link.model_copy(update=updated_link.model_dump()) - def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) command = RemoveLink( diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index d43a9e6e0b..5abe79e187 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -98,5 +98,20 @@ class LinkInternal(AntaresBaseModel): filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES def to_dto(self) -> LinkDTO: - data = self.model_dump() - return LinkDTO(**data) + return LinkDTO( + area1=self.area1, + area2=self.area2, + hurdles_cost=self.hurdles_cost, + loop_flow=self.loop_flow, + use_phase_shifter=self.use_phase_shifter, + transmission_capacities=self.transmission_capacities, + asset_type=self.asset_type, + display_comments=self.display_comments, + colorr=self.colorr, + colorb=self.colorb, + colorg=self.colorg, + link_width=self.link_width, + link_style=self.link_style, + filter_synthesis=self.filter_synthesis, + filter_year_by_year=self.filter_year_by_year, + ) diff --git a/antarest/study/business/table_mode_management.py b/antarest/study/business/table_mode_management.py index ecd20062c6..5905c78faf 100644 --- a/antarest/study/business/table_mode_management.py +++ b/antarest/study/business/table_mode_management.py @@ -201,7 +201,11 @@ def update_table_data( elif table_type == TableModeType.LINK: links_map = {tuple(key.split(" / ")): LinkOutput(**values) for key, values in data.items()} updated_map = self._link_manager.update_links_props(study, links_map) # type: ignore - excludes = set() if StudyVersion.parse(study.version) >= STUDY_VERSION_8_2 else {"filter_synthesis", "filter_year_by_year"} + excludes = ( + set() + if StudyVersion.parse(study.version) >= STUDY_VERSION_8_2 + else {"filter_synthesis", "filter_year_by_year"} + ) data = { f"{area1_id} / {area2_id}": link.model_dump(by_alias=True, exclude=excludes) for (area1_id, area2_id), link in updated_map.items() diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 7649d4fddd..fa775d258f 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -9,7 +9,6 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. -import typing as t from abc import ABCMeta from typing import Any, Dict, List, Optional, Tuple, Union, cast @@ -25,8 +24,6 @@ from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput, FilteringOptions from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand -from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix -from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig from antarest.study.storage.variantstudy.model.command_listener.command_listener import ICommandListener from antarest.study.storage.variantstudy.model.model import CommandDTO diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 4080d7b0af..696646184b 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -68,7 +68,10 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ }, ) assert res.status_code == 422 - expected = {"description": "Area 1 and Area 2 can not be the same", "exception": "LinkValidationError"} + expected = { + "description": "Cannot create a link that goes from and to the same single area: area 1", + "exception": "LinkValidationError", + } assert expected == res.json() # Test update link with non existing area From c5be5ee04a31fef2065c01216f84607a54d416f2 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 19 Nov 2024 09:19:48 +0100 Subject: [PATCH 094/103] refactor: remove duplicate and unused imports --- antarest/study/business/link_management.py | 4 ---- .../study/storage/variantstudy/model/command/create_link.py | 2 -- 2 files changed, 6 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 7bbb312529..ea744ac70a 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -15,9 +15,6 @@ from antares.study.version import StudyVersion from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError -from antares.study.version import StudyVersion - -from antarest.core.exceptions import ConfigFileNotFound from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.model.link_model import LinkDTO, LinkInternal @@ -122,7 +119,6 @@ def get_ini_link(self, study: RawStudy, link: LinkInternal) -> LinkInternal: return link.model_copy(update=updated_link.model_dump()) - def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) command = RemoveLink( diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index b665db8885..fa775d258f 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -24,8 +24,6 @@ from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput, FilteringOptions from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand -from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix -from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig from antarest.study.storage.variantstudy.model.command_listener.command_listener import ICommandListener from antarest.study.storage.variantstudy.model.model import CommandDTO From 0b121d41589c9d6d3e282de248c1ff7107961382 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Tue, 19 Nov 2024 10:08:33 +0100 Subject: [PATCH 095/103] feat: add study_version to CommandDTO in create_link --- antarest/study/business/link_management.py | 1 + .../storage/variantstudy/model/command/create_link.py | 11 +++++------ tests/variantstudy/test_command_factory.py | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 119450e24c..7ee3a9fa0c 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -91,6 +91,7 @@ def update_link(self, study: RawStudy, link_dto: LinkDTO) -> LinkDTO: include=link_dto.model_fields_set, exclude={"area1", "area2"}, exclude_none=True ), command_context=self.storage_service.variant_study_service.command_factory.command_context, + study_version=file_study.config.version, ) execute_or_add_commands(study, file_study, [command], self.storage_service) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index fa775d258f..447397f769 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -24,6 +24,8 @@ from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput, FilteringOptions from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand +from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix +from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig from antarest.study.storage.variantstudy.model.command_listener.command_listener import ICommandListener from antarest.study.storage.variantstudy.model.model import CommandDTO @@ -65,10 +67,7 @@ def to_dto(self) -> CommandDTO: for attr in MATRIX_ATTRIBUTES: if value := getattr(self, attr, None): args[attr] = strip_matrix_protocol(value) - return CommandDTO( - action=self.command_name.value, - args=args, - ) + return CommandDTO(action=self.command_name.value, args=args, study_version=self.study_version) def match(self, other: ICommand, equal: bool = False) -> bool: if not isinstance(other, CreateLink): @@ -86,8 +85,6 @@ def match(self, other: ICommand, equal: bool = False) -> bool: def _create_diff(self, other: "ICommand") -> List["ICommand"]: other = cast(CreateLink, other) - from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix - from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig commands: List[ICommand] = [] area_from, area_to = sorted([self.area1, self.area2]) @@ -100,6 +97,7 @@ def _create_diff(self, other: "ICommand") -> List["ICommand"]: target=f"input/links/{area_from}/properties/{area_to}", data=properties, command_context=self.command_context, + study_version=self.study_version, ) ) if self.series != other.series: @@ -108,6 +106,7 @@ def _create_diff(self, other: "ICommand") -> List["ICommand"]: target=f"@links_series/{area_from}/{area_to}", matrix=strip_matrix_protocol(other.series), command_context=self.command_context, + study_version=self.study_version, ) ) return commands diff --git a/tests/variantstudy/test_command_factory.py b/tests/variantstudy/test_command_factory.py index f1d29774c6..79dde561e7 100644 --- a/tests/variantstudy/test_command_factory.py +++ b/tests/variantstudy/test_command_factory.py @@ -96,6 +96,7 @@ "series": "series", } ], + study_version=STUDY_VERSION_8_8, ), CommandDTO( action=CommandName.REMOVE_LINK.value, From f12c921d335301e43f44415754d268cdfb6d6e15 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 20 Nov 2024 16:21:16 +0100 Subject: [PATCH 096/103] fix: refactor link management and update error handling --- antarest/core/exceptions.py | 5 +++++ antarest/study/business/link_management.py | 10 ++++----- antarest/study/business/model/link_model.py | 3 +++ .../study/business/xpansion_management.py | 7 +----- .../variantstudy/model/command/create_link.py | 22 +++++++++---------- .../variantstudy/model/command/update_link.py | 19 ++++++---------- antarest/study/web/study_data_blueprint.py | 7 +++--- .../study_data_blueprint/test_link.py | 11 ++++------ 8 files changed, 39 insertions(+), 45 deletions(-) diff --git a/antarest/core/exceptions.py b/antarest/core/exceptions.py index 5d574421a6..81edd606eb 100644 --- a/antarest/core/exceptions.py +++ b/antarest/core/exceptions.py @@ -300,6 +300,11 @@ def __init__(self, message: str) -> None: super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) +class LinkNotFound(HTTPException): + def __init__(self, message: str) -> None: + super().__init__(HTTPStatus.NOT_FOUND, message) + + class VariantStudyParentNotValid(HTTPException): def __init__(self, message: str) -> None: super().__init__(HTTPStatus.UNPROCESSABLE_ENTITY, message) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 7ee3a9fa0c..bea195c9af 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -14,7 +14,7 @@ from antares.study.version import StudyVersion -from antarest.core.exceptions import ConfigFileNotFound, LinkValidationError +from antarest.core.exceptions import ConfigFileNotFound, LinkNotFound, LinkValidationError from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.model.link_model import LinkDTO, LinkInternal @@ -96,7 +96,7 @@ def update_link(self, study: RawStudy, link_dto: LinkDTO) -> LinkDTO: execute_or_add_commands(study, file_study, [command], self.storage_service) - updated_link = self.get_ini_link(study, link) + updated_link = self.get_internal_link(study, link) return updated_link.to_dto() @@ -105,9 +105,9 @@ def check_link_existence(self, file_study: FileStudy, link: LinkInternal) -> Non try: file_study.tree.get(["input", "links", area_from, "properties", area_to]) except KeyError: - raise LinkValidationError(f"The link {area_from} -> {area_to} is not present in the study") + raise LinkNotFound(f"The link {area_from} -> {area_to} is not present in the study") - def get_ini_link(self, study: RawStudy, link: LinkInternal) -> LinkInternal: + def get_internal_link(self, study: RawStudy, link: LinkInternal) -> LinkInternal: file_study = self.storage_service.get_storage(study).get_raw(study) area_from, area_to = sorted([link.area1, link.area2]) @@ -119,7 +119,7 @@ def get_ini_link(self, study: RawStudy, link: LinkInternal) -> LinkInternal: link_properties.update({"area1": area_from, "area2": area_to}) updated_link = LinkInternal.model_validate(link_properties) - return link.model_copy(update=updated_link.model_dump()) + return updated_link def delete_link(self, study: RawStudy, area1_id: str, area2_id: str) -> None: file_study = self.storage_service.get_storage(study).get_raw(study) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index 5abe79e187..2533f94bc8 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -44,6 +44,9 @@ class Area(AntaresBaseModel): def validate_areas(self) -> t.Self: if self.area1 == self.area2: raise LinkValidationError(f"Cannot create a link that goes from and to the same single area: {self.area1}") + area_from, area_to = sorted([self.area1, self.area2]) + self.area1 = area_from + self.area2 = area_to return self diff --git a/antarest/study/business/xpansion_management.py b/antarest/study/business/xpansion_management.py index 02e1fc795c..c86dd5cab7 100644 --- a/antarest/study/business/xpansion_management.py +++ b/antarest/study/business/xpansion_management.py @@ -21,7 +21,7 @@ from fastapi import HTTPException, UploadFile from pydantic import Field, ValidationError, field_validator, model_validator -from antarest.core.exceptions import BadZipBinary, ChildNotFoundError +from antarest.core.exceptions import BadZipBinary, ChildNotFoundError, LinkNotFound from antarest.core.model import JSON from antarest.core.serialization import AntaresBaseModel from antarest.study.business.all_optional_meta import all_optional_model @@ -255,11 +255,6 @@ class XpansionCandidateDTO(AntaresBaseModel): ) -class LinkNotFound(HTTPException): - def __init__(self, message: str) -> None: - super().__init__(http.HTTPStatus.NOT_FOUND, message) - - class XpansionFileNotFoundError(HTTPException): def __init__(self, message: str) -> None: super().__init__(http.HTTPStatus.NOT_FOUND, message) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 447397f769..f07a18b538 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -70,7 +70,7 @@ def to_dto(self) -> CommandDTO: return CommandDTO(action=self.command_name.value, args=args, study_version=self.study_version) def match(self, other: ICommand, equal: bool = False) -> bool: - if not isinstance(other, CreateLink): + if not isinstance(other, self.__class__): return False simple_match = self.area1 == other.area1 and self.area2 == other.area2 if not equal: @@ -83,8 +83,13 @@ def match(self, other: ICommand, equal: bool = False) -> bool: and self.indirect == other.indirect ) + def match_signature(self) -> str: + return str( + self.command_name.value + MATCH_SIGNATURE_SEPARATOR + self.area1 + MATCH_SIGNATURE_SEPARATOR + self.area2 + ) + def _create_diff(self, other: "ICommand") -> List["ICommand"]: - other = cast(CreateLink, other) + other = cast(AbstractLinkCommand, other) commands: List[ICommand] = [] area_from, area_to = sorted([self.area1, self.area2]) @@ -120,7 +125,7 @@ def get_inner_matrices(self) -> List[str]: return list_matrices def save_series(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: - self.series = self.series or (self.command_context.generator_matrix_constants.get_link(version=version)) + self.series = self.command_context.generator_matrix_constants.get_link(version=version) assert isinstance(self.series, str) if version < STUDY_VERSION_8_2: study_data.tree.save(self.series, ["input", "links", area_from, area_to]) @@ -129,13 +134,11 @@ def save_series(self, area_from: str, area_to: str, study_data: FileStudy, versi self.series, ["input", "links", area_from, f"{area_to}_parameters"], ) - study_data.tree.save({}, ["input", "links", area_from, "capacities"]) def save_direct(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: - self.direct = self.direct or (self.command_context.generator_matrix_constants.get_link_direct()) + self.direct = self.command_context.generator_matrix_constants.get_link_direct() assert isinstance(self.direct, str) if version >= STUDY_VERSION_8_2: - study_data.tree.save({}, ["input", "links", area_from, "capacities"]) study_data.tree.save( self.direct, [ @@ -148,10 +151,9 @@ def save_direct(self, area_from: str, area_to: str, study_data: FileStudy, versi ) def save_indirect(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: - self.indirect = self.indirect or (self.command_context.generator_matrix_constants.get_link_indirect()) + self.indirect = self.command_context.generator_matrix_constants.get_link_indirect() assert isinstance(self.indirect, str) if version >= STUDY_VERSION_8_2: - study_data.tree.save({}, ["input", "links", area_from, "capacities"]) study_data.tree.save( self.indirect, [ @@ -281,9 +283,7 @@ def to_dto(self) -> CommandDTO: return super().to_dto() def match_signature(self) -> str: - return str( - self.command_name.value + MATCH_SIGNATURE_SEPARATOR + self.area1 + MATCH_SIGNATURE_SEPARATOR + self.area2 - ) + return super().match_signature() def match(self, other: ICommand, equal: bool = False) -> bool: return super().match(other, equal) diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 443e44e21f..46df356802 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -33,38 +33,35 @@ class UpdateLink(AbstractLinkCommand): version: int = 1 def _apply_config(self, study_data: FileStudyTreeConfig) -> OutputTuple: - area_from, area_to = sorted([self.area1, self.area2]) - return ( CommandOutput( status=True, message=f"Link between '{self.area1}' and '{self.area2}' updated", ), - {"area_from": area_from, "area_to": area_to}, + {}, ) def _apply(self, study_data: FileStudy, listener: t.Optional[ICommandListener] = None) -> CommandOutput: version = study_data.config.version - area_from, area_to = sorted([self.area1, self.area2]) - properties = study_data.tree.get(["input", "links", area_from, "properties", area_to]) + properties = study_data.tree.get(["input", "links", self.area1, "properties", self.area2]) new_properties = LinkInternal.model_validate(self.parameters).model_dump(include=self.parameters, by_alias=True) properties.update(new_properties) - study_data.tree.save(properties, ["input", "links", area_from, "properties", area_to]) + study_data.tree.save(properties, ["input", "links", self.area1, "properties", self.area2]) output, _ = self._apply_config(study_data.config) if self.series: - self.save_series(area_from, area_to, study_data, version) + self.save_series(self.area1, self.area2, study_data, version) if self.direct: - self.save_direct(area_from, area_to, study_data, version) + self.save_direct(self.area1, self.area2, study_data, version) if self.indirect: - self.save_indirect(area_from, area_to, study_data, version) + self.save_indirect(self.area1, self.area2, study_data, version) return output @@ -72,9 +69,7 @@ def to_dto(self) -> CommandDTO: return super().to_dto() def match_signature(self) -> str: - return str( - self.command_name.value + MATCH_SIGNATURE_SEPARATOR + self.area1 + MATCH_SIGNATURE_SEPARATOR + self.area2 - ) + return super().match_signature() def _create_diff(self, other: "ICommand") -> t.List["ICommand"]: return super()._create_diff(other) diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 91dd3aef87..f856e19adc 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -205,16 +205,15 @@ def create_link( ) def update_link( uuid: str, - link_creation_info: LinkDTO, + link_update_info: LinkDTO, current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Any: - area_from, area_to = sorted([link_creation_info.area1, link_creation_info.area2]) logger.info( - f"Updating link {area_from} -> {area_to} for study {uuid}", + f"Updating link {link_update_info.area1} -> {link_update_info.area2} for study {uuid}", extra={"user": current_user.id}, ) params = RequestParameters(user=current_user) - return study_service.update_link(uuid, link_creation_info, params) + return study_service.update_link(uuid, link_update_info, params) @bp.put( "/studies/{uuid}/areas/{area_id}/ui", diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 96cd35ffca..0ff43e60f7 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -84,10 +84,10 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ "hurdlesCost": False, }, ) - assert res.status_code == 422 + assert res.status_code == 404 expected = { "description": "The link area 1 -> id_do_not_exist is not present in the study", - "exception": "LinkValidationError", + "exception": "LinkNotFound", } assert expected == res.json() @@ -107,11 +107,8 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ ], ) assert res.status_code == 500 - expected = { - "description": "Unexpected exception occurred when trying to apply command CommandName.UPDATE_LINK: 1 validation error for LinkInternal\nwrong\n Extra inputs are not permitted [type=extra_forbidden, input_value='parameter', input_type=str]\n For further information visit https://errors.pydantic.dev/2.8/v/extra_forbidden", - "exception": "CommandApplicationError", - } - assert expected == res.json() + expected = "Unexpected exception occurred when trying to apply command CommandName.UPDATE_LINK" + assert expected in res.json()['description'] # Test update link variant returns only modified values From 306f85bb4f3533f486666d81e931dc35b21cf0e6 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 21 Nov 2024 11:29:40 +0100 Subject: [PATCH 097/103] fix: refactor to_dto method and fix test string format --- antarest/study/business/model/link_model.py | 19 ++----------------- .../study_data_blueprint/test_link.py | 2 +- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index 2533f94bc8..1b7a9c925d 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -101,20 +101,5 @@ class LinkInternal(AntaresBaseModel): filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES def to_dto(self) -> LinkDTO: - return LinkDTO( - area1=self.area1, - area2=self.area2, - hurdles_cost=self.hurdles_cost, - loop_flow=self.loop_flow, - use_phase_shifter=self.use_phase_shifter, - transmission_capacities=self.transmission_capacities, - asset_type=self.asset_type, - display_comments=self.display_comments, - colorr=self.colorr, - colorb=self.colorb, - colorg=self.colorg, - link_width=self.link_width, - link_style=self.link_style, - filter_synthesis=self.filter_synthesis, - filter_year_by_year=self.filter_year_by_year, - ) + data = self.model_dump() + return LinkDTO(**data) diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 0ff43e60f7..582a3f4427 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -108,7 +108,7 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ ) assert res.status_code == 500 expected = "Unexpected exception occurred when trying to apply command CommandName.UPDATE_LINK" - assert expected in res.json()['description'] + assert expected in res.json()["description"] # Test update link variant returns only modified values From d4b4716cc0449258babaaeb859dc1ff7f22164ee Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 25 Nov 2024 10:28:58 +0100 Subject: [PATCH 098/103] fix: changes after review --- antarest/study/business/link_management.py | 18 +++++++----------- .../variantstudy/model/command/update_link.py | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index bea195c9af..1f380c8110 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -11,6 +11,7 @@ # This file is part of the Antares project. import typing as t +from typing import Any, Dict from antares.study.version import StudyVersion @@ -82,7 +83,7 @@ def update_link(self, study: RawStudy, link_dto: LinkDTO) -> LinkDTO: link = link_dto.to_internal(StudyVersion.parse(study.version)) file_study = self.storage_service.get_storage(study).get_raw(study) - self.check_link_existence(file_study, link) + self.get_link_if_exists(file_study, link) command = UpdateLink( area1=link.area1, @@ -100,23 +101,18 @@ def update_link(self, study: RawStudy, link_dto: LinkDTO) -> LinkDTO: return updated_link.to_dto() - def check_link_existence(self, file_study: FileStudy, link: LinkInternal) -> None: - area_from, area_to = sorted([link.area1, link.area2]) + def get_link_if_exists(self, file_study: FileStudy, link: LinkInternal) -> dict[str, Any]: try: - file_study.tree.get(["input", "links", area_from, "properties", area_to]) + return file_study.tree.get(["input", "links", link.area1, "properties", link.area2]) except KeyError: - raise LinkNotFound(f"The link {area_from} -> {area_to} is not present in the study") + raise LinkNotFound(f"The link {link.area1} -> {link.area2} is not present in the study") def get_internal_link(self, study: RawStudy, link: LinkInternal) -> LinkInternal: file_study = self.storage_service.get_storage(study).get_raw(study) - area_from, area_to = sorted([link.area1, link.area2]) - try: - link_properties = file_study.tree.get(["input", "links", area_from, "properties", area_to]) - except KeyError: - raise LinkValidationError(f"The link {area_from} -> {area_to} is not present in the study") + link_properties = self.get_link_if_exists(file_study, link) - link_properties.update({"area1": area_from, "area2": area_to}) + link_properties.update({"area1": link.area1, "area2": link.area2}) updated_link = LinkInternal.model_validate(link_properties) return updated_link diff --git a/antarest/study/storage/variantstudy/model/command/update_link.py b/antarest/study/storage/variantstudy/model/command/update_link.py index 46df356802..ceab38ed56 100644 --- a/antarest/study/storage/variantstudy/model/command/update_link.py +++ b/antarest/study/storage/variantstudy/model/command/update_link.py @@ -16,7 +16,7 @@ from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput from antarest.study.storage.variantstudy.model.command.create_link import AbstractLinkCommand -from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand, OutputTuple +from antarest.study.storage.variantstudy.model.command.icommand import ICommand, OutputTuple from antarest.study.storage.variantstudy.model.command_listener.command_listener import ICommandListener from antarest.study.storage.variantstudy.model.model import CommandDTO From cb303b5fa4317eccfa803c3b761b8a6078903b88 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Mon, 25 Nov 2024 15:12:11 +0100 Subject: [PATCH 099/103] fix: changes after review --- .../variantstudy/model/command/create_link.py | 9 ++++++++ .../study_data_blueprint/test_link.py | 3 +++ .../model/command/test_create_link.py | 22 ++++++++++++------- .../model/command/test_remove_area.py | 1 - 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index f07a18b538..7d8299ce83 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -15,6 +15,7 @@ from antares.study.version import StudyVersion from pydantic import ValidationInfo, field_validator, model_validator +from antarest.core.exceptions import LinkValidationError from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData from antarest.study.business.model.link_model import LinkInternal @@ -56,6 +57,14 @@ def validate_series( def validate_areas(self) -> "AbstractLinkCommand": if self.area1 == self.area2: raise ValueError("Cannot create link on same node") + + if StudyVersion.parse(self.study_version) < STUDY_VERSION_8_2 and ( + self.series is not None or self.direct is not None or self.indirect is not None + ): + raise LinkValidationError( + "The fields 'series', 'direct', and 'indirect' cannot be provided when the version is less than 820." + ) + return self def to_dto(self) -> CommandDTO: diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 582a3f4427..2e69c473ad 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -15,6 +15,9 @@ from starlette.testclient import TestClient from antarest.study.storage.rawstudy.model.filesystem.config.links import TransmissionCapacity +from antarest.study.storage.variantstudy.model.command.create_link import CreateLink +from antarest.study.storage.variantstudy.model.command.icommand import ICommand +from antarest.study.storage.variantstudy.model.command_context import CommandContext from tests.integration.prepare_proxy import PreparerProxy diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index c2e49e6509..fce4b761f0 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -17,6 +17,7 @@ import pytest from pydantic import ValidationError +from antarest.core.exceptions import LinkValidationError from antarest.study.business.link_management import LinkInternal from antarest.study.model import STUDY_VERSION_8_8 from antarest.study.storage.rawstudy.ini_reader import IniReader @@ -52,7 +53,6 @@ def test_validation(self, empty_study: FileStudy, command_context: CommandContex area2=area1, parameters={}, command_context=command_context, - series=[[0]], study_version=STUDY_VERSION_8_8, ) @@ -85,7 +85,6 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): area2=area2_id, parameters={}, command_context=command_context, - series=[[0]], study_version=study_version, ) output = create_link_command.apply( @@ -116,8 +115,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): area2=area3_id, parameters={}, command_context=command_context, - series=[[0]], - study_version=study_version, + study_version=empty_study.config.version, ) output = create_link_command.apply( study_data=empty_study, @@ -136,9 +134,8 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): "area1": area1_id, "area2": area2_id, "parameters": {}, - "series": [[0]], "command_context": command_context, - "study_version": study_version, + "study_version": empty_study.config.version, } ).apply(study_data=empty_study) @@ -165,7 +162,6 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): "area1": area3_id, "area2": area1_id, "parameters": parameters, - "series": [[0]], "command_context": command_context, "study_version": study_version, } @@ -175,6 +171,17 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): ) assert output.status + with pytest.raises(LinkValidationError): + CreateLink.model_validate( + { + "area1": area3_id, + "area2": area1_id, + "parameters": parameters, + "series": [[0]], + "command_context": command_context, + "study_version": study_version, + } + ) assert (study_path / "input" / "links" / area1_id / f"{area3_id}.txt.link").exists() @@ -201,7 +208,6 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): area1="does_not_exist", area2=area2_id, parameters={}, - series=[[0]], command_context=command_context, study_version=study_version, ).apply(empty_study) diff --git a/tests/variantstudy/model/command/test_remove_area.py b/tests/variantstudy/model/command/test_remove_area.py index 014d9f9e5f..22d9de6cf9 100644 --- a/tests/variantstudy/model/command/test_remove_area.py +++ b/tests/variantstudy/model/command/test_remove_area.py @@ -136,7 +136,6 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): parameters={}, command_context=command_context, study_version=study_version, - series=[[0]], ) output = create_link_command.apply(study_data=empty_study) assert output.status, output.message From 729eceefaf2ac509bf3abe54d73c7abfec1010a3 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Wed, 27 Nov 2024 09:28:13 +0100 Subject: [PATCH 100/103] feat: fix tests --- .../variantstudy/model/command/create_link.py | 9 ++------- tests/variantstudy/model/command/test_create_link.py | 12 +++++++++--- tests/variantstudy/model/command/test_remove_area.py | 1 + 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/antarest/study/storage/variantstudy/model/command/create_link.py b/antarest/study/storage/variantstudy/model/command/create_link.py index 7d8299ce83..1e46c20bf1 100644 --- a/antarest/study/storage/variantstudy/model/command/create_link.py +++ b/antarest/study/storage/variantstudy/model/command/create_link.py @@ -58,11 +58,9 @@ def validate_areas(self) -> "AbstractLinkCommand": if self.area1 == self.area2: raise ValueError("Cannot create link on same node") - if StudyVersion.parse(self.study_version) < STUDY_VERSION_8_2 and ( - self.series is not None or self.direct is not None or self.indirect is not None - ): + if self.study_version < STUDY_VERSION_8_2 and (self.direct is not None or self.indirect is not None): raise LinkValidationError( - "The fields 'series', 'direct', and 'indirect' cannot be provided when the version is less than 820." + "The fields 'direct' and 'indirect' cannot be provided when the version is less than 820." ) return self @@ -134,7 +132,6 @@ def get_inner_matrices(self) -> List[str]: return list_matrices def save_series(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: - self.series = self.command_context.generator_matrix_constants.get_link(version=version) assert isinstance(self.series, str) if version < STUDY_VERSION_8_2: study_data.tree.save(self.series, ["input", "links", area_from, area_to]) @@ -145,7 +142,6 @@ def save_series(self, area_from: str, area_to: str, study_data: FileStudy, versi ) def save_direct(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: - self.direct = self.command_context.generator_matrix_constants.get_link_direct() assert isinstance(self.direct, str) if version >= STUDY_VERSION_8_2: study_data.tree.save( @@ -160,7 +156,6 @@ def save_direct(self, area_from: str, area_to: str, study_data: FileStudy, versi ) def save_indirect(self, area_from: str, area_to: str, study_data: FileStudy, version: StudyVersion) -> None: - self.indirect = self.command_context.generator_matrix_constants.get_link_indirect() assert isinstance(self.indirect, str) if version >= STUDY_VERSION_8_2: study_data.tree.save( diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index fce4b761f0..bd4bac60ef 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -53,6 +53,7 @@ def test_validation(self, empty_study: FileStudy, command_context: CommandContex area2=area1, parameters={}, command_context=command_context, + series=[[0]], study_version=STUDY_VERSION_8_8, ) @@ -85,6 +86,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): area2=area2_id, parameters={}, command_context=command_context, + series=[[0]], study_version=study_version, ) output = create_link_command.apply( @@ -115,7 +117,8 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): area2=area3_id, parameters={}, command_context=command_context, - study_version=empty_study.config.version, + series=[[0]], + study_version=study_version, ) output = create_link_command.apply( study_data=empty_study, @@ -135,7 +138,8 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): "area2": area2_id, "parameters": {}, "command_context": command_context, - "study_version": empty_study.config.version, + "series": [[0]], + "study_version": study_version, } ).apply(study_data=empty_study) @@ -163,6 +167,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): "area2": area1_id, "parameters": parameters, "command_context": command_context, + "series": [[0]], "study_version": study_version, } ) @@ -177,7 +182,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): "area1": area3_id, "area2": area1_id, "parameters": parameters, - "series": [[0]], + "direct": [[0]], "command_context": command_context, "study_version": study_version, } @@ -209,6 +214,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): area2=area2_id, parameters={}, command_context=command_context, + series=[[0]], study_version=study_version, ).apply(empty_study) assert not output.status diff --git a/tests/variantstudy/model/command/test_remove_area.py b/tests/variantstudy/model/command/test_remove_area.py index 22d9de6cf9..b3de1c3342 100644 --- a/tests/variantstudy/model/command/test_remove_area.py +++ b/tests/variantstudy/model/command/test_remove_area.py @@ -135,6 +135,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): area2=area_id2, parameters={}, command_context=command_context, + series=[[0]], study_version=study_version, ) output = create_link_command.apply(study_data=empty_study) From c9077e9167c7c9c402fc7e084304f8e6265506c2 Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 28 Nov 2024 14:49:21 +0100 Subject: [PATCH 101/103] feat: change the url for the update endpoint. Added new linkDtoForUpdate object to hide area1 and area2 from the client --- antarest/study/business/link_management.py | 12 ++++---- antarest/study/business/model/link_model.py | 12 +++++++- antarest/study/service.py | 8 ++++-- antarest/study/web/study_data_blueprint.py | 12 ++++---- .../study_data_blueprint/test_link.py | 28 ++++++------------- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 1f380c8110..40b8d92301 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -18,7 +18,7 @@ from antarest.core.exceptions import ConfigFileNotFound, LinkNotFound, LinkValidationError from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model -from antarest.study.business.model.link_model import LinkDTO, LinkInternal +from antarest.study.business.model.link_model import LinkDTO, LinkDtoForUpdate, LinkInternal from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy, Study from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties @@ -79,17 +79,19 @@ def create_link(self, study: Study, link_creation_dto: LinkDTO) -> LinkDTO: return link_creation_dto - def update_link(self, study: RawStudy, link_dto: LinkDTO) -> LinkDTO: + def update_link(self, study: RawStudy, area_from: str, area_to: str, link_update_dto: LinkDtoForUpdate) -> LinkDTO: + link_dto = LinkDTO(area1=area_from, area2=area_to, **link_update_dto.model_dump()) + link = link_dto.to_internal(StudyVersion.parse(study.version)) file_study = self.storage_service.get_storage(study).get_raw(study) self.get_link_if_exists(file_study, link) command = UpdateLink( - area1=link.area1, - area2=link.area2, + area1=area_from, + area2=area_to, parameters=link.model_dump( - include=link_dto.model_fields_set, exclude={"area1", "area2"}, exclude_none=True + include=link_update_dto.model_fields_set, exclude={"area1", "area2"}, exclude_none=True ), command_context=self.storage_service.variant_study_service.command_factory.command_context, study_version=file_study.config.version, diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index 1b7a9c925d..a171631cba 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -13,6 +13,7 @@ from antares.study.version import StudyVersion from pydantic import ConfigDict, Field, model_validator +from pydantic.json_schema import SkipJsonSchema from antarest.core.exceptions import LinkValidationError from antarest.core.serialization import AntaresBaseModel @@ -51,7 +52,11 @@ def validate_areas(self) -> t.Self: class LinkDTO(Area): - model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") + model_config = ConfigDict( + alias_generator=to_camel_case, + populate_by_name=True, + extra="forbid", + ) hurdles_cost: bool = False loop_flow: bool = False @@ -81,6 +86,11 @@ def to_internal(self, version: StudyVersion) -> "LinkInternal": return LinkInternal(**data) +class LinkDtoForUpdate(LinkDTO): + area1: SkipJsonSchema[str] = Field("a", exclude=True) + area2: SkipJsonSchema[str] = Field("b", exclude=True) + + class LinkInternal(AntaresBaseModel): model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True, extra="forbid") diff --git a/antarest/study/service.py b/antarest/study/service.py index 49759e1d3c..ebab8bac69 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -89,7 +89,7 @@ from antarest.study.business.general_management import GeneralManager from antarest.study.business.link_management import LinkManager from antarest.study.business.matrix_management import MatrixManager, MatrixManagerError -from antarest.study.business.model.link_model import LinkDTO +from antarest.study.business.model.link_model import LinkDTO, LinkDtoForUpdate from antarest.study.business.optimization_management import OptimizationManager from antarest.study.business.playlist_management import PlaylistManager from antarest.study.business.scenario_builder_management import ScenarioBuilderManager @@ -1908,13 +1908,15 @@ def create_link( def update_link( self, uuid: str, - link_update_dto: LinkDTO, + area_from: str, + area_to: str, + link_update_dto: LinkDtoForUpdate, params: RequestParameters, ) -> LinkDTO: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.WRITE) self._assert_study_unarchived(study) - updated_link = self.links_manager.update_link(study, link_update_dto) + updated_link = self.links_manager.update_link(study, area_from, area_to, link_update_dto) self.event_bus.push( Event( type=EventType.STUDY_DATA_EDITED, diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index f856e19adc..e1a9ed90e9 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -68,7 +68,7 @@ ) from antarest.study.business.district_manager import DistrictCreationDTO, DistrictInfoDTO, DistrictUpdateDTO from antarest.study.business.general_management import GeneralFormFields -from antarest.study.business.model.link_model import LinkDTO +from antarest.study.business.model.link_model import LinkDTO, LinkDtoForUpdate from antarest.study.business.optimization_management import OptimizationFormFields from antarest.study.business.playlist_management import PlaylistColumns from antarest.study.business.scenario_builder_management import Rulesets, ScenarioType @@ -198,22 +198,24 @@ def create_link( return study_service.create_link(uuid, link_creation_info, params) @bp.put( - "/studies/{uuid}/links", + "/studies/{uuid}/links/{area_from}/{area_to}", tags=[APITag.study_data], summary="Update a link", response_model=LinkDTO, ) def update_link( uuid: str, - link_update_info: LinkDTO, + area_from: str, + area_to: str, + link_update_dto: LinkDtoForUpdate, current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Any: logger.info( - f"Updating link {link_update_info.area1} -> {link_update_info.area2} for study {uuid}", + f"Updating link {area_from} -> {area_to} for study {uuid}", extra={"user": current_user.id}, ) params = RequestParameters(user=current_user) - return study_service.update_link(uuid, link_update_info, params) + return study_service.update_link(uuid, area_from, area_to, link_update_dto, params) @bp.put( "/studies/{uuid}/areas/{area_id}/ui", diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 2e69c473ad..027aaea21a 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -36,8 +36,8 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ area2_id = preparer.create_area(study_id, name="Area 2")["id"] client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "hurdlesCost": True}) res = client.put( - f"/v1/studies/{study_id}/links", - json={"area1": area1_id, "area2": area2_id, "colorr": 150}, + f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}", + json={"colorr": 150}, ) assert res.status_code == 200 @@ -63,12 +63,8 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ # Test update link same area res = client.put( - f"/v1/studies/{study_id}/links", - json={ - "area1": area1_id, - "area2": area1_id, - "hurdlesCost": False, - }, + f"/v1/studies/{study_id}/links/{area1_id}/{area1_id}", + json={"hurdlesCost": False}, ) assert res.status_code == 422 expected = { @@ -80,12 +76,8 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ # Test update link with non existing area res = client.put( - f"/v1/studies/{study_id}/links", - json={ - "area1": area1_id, - "area2": "id_do_not_exist", - "hurdlesCost": False, - }, + f"/v1/studies/{study_id}/links/{area1_id}/id_do_not_exist", + json={"hurdlesCost": False}, ) assert res.status_code == 404 expected = { @@ -117,12 +109,8 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ if study_type == "variant": res = client.put( - f"/v1/studies/{study_id}/links", - json={ - "area1": area1_id, - "area2": area2_id, - "hurdlesCost": False, - }, + f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}", + json={"hurdlesCost": False}, ) assert res.status_code == 200 From 841ea41c29880e92025b00aabb9dc021cb541b4e Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 28 Nov 2024 18:07:10 +0100 Subject: [PATCH 102/103] feat: change model logic for update endpoint --- antarest/study/business/link_management.py | 4 +- antarest/study/business/model/link_model.py | 45 ++++++++----------- antarest/study/service.py | 4 +- antarest/study/web/study_data_blueprint.py | 4 +- .../study_data_blueprint/test_link.py | 3 -- 5 files changed, 24 insertions(+), 36 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 40b8d92301..7314934692 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -18,7 +18,7 @@ from antarest.core.exceptions import ConfigFileNotFound, LinkNotFound, LinkValidationError from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model -from antarest.study.business.model.link_model import LinkDTO, LinkDtoForUpdate, LinkInternal +from antarest.study.business.model.link_model import LinkBaseDto, LinkDTO, LinkInternal from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy, Study from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties @@ -79,7 +79,7 @@ def create_link(self, study: Study, link_creation_dto: LinkDTO) -> LinkDTO: return link_creation_dto - def update_link(self, study: RawStudy, area_from: str, area_to: str, link_update_dto: LinkDtoForUpdate) -> LinkDTO: + def update_link(self, study: RawStudy, area_from: str, area_to: str, link_update_dto: LinkBaseDto) -> LinkDTO: link_dto = LinkDTO(area1=area_from, area2=area_to, **link_update_dto.model_dump()) link = link_dto.to_internal(StudyVersion.parse(study.version)) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index a171631cba..9038270d32 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -13,7 +13,6 @@ from antares.study.version import StudyVersion from pydantic import ConfigDict, Field, model_validator -from pydantic.json_schema import SkipJsonSchema from antarest.core.exceptions import LinkValidationError from antarest.core.serialization import AntaresBaseModel @@ -37,26 +36,8 @@ ] -class Area(AntaresBaseModel): - area1: str - area2: str - - @model_validator(mode="after") - def validate_areas(self) -> t.Self: - if self.area1 == self.area2: - raise LinkValidationError(f"Cannot create a link that goes from and to the same single area: {self.area1}") - area_from, area_to = sorted([self.area1, self.area2]) - self.area1 = area_from - self.area2 = area_to - return self - - -class LinkDTO(Area): - model_config = ConfigDict( - alias_generator=to_camel_case, - populate_by_name=True, - extra="forbid", - ) +class LinkBaseDto(AntaresBaseModel): + model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") hurdles_cost: bool = False loop_flow: bool = False @@ -69,10 +50,25 @@ class LinkDTO(Area): colorg: int = Field(default=DEFAULT_COLOR, ge=0, le=255) link_width: float = 1 link_style: LinkStyle = LinkStyle.PLAIN - filter_synthesis: t.Optional[comma_separated_enum_list] = FILTER_VALUES filter_year_by_year: t.Optional[comma_separated_enum_list] = FILTER_VALUES + +class Area(AntaresBaseModel): + area1: str + area2: str + + @model_validator(mode="after") + def validate_areas(self) -> t.Self: + if self.area1 == self.area2: + raise LinkValidationError(f"Cannot create a link that goes from and to the same single area: {self.area1}") + area_from, area_to = sorted([self.area1, self.area2]) + self.area1 = area_from + self.area2 = area_to + return self + + +class LinkDTO(Area, LinkBaseDto): def to_internal(self, version: StudyVersion) -> "LinkInternal": if version < STUDY_VERSION_8_2 and {"filter_synthesis", "filter_year_by_year"} & self.model_fields_set: raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") @@ -86,11 +82,6 @@ def to_internal(self, version: StudyVersion) -> "LinkInternal": return LinkInternal(**data) -class LinkDtoForUpdate(LinkDTO): - area1: SkipJsonSchema[str] = Field("a", exclude=True) - area2: SkipJsonSchema[str] = Field("b", exclude=True) - - class LinkInternal(AntaresBaseModel): model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True, extra="forbid") diff --git a/antarest/study/service.py b/antarest/study/service.py index ebab8bac69..f0d5074624 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -89,7 +89,7 @@ from antarest.study.business.general_management import GeneralManager from antarest.study.business.link_management import LinkManager from antarest.study.business.matrix_management import MatrixManager, MatrixManagerError -from antarest.study.business.model.link_model import LinkDTO, LinkDtoForUpdate +from antarest.study.business.model.link_model import LinkBaseDto, LinkDTO from antarest.study.business.optimization_management import OptimizationManager from antarest.study.business.playlist_management import PlaylistManager from antarest.study.business.scenario_builder_management import ScenarioBuilderManager @@ -1910,7 +1910,7 @@ def update_link( uuid: str, area_from: str, area_to: str, - link_update_dto: LinkDtoForUpdate, + link_update_dto: LinkBaseDto, params: RequestParameters, ) -> LinkDTO: study = self.get_study(uuid) diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index e1a9ed90e9..1ea06a82c3 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -68,7 +68,7 @@ ) from antarest.study.business.district_manager import DistrictCreationDTO, DistrictInfoDTO, DistrictUpdateDTO from antarest.study.business.general_management import GeneralFormFields -from antarest.study.business.model.link_model import LinkDTO, LinkDtoForUpdate +from antarest.study.business.model.link_model import LinkBaseDto, LinkDTO from antarest.study.business.optimization_management import OptimizationFormFields from antarest.study.business.playlist_management import PlaylistColumns from antarest.study.business.scenario_builder_management import Rulesets, ScenarioType @@ -207,7 +207,7 @@ def update_link( uuid: str, area_from: str, area_to: str, - link_update_dto: LinkDtoForUpdate, + link_update_dto: LinkBaseDto, current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Any: logger.info( diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 027aaea21a..635aedb830 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -15,9 +15,6 @@ from starlette.testclient import TestClient from antarest.study.storage.rawstudy.model.filesystem.config.links import TransmissionCapacity -from antarest.study.storage.variantstudy.model.command.create_link import CreateLink -from antarest.study.storage.variantstudy.model.command.icommand import ICommand -from antarest.study.storage.variantstudy.model.command_context import CommandContext from tests.integration.prepare_proxy import PreparerProxy From 43353c426dba9f33bee112f667aaecffb3af53aa Mon Sep 17 00:00:00 2001 From: Theo Pascoli Date: Thu, 28 Nov 2024 18:09:29 +0100 Subject: [PATCH 103/103] feat: change model logic for update endpoint --- antarest/study/business/link_management.py | 4 ++-- antarest/study/business/model/link_model.py | 4 ++-- antarest/study/service.py | 4 ++-- antarest/study/web/study_data_blueprint.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 7314934692..777ce3cd9e 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -18,7 +18,7 @@ from antarest.core.exceptions import ConfigFileNotFound, LinkNotFound, LinkValidationError from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model -from antarest.study.business.model.link_model import LinkBaseDto, LinkDTO, LinkInternal +from antarest.study.business.model.link_model import LinkBaseDTO, LinkDTO, LinkInternal from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy, Study from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties @@ -79,7 +79,7 @@ def create_link(self, study: Study, link_creation_dto: LinkDTO) -> LinkDTO: return link_creation_dto - def update_link(self, study: RawStudy, area_from: str, area_to: str, link_update_dto: LinkBaseDto) -> LinkDTO: + def update_link(self, study: RawStudy, area_from: str, area_to: str, link_update_dto: LinkBaseDTO) -> LinkDTO: link_dto = LinkDTO(area1=area_from, area2=area_to, **link_update_dto.model_dump()) link = link_dto.to_internal(StudyVersion.parse(study.version)) diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index 9038270d32..977ce69ee3 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -36,7 +36,7 @@ ] -class LinkBaseDto(AntaresBaseModel): +class LinkBaseDTO(AntaresBaseModel): model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") hurdles_cost: bool = False @@ -68,7 +68,7 @@ def validate_areas(self) -> t.Self: return self -class LinkDTO(Area, LinkBaseDto): +class LinkDTO(Area, LinkBaseDTO): def to_internal(self, version: StudyVersion) -> "LinkInternal": if version < STUDY_VERSION_8_2 and {"filter_synthesis", "filter_year_by_year"} & self.model_fields_set: raise LinkValidationError("Cannot specify a filter value for study's version earlier than v8.2") diff --git a/antarest/study/service.py b/antarest/study/service.py index f0d5074624..386173b788 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -89,7 +89,7 @@ from antarest.study.business.general_management import GeneralManager from antarest.study.business.link_management import LinkManager from antarest.study.business.matrix_management import MatrixManager, MatrixManagerError -from antarest.study.business.model.link_model import LinkBaseDto, LinkDTO +from antarest.study.business.model.link_model import LinkBaseDTO, LinkDTO from antarest.study.business.optimization_management import OptimizationManager from antarest.study.business.playlist_management import PlaylistManager from antarest.study.business.scenario_builder_management import ScenarioBuilderManager @@ -1910,7 +1910,7 @@ def update_link( uuid: str, area_from: str, area_to: str, - link_update_dto: LinkBaseDto, + link_update_dto: LinkBaseDTO, params: RequestParameters, ) -> LinkDTO: study = self.get_study(uuid) diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 1ea06a82c3..9f267df774 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -68,7 +68,7 @@ ) from antarest.study.business.district_manager import DistrictCreationDTO, DistrictInfoDTO, DistrictUpdateDTO from antarest.study.business.general_management import GeneralFormFields -from antarest.study.business.model.link_model import LinkBaseDto, LinkDTO +from antarest.study.business.model.link_model import LinkBaseDTO, LinkDTO from antarest.study.business.optimization_management import OptimizationFormFields from antarest.study.business.playlist_management import PlaylistColumns from antarest.study.business.scenario_builder_management import Rulesets, ScenarioType @@ -207,7 +207,7 @@ def update_link( uuid: str, area_from: str, area_to: str, - link_update_dto: LinkBaseDto, + link_update_dto: LinkBaseDTO, current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Any: logger.info(