diff --git a/antarest/__init__.py b/antarest/__init__.py
index f8b3bd0418..ed494cf4c5 100644
--- a/antarest/__init__.py
+++ b/antarest/__init__.py
@@ -19,9 +19,9 @@
# Standard project metadata
-__version__ = "2.18.0"
+__version__ = "2.18.1"
__author__ = "RTE, Antares Web Team"
-__date__ = "2024-11-29"
+__date__ = "2024-12-02"
# noinspection SpellCheckingInspection
__credits__ = "(c) Réseau de Transport de l’Électricité (RTE)"
diff --git a/antarest/eventbus/business/redis_eventbus.py b/antarest/eventbus/business/redis_eventbus.py
index f3642bf994..8bbf6cbc38 100644
--- a/antarest/eventbus/business/redis_eventbus.py
+++ b/antarest/eventbus/business/redis_eventbus.py
@@ -20,6 +20,7 @@
logger = logging.getLogger(__name__)
REDIS_STORE_KEY = "events"
+MAX_EVENTS_LIST_SIZE = 1000
class RedisEventBus(IEventBusBackend):
@@ -41,14 +42,23 @@ def pull_queue(self, queue: str) -> Optional[Event]:
return None
def get_events(self) -> List[Event]:
+ messages = []
try:
- event = self.pubsub.get_message(ignore_subscribe_messages=True)
- if event is not None:
- return [Event.parse_raw(event["data"])]
+ while msg := self.pubsub.get_message(ignore_subscribe_messages=True):
+ messages.append(msg)
+ if len(messages) >= MAX_EVENTS_LIST_SIZE:
+ break
except Exception:
- logger.error("Failed to retrieve or parse event !", exc_info=True)
+ logger.error("Failed to retrieve events !", exc_info=True)
- return []
+ events = []
+ for msg in messages:
+ try:
+ events.append(Event.model_validate_json(msg["data"]))
+ except Exception:
+ logger.error(f"Failed to parse event ! {msg}", exc_info=True)
+
+ return events
def clear_events(self) -> None:
# Nothing to do
diff --git a/antarest/eventbus/service.py b/antarest/eventbus/service.py
index 201efb3be6..eadf75e8c8 100644
--- a/antarest/eventbus/service.py
+++ b/antarest/eventbus/service.py
@@ -24,6 +24,9 @@
logger = logging.getLogger(__name__)
+EVENT_LOOP_REST_TIME = 0.2
+
+
class EventBusService(IEventBus):
def __init__(self, backend: IEventBusBackend, autostart: bool = True) -> None:
self.backend = backend
@@ -76,18 +79,22 @@ def remove_listener(self, listener_id: str) -> None:
async def _run_loop(self) -> None:
while True:
- time.sleep(0.2)
try:
- await self._on_events()
+ processed_events_count = await self._on_events()
+ # Give the loop some rest if it has nothing to do
+ if processed_events_count == 0:
+ await asyncio.sleep(EVENT_LOOP_REST_TIME)
except Exception as e:
logger.error("Unexpected error when processing events", exc_info=e)
- async def _on_events(self) -> None:
+ async def _on_events(self) -> int:
+ processed_events_count = 0
with self.lock:
for queue in self.consumers:
if len(self.consumers[queue]) > 0:
event = self.backend.pull_queue(queue)
while event is not None:
+ processed_events_count += 1
try:
await list(self.consumers[queue].values())[
random.randint(0, len(self.consumers[queue]) - 1)
@@ -99,7 +106,9 @@ async def _on_events(self) -> None:
)
event = self.backend.pull_queue(queue)
- for e in self.backend.get_events():
+ events = self.backend.get_events()
+ processed_events_count += len(events)
+ for e in events:
if e.type in self.listeners:
responses = await asyncio.gather(
*[
@@ -115,6 +124,7 @@ async def _on_events(self) -> None:
exc_info=res,
)
self.backend.clear_events()
+ return processed_events_count
def _async_loop(self, new_loop: bool = True) -> None:
loop = asyncio.new_event_loop() if new_loop else asyncio.get_event_loop()
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 00c33a8f5a..2a35203f73 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,8 +1,22 @@
Antares Web Changelog
=====================
+v2.18.1 (2024-12-02)
+--------------------
+
+## What's Changed
+
+### Bug Fixes
+
+* **ui-tablemode**: style missing [`2257`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/2257)
+* **ui-studies**: multiple API calls on study list view [`2258`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/2258)
+* **events**: avoid slow processing of events [`2259`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/2259)
+
+**Full Changelog**: https://github.com/AntaresSimulatorTeam/AntaREST/compare/v2.18.0...v2.18.1
+
+
v2.18.0 (2024-11-29)
--------------------
+--------------------
## What's Changed
diff --git a/pyproject.toml b/pyproject.toml
index 5e77bb439e..00613c0cc4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ requires = ["setuptools"]
[project]
name = "AntaREST"
-version = "2.18.0"
+version = "2.18.1"
authors = [{name="RTE, Antares Web Team", email="andrea.sgattoni@rte-france.com" }]
description="Antares Server"
readme = {file = "README.md", content-type = "text/markdown"}
diff --git a/resources/application.yaml b/resources/application.yaml
index c962d09015..21ec482177 100644
--- a/resources/application.yaml
+++ b/resources/application.yaml
@@ -52,3 +52,9 @@ logging:
# True to get sqlalchemy logs
debug: False
+
+# Uncomment these lines to use redis as a backend for the eventbus
+# It is required to use redis when using this application on multiple workers in a preforked model like gunicorn for instance
+#redis:
+# host: localhost
+# port: 6379
diff --git a/sonar-project.properties b/sonar-project.properties
index c0897c1d14..92b73ee890 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -6,5 +6,5 @@ sonar.exclusions=antarest/gui.py,antarest/main.py
sonar.python.coverage.reportPaths=coverage.xml
sonar.python.version=3.11
sonar.javascript.lcov.reportPaths=webapp/coverage/lcov.info
-sonar.projectVersion=2.18.0
+sonar.projectVersion=2.18.1
sonar.coverage.exclusions=antarest/gui.py,antarest/main.py,antarest/singleton_services.py,antarest/worker/archive_worker_service.py,webapp/**/*,,antarest/fastapi_jwt_auth/**
\ No newline at end of file
diff --git a/tests/eventbus/test_redis_event_bus.py b/tests/eventbus/test_redis_event_bus.py
index 37f69e1fad..fa8722f742 100644
--- a/tests/eventbus/test_redis_event_bus.py
+++ b/tests/eventbus/test_redis_event_bus.py
@@ -29,8 +29,9 @@ def test_lifecycle():
payload="foo",
permissions=PermissionInfo(public_mode=PublicMode.READ),
)
+
serialized = event.model_dump_json()
- pubsub_mock.get_message.return_value = {"data": serialized}
+ pubsub_mock.get_message = Mock(side_effect=[{"data": serialized}, {"data": serialized}, None])
eventbus.push_event(event)
redis_client.publish.assert_called_once_with("events", serialized)
- assert eventbus.get_events() == [event]
+ assert eventbus.get_events() == [event, event]
diff --git a/webapp/package-lock.json b/webapp/package-lock.json
index a8fd1acc16..3ed8e546ee 100644
--- a/webapp/package-lock.json
+++ b/webapp/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "antares-web",
- "version": "2.18.0",
+ "version": "2.18.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "antares-web",
- "version": "2.18.0",
+ "version": "2.18.1",
"dependencies": {
"@emotion/react": "11.13.3",
"@emotion/styled": "11.13.0",
diff --git a/webapp/package.json b/webapp/package.json
index a5b9d9dca6..1abf973a65 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -1,6 +1,6 @@
{
"name": "antares-web",
- "version": "2.18.0",
+ "version": "2.18.1",
"private": true,
"type": "module",
"scripts": {
diff --git a/webapp/src/components/App/Studies/StudyCard/index.tsx b/webapp/src/components/App/Studies/StudyCard/index.tsx
index cb74b58819..89813e6271 100644
--- a/webapp/src/components/App/Studies/StudyCard/index.tsx
+++ b/webapp/src/components/App/Studies/StudyCard/index.tsx
@@ -405,30 +405,27 @@ const StudyCard = memo((props: Props) => {
setOpenDialog={setOpenDialog}
/>
-
-
- {t("studies.question.delete")}
-
-
-
+ {/* Keep conditional rendering for dialogs and not use only `open` property, because API calls are made on mount */}
+ {openDialog === "properties" && (
+
+ )}
+ {openDialog === "delete" && (
+
+ {t("studies.question.delete")}
+
+ )}
+ {openDialog === "export" && (
+
+ )}
+ {openDialog === "move" && (
+
+ )}
);
}, areEqual);
diff --git a/webapp/src/components/common/Handsontable.tsx b/webapp/src/components/common/Handsontable.tsx
index 0e6d6c3f8a..89f3ef7c0f 100644
--- a/webapp/src/components/common/Handsontable.tsx
+++ b/webapp/src/components/common/Handsontable.tsx
@@ -18,6 +18,7 @@ import { styled } from "@mui/material";
import { forwardRef } from "react";
import * as RA from "ramda-adjunct";
import { SECONDARY_MAIN_COLOR } from "../../theme";
+import "handsontable/dist/handsontable.min.css";
// Register Handsontable's modules
registerAllModules();