MukeshKapoor25 commited on
Commit
88dcd96
·
1 Parent(s): 1519172

widget lib changes

Browse files
app/main.py CHANGED
@@ -22,6 +22,7 @@ from app.events.controllers.router import router as events_router
22
  from app.reports.controllers.router import router as reports_router
23
  from app.dashboard.controllers.router import router as dashboard_router
24
  from app.kpi_cache.controllers.router import router as kpi_cache_router
 
25
 
26
  # Init logging
27
  setup_logging(level=settings.LOG_LEVEL.strip().upper())
@@ -45,6 +46,9 @@ async def lifespan(app: FastAPI):
45
  )
46
  await kpi_col.create_index([("merchant_id", 1)], background=True, name="kpi_cache_merchant_id")
47
  await kpi_col.create_index([("expires_at", 1)], background=True, name="kpi_cache_expires_at")
 
 
 
48
  logger.info("Analytics Microservice started", extra={"event": "service_ready"})
49
  yield
50
  logger.info("Shutting down Analytics Microservice", extra={"event": "service_stopping"})
@@ -133,6 +137,7 @@ app.include_router(events_router)
133
  app.include_router(reports_router)
134
  app.include_router(dashboard_router)
135
  app.include_router(kpi_cache_router)
 
136
 
137
 
138
  @app.get("/health", tags=["health"])
 
22
  from app.reports.controllers.router import router as reports_router
23
  from app.dashboard.controllers.router import router as dashboard_router
24
  from app.kpi_cache.controllers.router import router as kpi_cache_router
25
+ from app.widget_collection.router import router as widget_collection_router
26
 
27
  # Init logging
28
  setup_logging(level=settings.LOG_LEVEL.strip().upper())
 
46
  )
47
  await kpi_col.create_index([("merchant_id", 1)], background=True, name="kpi_cache_merchant_id")
48
  await kpi_col.create_index([("expires_at", 1)], background=True, name="kpi_cache_expires_at")
49
+ # Ensure MongoDB index for analytics_widget_collection
50
+ wc_col = db["analytics_widget_collection"]
51
+ await wc_col.create_index("widget_id", unique=True, background=True, name="widget_id_unique")
52
  logger.info("Analytics Microservice started", extra={"event": "service_ready"})
53
  yield
54
  logger.info("Shutting down Analytics Microservice", extra={"event": "service_stopping"})
 
137
  app.include_router(reports_router)
138
  app.include_router(dashboard_router)
139
  app.include_router(kpi_cache_router)
140
+ app.include_router(widget_collection_router)
141
 
142
 
143
  @app.get("/health", tags=["health"])
app/widget_collection/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # widget_collection module
app/widget_collection/model.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MongoDB document model for analytics_widget_collection."""
2
+ from typing import Any, Dict, Optional
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class DataConfig(BaseModel):
7
+ source: str
8
+ params: Dict[str, Any] = {}
9
+
10
+
11
+ class WidgetCollectionDocument(BaseModel):
12
+ widget_id: str
13
+ type: str = "kpi"
14
+ title: str
15
+ category: str
16
+ unit: Optional[str] = None
17
+ description: Optional[str] = None
18
+ drill_down_url: Optional[str] = None
19
+ data_config: DataConfig
20
+
21
+ class Config:
22
+ populate_by_name = True
app/widget_collection/router.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """FastAPI router for widget_collection endpoints."""
2
+ from fastapi import APIRouter, Depends, HTTPException, status
3
+
4
+ from app.core.logging import get_logger
5
+ from app.dependencies.auth import get_current_user, TokenUser
6
+ from app.dependencies.kpi_permissions import require_dashboard_view
7
+ from app.widget_collection.schemas import ListWidgetCollectionRequest, UpdateWidgetRolesRequest
8
+ from app.widget_collection.service import WidgetCollectionService
9
+
10
+ logger = get_logger(__name__)
11
+
12
+ router = APIRouter(prefix="/widget-collection", tags=["Widget Collection"])
13
+
14
+
15
+ @router.post("/list")
16
+ async def list_widgets(
17
+ payload: ListWidgetCollectionRequest,
18
+ current_user: TokenUser = Depends(require_dashboard_view),
19
+ ):
20
+ """
21
+ List widget collection documents with optional projection_list.
22
+ Requires: permissions.dashboard.view
23
+ """
24
+ filters: dict = {}
25
+ if payload.filters:
26
+ if payload.filters.category:
27
+ filters["category"] = payload.filters.category
28
+ if payload.filters.type:
29
+ filters["type"] = payload.filters.type
30
+
31
+ docs = await WidgetCollectionService.list_widgets(
32
+ filters=filters,
33
+ skip=payload.skip,
34
+ limit=payload.limit,
35
+ projection_list=payload.projection_list,
36
+ )
37
+ return {"success": True, "data": docs, "count": len(docs)}
38
+
39
+
40
+ @router.get("/{widget_id}")
41
+ async def get_widget(
42
+ widget_id: str,
43
+ current_user: TokenUser = Depends(require_dashboard_view),
44
+ ):
45
+ """Fetch a single widget config document by widget_id."""
46
+ doc = await WidgetCollectionService.get_widget(widget_id)
47
+ if not doc:
48
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Widget not found")
49
+ return {"success": True, "data": doc}
50
+
51
+
52
+ @router.put("/{widget_id}")
53
+ async def update_widget(
54
+ widget_id: str,
55
+ payload: UpdateWidgetRolesRequest,
56
+ current_user: TokenUser = Depends(require_dashboard_view),
57
+ ):
58
+ """Update data_config for a widget."""
59
+ update_data: dict = {}
60
+ if payload.data_config is not None:
61
+ update_data["data_config"] = payload.data_config
62
+
63
+ if not update_data:
64
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Nothing to update")
65
+
66
+ updated = await WidgetCollectionService.update_widget(widget_id, update_data)
67
+ if not updated:
68
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Widget not found")
69
+
70
+ return {"success": True, "message": "Widget updated"}
app/widget_collection/schemas.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Request/response schemas for widget_collection endpoints."""
2
+ from typing import Any, Dict, List, Optional
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class WidgetCollectionFilters(BaseModel):
7
+ category: Optional[str] = None
8
+ type: Optional[str] = None
9
+
10
+
11
+ class ListWidgetCollectionRequest(BaseModel):
12
+ filters: Optional[WidgetCollectionFilters] = None
13
+ skip: int = Field(0, ge=0)
14
+ limit: int = Field(100, ge=1, le=500)
15
+ projection_list: Optional[List[str]] = Field(
16
+ None,
17
+ description="List of fields to include in response"
18
+ )
19
+
20
+
21
+ class UpdateWidgetRolesRequest(BaseModel):
22
+ data_config: Optional[Dict[str, Any]] = None
app/widget_collection/service.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CRUD service for analytics_widget_collection MongoDB collection."""
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ from app.nosql import get_database
5
+ from app.core.logging import get_logger
6
+
7
+ logger = get_logger(__name__)
8
+
9
+ COLLECTION = "analytics_widget_collection"
10
+
11
+
12
+ class WidgetCollectionService:
13
+
14
+ @staticmethod
15
+ async def list_widgets(
16
+ filters: Dict[str, Any],
17
+ skip: int = 0,
18
+ limit: int = 100,
19
+ projection_list: Optional[List[str]] = None,
20
+ ) -> List[Any]:
21
+ db = get_database()
22
+ col = db[COLLECTION]
23
+
24
+ projection: Optional[Dict[str, int]] = None
25
+ if projection_list:
26
+ projection = {field: 1 for field in projection_list}
27
+ projection["_id"] = 0
28
+
29
+ cursor = col.find(filters, projection).skip(skip).limit(limit)
30
+ docs = await cursor.to_list(length=limit)
31
+
32
+ if projection_list:
33
+ return docs
34
+
35
+ # Strip MongoDB _id for clean output
36
+ for doc in docs:
37
+ doc.pop("_id", None)
38
+ return docs
39
+
40
+ @staticmethod
41
+ async def get_widget(widget_id: str) -> Optional[Dict[str, Any]]:
42
+ db = get_database()
43
+ col = db[COLLECTION]
44
+ doc = await col.find_one({"widget_id": widget_id}, {"_id": 0})
45
+ return doc
46
+
47
+ @staticmethod
48
+ async def update_widget(widget_id: str, update_data: Dict[str, Any]) -> bool:
49
+ db = get_database()
50
+ col = db[COLLECTION]
51
+ result = await col.update_one({"widget_id": widget_id}, {"$set": update_data})
52
+ return result.matched_count > 0
53
+
54
+ @staticmethod
55
+ async def upsert_widget(doc: Dict[str, Any]) -> None:
56
+ db = get_database()
57
+ col = db[COLLECTION]
58
+ await col.update_one(
59
+ {"widget_id": doc["widget_id"]},
60
+ {"$set": doc},
61
+ upsert=True,
62
+ )
scripts/seed_widget_collection.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Seed script — populates analytics_widget_collection in MongoDB.
3
+
4
+ Derives all documents from KPI_WIDGET_REGISTRY (single source of truth).
5
+ Run from the analytics-ms root:
6
+ python -m scripts.seed_widget_collection
7
+ """
8
+ import asyncio
9
+ import sys
10
+ import os
11
+
12
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
13
+
14
+ from app.core.config import settings # noqa: E402
15
+ from app.nosql import connect_to_mongo, close_mongo_connection, get_database # noqa: E402
16
+ from app.kpi_cache.constants import KPI_WIDGET_REGISTRY # noqa: E402
17
+
18
+ COLLECTION = "dashboard_widgets"
19
+
20
+
21
+ def _build_document(widget_id: str, meta: dict) -> dict:
22
+ return {
23
+ "widget_id": widget_id,
24
+ "type": "kpi",
25
+ "title": meta.get("title", widget_id),
26
+ "category": meta.get("category", ""),
27
+ "unit": meta.get("unit"),
28
+ "description": meta.get("description"),
29
+ "drill_down_url": meta.get("drill_down_url"),
30
+ "data_config": {
31
+ "source": "merchant_kpi_stats",
32
+ "params": {
33
+ "widget": widget_id,
34
+ "period_window": "mtd",
35
+ "unit": meta.get("unit", "count"),
36
+ },
37
+ },
38
+ }
39
+
40
+
41
+ async def seed():
42
+ await connect_to_mongo()
43
+ db = get_database()
44
+ col = db[COLLECTION]
45
+
46
+ # Ensure unique index on widget_id
47
+ await col.create_index("widget_id", unique=True, background=True, name="widget_id_unique")
48
+
49
+ docs = [_build_document(wid, meta) for wid, meta in KPI_WIDGET_REGISTRY.items()]
50
+
51
+ upserted = 0
52
+ for doc in docs:
53
+ result = await col.update_one(
54
+ {"widget_id": doc["widget_id"]},
55
+ {"$set": doc},
56
+ upsert=True,
57
+ )
58
+ if result.upserted_id or result.modified_count:
59
+ upserted += 1
60
+
61
+ print(f"Seeded {upserted}/{len(docs)} widget collection documents into '{COLLECTION}'.")
62
+ await close_mongo_connection()
63
+
64
+
65
+ if __name__ == "__main__":
66
+ asyncio.run(seed())