← Back to team overview

sts-sponsors team mailing list archive

[Merge] ~ack/maas-site-manager:site-stats into maas-site-manager:main

 

Alberto Donato has proposed merging ~ack/maas-site-manager:site-stats into maas-site-manager:main.

Commit message:
add site stats to listing API

Requested reviews:
  MAAS Committers (maas-committers)

For more details, see:
https://code.launchpad.net/~ack/maas-site-manager/+git/site-manager/+merge/440941
-- 
Your team MAAS Committers is requested to review the proposed merge of ~ack/maas-site-manager:site-stats into maas-site-manager:main.
diff --git a/backend/msm/db/queries.py b/backend/msm/db/queries.py
index 222e2d3..3a35472 100644
--- a/backend/msm/db/queries.py
+++ b/backend/msm/db/queries.py
@@ -1,5 +1,3 @@
-from __future__ import annotations
-
 from collections.abc import Iterable
 from datetime import (
     datetime,
@@ -9,13 +7,14 @@ from typing import (
     Any,
     Sequence,
     Type,
-    TYPE_CHECKING,
     TypeVar,
 )
 from uuid import UUID
 
 from sqlalchemy import (
+    case,
     func,
+    Operators,
     Row,
     select,
     Table,
@@ -29,12 +28,10 @@ from ..schema import (
 )
 from ._tables import (
     Site,
+    SiteData,
     Token,
 )
 
-if TYPE_CHECKING:
-    from sqlalchemy import Operators
-
 
 def filters_from_arguments(
     table: Table,
@@ -131,8 +128,28 @@ async def get_filtered_sites(
             Site.c.street,
             Site.c.timezone,
             Site.c.url,
+            case(
+                (
+                    SiteData.c.site_id != None,  # noqa: E711
+                    func.json_build_object(
+                        "allocated_machines",
+                        SiteData.c.allocated_machines,
+                        "deployed_machines",
+                        SiteData.c.deployed_machines,
+                        "ready_machines",
+                        SiteData.c.ready_machines,
+                        "error_machines",
+                        SiteData.c.error_machines,
+                        "last_seen",
+                        SiteData.c.last_seen,
+                    ),
+                ),
+                else_=None,
+            ).label("stats"),
+        )
+        .select_from(
+            Site.join(SiteData, SiteData.c.site_id == Site.c.id, isouter=True)
         )
-        .select_from(Site)
         .limit(limit)
         .offset(offset)
     )
diff --git a/backend/msm/schema.py b/backend/msm/schema.py
index d57933f..69c4a66 100644
--- a/backend/msm/schema.py
+++ b/backend/msm/schema.py
@@ -61,20 +61,9 @@ class CreateSite(BaseModel):
     # TODO: we will need to add tags
 
 
-class Site(CreateSite):
-    """
-    Site persisted to the database
-    """
+class SiteData(BaseModel):
+    """Data for a site"""
 
-    id: int
-
-
-class CreateSiteData(BaseModel):
-    """
-    All SiteData is obligatory
-    """
-
-    site_id: int
     allocated_machines: int
     deployed_machines: int
     ready_machines: int
@@ -82,12 +71,19 @@ class CreateSiteData(BaseModel):
     last_seen: datetime
 
 
-class SiteData(CreateSiteData):
+class Site(CreateSite):
     """
-    SiteData persisted to the database
+    Site persisted to the database
     """
 
     id: int
+    stats: SiteData | None
+
+
+class CreateSiteData(SiteData):
+    """Site data"""
+
+    site_id: int
 
 
 class PaginatedSites(PaginatedResults):
diff --git a/backend/tests/user_api/test_handlers.py b/backend/tests/user_api/test_handlers.py
index f2cace2..0a60ee4 100644
--- a/backend/tests/user_api/test_handlers.py
+++ b/backend/tests/user_api/test_handlers.py
@@ -2,6 +2,7 @@ from datetime import (
     datetime,
     timedelta,
 )
+from typing import Any
 
 # from fastapi.testclient import TestClient
 from httpx import AsyncClient
@@ -51,6 +52,7 @@ async def test_list_sites(
         "longitude": "-0.118092",
         "note": "the first site",
         "region": "Blue Fin Bldg",
+        "stats": None,
         "street": "110 Southwark St",
         "timezone": "0.00",
         "url": "https://londoncalling.example.com";,
@@ -89,6 +91,51 @@ async def test_list_sites(
 
 
 @pytest.mark.asyncio
+async def test_list_sites_with_stats(
+    user_app_client: AsyncClient, fixture: Fixture
+) -> None:
+    site: dict[str, Any] = {
+        "name": "LondonHQ",
+        "city": "London",
+        "country": "gb",
+        "latitude": "51.509865",
+        "longitude": "-0.118092",
+        "note": "the first site",
+        "region": "Blue Fin Bldg",
+        "street": "110 Southwark St",
+        "timezone": "0.00",
+        "url": "https://londoncalling.example.com";,
+    }
+    await fixture.create("site", [site])
+    last_seen = datetime.utcnow()
+    site_data = {
+        "site_id": 1,
+        "allocated_machines": 10,
+        "deployed_machines": 20,
+        "ready_machines": 30,
+        "error_machines": 40,
+        "last_seen": last_seen,
+    }
+    await fixture.create("site_data", [site_data])
+    del site_data["site_id"]
+    site_data["last_seen"] = last_seen.isoformat()
+    site.update(
+        {
+            "id": 1,
+            "stats": site_data,
+        }
+    )
+    page = await user_app_client.get("/sites")
+    assert page.status_code == 200
+    assert page.json() == {
+        "page": 1,
+        "size": 20,
+        "total": 1,
+        "items": [site],
+    }
+
+
+@pytest.mark.asyncio
 @pytest.mark.parametrize("time_format", ["ISO 8601", "Float"])
 async def test_token_time_format(
     time_format: str, user_app_client: AsyncClient

Follow ups