Merge pull request 'feature/wc' (#51) from feature/wc into feature/icecheng/metrics
Reviewed-on: freeleaps/freeleaps-service-hub#51
This commit is contained in:
commit
806778a5b9
@ -10,9 +10,28 @@ COPY requirements.txt .
|
|||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy environment file
|
||||||
|
COPY local.env .
|
||||||
|
|
||||||
# Copy application code
|
# Copy application code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
ENV MONGODB_NAME = "freeleaps2"
|
||||||
|
ENV MONGODB_URI = "mongodb://freeleaps2-mongodb:27017"
|
||||||
|
|
||||||
|
#app_settings
|
||||||
|
ENV GITEA_TOKEN = ""
|
||||||
|
ENV GITEA_URL = ""
|
||||||
|
ENV GITEA_DEPOT_ORGANIZATION = ""
|
||||||
|
ENV CODE_DEPOT_HTTP_PORT = ""
|
||||||
|
ENV CODE_DEPOT_SSH_PORT = ""
|
||||||
|
ENV CODE_DEPOT_DOMAIN_NAME = ""
|
||||||
|
|
||||||
|
#log_settings
|
||||||
|
ENV LOG_BASE_PATH = "./logs"
|
||||||
|
ENV BACKEND_LOG_FILE_NAME = "freeleaps-metrics"
|
||||||
|
ENV APPLICATION_ACTIVITY_LOG = "freeleaps-metrics-activity"
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 8009
|
EXPOSE 8009
|
||||||
|
|
||||||
|
|||||||
@ -1,35 +1,28 @@
|
|||||||
# 📊 Metrics Service
|
# 📊 Metrics Service
|
||||||
|
|
||||||
> A lightweight FastAPI microservice for user registration analytics and statistics
|
> A lightweight FastAPI microservice for user registration analytics and metrics
|
||||||
|
|
||||||
[](https://python.org)
|
[](https://python.org)
|
||||||
[](https://fastapi.tiangolo.com)
|
[](https://fastapi.tiangolo.com)
|
||||||
[](https://docker.com)
|
[](https://docker.com)
|
||||||
|
|
||||||
The Metrics service provides real-time APIs for querying user registration data from StarRocks database, offering flexible analytics and insights into user growth patterns.
|
The Metrics service provides real-time APIs for querying user registration data (via StarRocks) and querying monitoring metrics (via Prometheus).
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
### 📊 User Registration Statistics APIs
|
### 📊 Registration Analytics (StarRocks)
|
||||||
- **Date Range Query** - Query registration data for specific date ranges
|
- Date Range Query (start_date ~ end_date)
|
||||||
- **Recent N Days Query** - Get registration data for the last N days
|
- Typed responses with Pydantic models
|
||||||
- **Start Date + Days Query** - Query N days starting from a specified date
|
|
||||||
- **Statistics Summary** - Get comprehensive statistics and analytics
|
|
||||||
- **POST Method Support** - JSON request body support for complex queries
|
|
||||||
|
|
||||||
### 🗄️ Database Integration
|
### 📈 Prometheus Metrics
|
||||||
- **StarRocks Database Connection**
|
- Predefined PromQL metrics per product
|
||||||
- Host: `freeleaps-starrocks-cluster-fe-service.freeleaps-data-platform.svc`
|
- Time-range queries and metric info discovery
|
||||||
- Port: `9030`
|
|
||||||
- Database: `freeleaps`
|
|
||||||
- Table: `dws_daily_registered_users`
|
|
||||||
|
|
||||||
### 🔧 Technical Features
|
### 🔧 Technical Features
|
||||||
- **Data Models**: Pydantic validation for data integrity
|
- Data Models: Pydantic v2
|
||||||
- **Connection Management**: Automatic database connection and disconnection
|
- Async HTTP and robust error handling
|
||||||
- **Error Handling**: Comprehensive exception handling with user-friendly error messages
|
- Structured logging
|
||||||
- **Logging**: Structured logging using Loguru
|
- Auto-generated Swagger/OpenAPI docs
|
||||||
- **API Documentation**: Auto-generated Swagger/OpenAPI documentation
|
|
||||||
|
|
||||||
## 📁 Project Structure
|
## 📁 Project Structure
|
||||||
|
|
||||||
@ -37,31 +30,43 @@ The Metrics service provides real-time APIs for querying user registration data
|
|||||||
metrics/
|
metrics/
|
||||||
├── backend/ # Business logic layer
|
├── backend/ # Business logic layer
|
||||||
│ ├── infra/ # Infrastructure components
|
│ ├── infra/ # Infrastructure components
|
||||||
│ │ └── database_client.py
|
│ │ └── external_service/
|
||||||
|
│ │ ├── prometheus_client.py
|
||||||
|
│ │ └── starrocks_client.py
|
||||||
│ ├── models/ # Data models
|
│ ├── models/ # Data models
|
||||||
│ │ └── user_registration_models.py
|
│ │ └── user_registration_models.py
|
||||||
│ └── services/ # Business services
|
│ └── services/ # Business services
|
||||||
|
│ ├── prometheus_metrics_service.py
|
||||||
│ └── registration_analytics_service.py
|
│ └── registration_analytics_service.py
|
||||||
├── webapi/ # API layer
|
├── webapi/ # API layer
|
||||||
│ ├── routes/ # API endpoints
|
│ ├── routes/ # API endpoints
|
||||||
│ │ └── registration_metrics.py
|
│ │ ├── metrics/ # Aggregated routers (prefix: /api/metrics)
|
||||||
│ ├── config/ # Configuration
|
│ │ ├── prometheus_metrics/
|
||||||
│ │ └── app_settings.py
|
│ │ │ ├── __init__.py
|
||||||
│ ├── bootstrap/ # App initialization
|
│ │ │ ├── available_metrics.py
|
||||||
│ │ └── app_factory.py
|
│ │ │ ├── metric_info.py
|
||||||
│ └── main.py # FastAPI app entry point
|
│ │ │ └── metrics_query.py
|
||||||
├── common/ # Shared utilities
|
│ │ └── starrocks_metrics/
|
||||||
├── requirements.txt # Dependencies
|
│ │ ├── __init__.py
|
||||||
├── Dockerfile # Container config
|
│ │ ├── available_metrics.py
|
||||||
├── local.env # Environment variables
|
│ │ ├── metric_info.py
|
||||||
└── README.md # Documentation
|
│ │ └── metrics_query.py
|
||||||
|
│ ├── bootstrap/
|
||||||
|
│ │ └── application.py
|
||||||
|
│ └── main.py
|
||||||
|
├── common/
|
||||||
|
├── requirements.txt
|
||||||
|
├── Dockerfile
|
||||||
|
├── local.env
|
||||||
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
- Python 3.12+ or Docker
|
- Python 3.12+ or Docker
|
||||||
- Access to StarRocks database
|
- Access to StarRocks database (for registration analytics)
|
||||||
|
- Access to Prometheus (for monitoring metrics)
|
||||||
|
|
||||||
### 🐍 Python Setup
|
### 🐍 Python Setup
|
||||||
|
|
||||||
@ -79,8 +84,12 @@ python3 -m uvicorn webapi.main:app --host 0.0.0.0 --port 8009 --reload
|
|||||||
# 1. Build image
|
# 1. Build image
|
||||||
docker build -t metrics:latest .
|
docker build -t metrics:latest .
|
||||||
|
|
||||||
# 2. Run container
|
# 2. Run container (env from baked local.env)
|
||||||
docker run --rm -p 8009:8009 metrics:latest
|
docker run --rm -p 8009:8009 metrics:latest
|
||||||
|
|
||||||
|
# Alternatively: run with a custom env file
|
||||||
|
# (this overrides envs copied into the image)
|
||||||
|
docker run --rm -p 8009:8009 --env-file local.env metrics:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### 📖 Access Documentation
|
### 📖 Access Documentation
|
||||||
@ -88,69 +97,36 @@ Visit `http://localhost:8009/docs` for interactive API documentation.
|
|||||||
|
|
||||||
## 📊 API Endpoints
|
## 📊 API Endpoints
|
||||||
|
|
||||||
| Endpoint | Method | Description |
|
All endpoints are exposed under the API base prefix `/api/metrics`.
|
||||||
|----------|--------|-------------|
|
|
||||||
| `/api/metrics/daily-registered-users` | GET/POST | Query registration data by date range |
|
|
||||||
| `/api/metrics/recent-registered-users` | GET | Get recent N days data |
|
|
||||||
| `/api/metrics/registered-users-by-days` | GET | Query N days from start date |
|
|
||||||
| `/api/metrics/registration-summary` | GET | Get statistical summary |
|
|
||||||
|
|
||||||
### Example Requests
|
### StarRocks (Registration Analytics)
|
||||||
|
- POST `/api/metrics/starrocks/dru_query` — Query daily registered users time series for a date range
|
||||||
|
- GET `/api/metrics/starrocks/product/{product_id}/available-metrics` — List available StarRocks-backed metrics for the product
|
||||||
|
- GET `/api/metrics/starrocks/product/{product_id}/metric/{metric_name}/info` — Get metric info for the product
|
||||||
|
|
||||||
|
Example request:
|
||||||
```bash
|
```bash
|
||||||
# Get last 7 days
|
curl -X POST "http://localhost:8009/api/metrics/starrocks/dru_query" \
|
||||||
curl "http://localhost:8009/api/metrics/recent-registered-users?days=7"
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
# Get date range
|
"product_id": "freeleaps",
|
||||||
curl "http://localhost:8009/api/metrics/daily-registered-users?start_date=2024-09-10&end_date=2024-09-20"
|
"start_date": "2024-09-10",
|
||||||
|
"end_date": "2024-09-20"
|
||||||
# Get summary statistics
|
}'
|
||||||
curl "http://localhost:8009/api/metrics/registration-summary?start_date=2024-09-10&end_date=2024-09-20"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Parameters
|
### Prometheus
|
||||||
- `start_date` / `end_date`: Date in `YYYY-MM-DD` format
|
- POST `/api/metrics/prometheus/metrics_query` — Query metric time series by product/metric
|
||||||
- `days`: Number of days (max: 365)
|
- GET `/api/metrics/prometheus/product/{product_id}/available-metrics` — List available metrics for product
|
||||||
- `product_id`: Product identifier (default: "freeleaps")
|
- GET `/api/metrics/prometheus/product/{product_id}/metric/{metric_name}/info` — Get metric info
|
||||||
|
|
||||||
## 📈 Response Format
|
|
||||||
|
|
||||||
### Standard Response
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dates": ["2024-09-10", "2024-09-11", "2024-09-12"],
|
|
||||||
"counts": [39, 38, 31],
|
|
||||||
"total_registrations": 108,
|
|
||||||
"query_period": "2024-09-10 to 2024-09-12"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Summary Response
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"total_registrations": 282,
|
|
||||||
"average_daily": 25.64,
|
|
||||||
"max_daily": 39,
|
|
||||||
"min_daily": 8,
|
|
||||||
"days_with_registrations": 10,
|
|
||||||
"total_days": 11
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🧪 Testing
|
## 🧪 Testing
|
||||||
|
|
||||||
### Quick Test
|
|
||||||
```bash
|
```bash
|
||||||
# Health check
|
# Health check
|
||||||
curl http://localhost:8009/
|
curl http://localhost:8009/
|
||||||
|
|
||||||
# Test recent registrations
|
|
||||||
curl "http://localhost:8009/api/metrics/recent-registered-users?days=7"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Interactive Testing
|
|
||||||
Visit `http://localhost:8009/docs` for the Swagger UI interface where you can test all endpoints directly.
|
|
||||||
|
|
||||||
## ⚙️ Configuration
|
## ⚙️ Configuration
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
@ -170,9 +146,12 @@ STARROCKS_DATABASE=freeleaps
|
|||||||
LOG_BASE_PATH=./logs
|
LOG_BASE_PATH=./logs
|
||||||
BACKEND_LOG_FILE_NAME=metrics
|
BACKEND_LOG_FILE_NAME=metrics
|
||||||
APPLICATION_ACTIVITY_LOG=metrics-activity
|
APPLICATION_ACTIVITY_LOG=metrics-activity
|
||||||
|
|
||||||
|
# Prometheus
|
||||||
|
PROMETHEUS_ENDPOINT=http://localhost:9090
|
||||||
```
|
```
|
||||||
|
|
||||||
> 💡 **Tip**: Copy `local.env` to `.env` and modify as needed for your environment.
|
> Tip: Copy `local.env` to `.env` and modify as needed for your environment.
|
||||||
|
|
||||||
### 🐳 Docker Deployment
|
### 🐳 Docker Deployment
|
||||||
|
|
||||||
@ -206,20 +185,16 @@ python -m uvicorn webapi.main:app --reload
|
|||||||
```
|
```
|
||||||
|
|
||||||
## 📝 API Documentation
|
## 📝 API Documentation
|
||||||
|
- Swagger UI: `http://localhost:8009/docs`
|
||||||
- **Swagger UI**: `http://localhost:8009/docs`
|
- ReDoc: `http://localhost:8009/redoc`
|
||||||
- **ReDoc**: `http://localhost:8009/redoc`
|
- OpenAPI JSON: `http://localhost:8009/openapi.json`
|
||||||
- **OpenAPI JSON**: `http://localhost:8009/openapi.json`
|
|
||||||
|
|
||||||
## ⚠️ Important Notes
|
## ⚠️ Important Notes
|
||||||
|
- Date format: `YYYY-MM-DD` (single-digit month/day also accepted by API)
|
||||||
- Date format: `YYYY-MM-DD`
|
|
||||||
- Max query range: 365 days
|
|
||||||
- Default `product_id`: "freeleaps"
|
- Default `product_id`: "freeleaps"
|
||||||
- Requires StarRocks database access
|
- Requires StarRocks database access and/or Prometheus endpoint
|
||||||
|
|
||||||
## 🐛 Troubleshooting
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
| Issue | Solution |
|
| Issue | Solution |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| Port in use | `docker stop $(docker ps -q --filter ancestor=metrics:latest)` |
|
| Port in use | `docker stop $(docker ps -q --filter ancestor=metrics:latest)` |
|
||||||
|
|||||||
@ -88,12 +88,3 @@ class StarRocksClient:
|
|||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
"""Context manager exit"""
|
"""Context manager exit"""
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
from typing import List, Dict, Any
|
from typing import List, Dict, Any
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from backend.infra.database_client import StarRocksClient
|
from backend.infra.external_service.starrocks_client import StarRocksClient
|
||||||
from backend.models.user_registration_models import UserRegistrationResponse, DailyRegisteredUsers
|
from backend.models.user_registration_models import UserRegistrationResponse, DailyRegisteredUsers
|
||||||
|
|
||||||
|
|
||||||
@ -33,7 +33,6 @@ class RegistrationService:
|
|||||||
raw_data = self.starrocks_client.get_daily_registered_users(
|
raw_data = self.starrocks_client.get_daily_registered_users(
|
||||||
start_date, end_date, product_id
|
start_date, end_date, product_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert to DailyRegisteredUsers objects
|
# Convert to DailyRegisteredUsers objects
|
||||||
daily_data = [
|
daily_data = [
|
||||||
DailyRegisteredUsers(
|
DailyRegisteredUsers(
|
||||||
@ -44,7 +43,6 @@ class RegistrationService:
|
|||||||
)
|
)
|
||||||
for row in raw_data
|
for row in raw_data
|
||||||
]
|
]
|
||||||
|
|
||||||
# Create date-to-count mapping
|
# Create date-to-count mapping
|
||||||
data_dict = {str(item.date_id): item.registered_cnt for item in daily_data}
|
data_dict = {str(item.date_id): item.registered_cnt for item in daily_data}
|
||||||
|
|
||||||
|
|||||||
@ -14,3 +14,4 @@ pytest==8.4.1
|
|||||||
pytest-asyncio==0.21.2
|
pytest-asyncio==0.21.2
|
||||||
pymysql==1.1.0
|
pymysql==1.1.0
|
||||||
sqlalchemy==2.0.23
|
sqlalchemy==2.0.23
|
||||||
|
python-dotenv
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from webapi.routes.metrics.registration_metrics import router as registration_router
|
from webapi.routes.starrocks_metrics import api_router as starrocks_metrics_router
|
||||||
from webapi.routes.prometheus_metrics import api_router as prometheus_metrics_router
|
from webapi.routes.prometheus_metrics import api_router as prometheus_metrics_router
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
router.include_router(registration_router, prefix="/metrics", tags=["registration-metrics"])
|
router.include_router(starrocks_metrics_router, prefix="/metrics", tags=["starrocks-metrics"])
|
||||||
router.include_router(prometheus_metrics_router, prefix="/metrics", tags=["prometheus-metrics"])
|
router.include_router(prometheus_metrics_router, prefix="/metrics", tags=["prometheus-metrics"])
|
||||||
|
|||||||
@ -1,229 +0,0 @@
|
|||||||
from fastapi import APIRouter, HTTPException, Query
|
|
||||||
from datetime import date, datetime, timedelta
|
|
||||||
from typing import Optional
|
|
||||||
from loguru import logger
|
|
||||||
from backend.services.registration_analytics_service import RegistrationService
|
|
||||||
from backend.models.user_registration_models import UserRegistrationResponse, UserRegistrationQuery
|
|
||||||
|
|
||||||
router = APIRouter(tags=["registration"])
|
|
||||||
|
|
||||||
# Initialize service
|
|
||||||
registration_service = RegistrationService()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/daily-registered-users", response_model=UserRegistrationResponse)
|
|
||||||
async def get_daily_registered_users(
|
|
||||||
start_date: date = Query(..., description="Start date in YYYY-MM-DD format"),
|
|
||||||
end_date: date = Query(..., description="End date in YYYY-MM-DD format"),
|
|
||||||
product_id: str = Query("freeleaps", description="Product identifier")
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Get daily registered users count for a date range
|
|
||||||
|
|
||||||
Returns two lists:
|
|
||||||
- dates: List of dates in YYYY-MM-DD format
|
|
||||||
- counts: List of daily registration counts
|
|
||||||
|
|
||||||
Example:
|
|
||||||
- GET /api/metrics/daily-registered-users?start_date=2024-01-01&end_date=2024-01-07
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Validate date range
|
|
||||||
if start_date > end_date:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail="Start date must be before or equal to end date"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check date range is not too large (max 1 year)
|
|
||||||
if (end_date - start_date).days > 365:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail="Date range cannot exceed 365 days"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Querying registration data from {start_date} to {end_date} for product {product_id}")
|
|
||||||
|
|
||||||
# Get data from service
|
|
||||||
result = registration_service.get_daily_registered_users(
|
|
||||||
start_date, end_date, product_id
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Successfully retrieved data for {len(result.dates)} days")
|
|
||||||
return result
|
|
||||||
|
|
||||||
except HTTPException:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to get daily registered users: {e}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=500,
|
|
||||||
detail=f"Internal server error: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/registration-summary")
|
|
||||||
async def get_registration_summary(
|
|
||||||
start_date: date = Query(..., description="Start date in YYYY-MM-DD format"),
|
|
||||||
end_date: date = Query(..., description="End date in YYYY-MM-DD format"),
|
|
||||||
product_id: str = Query("freeleaps", description="Product identifier")
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Get summary statistics for user registrations in a date range
|
|
||||||
|
|
||||||
Returns summary statistics including:
|
|
||||||
- total_registrations: Total number of registrations
|
|
||||||
- average_daily: Average daily registrations
|
|
||||||
- max_daily: Maximum daily registrations
|
|
||||||
- min_daily: Minimum daily registrations
|
|
||||||
- days_with_registrations: Number of days with registrations
|
|
||||||
- total_days: Total number of days in range
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Validate date range
|
|
||||||
if start_date > end_date:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail="Start date must be before or equal to end date"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (end_date - start_date).days > 365:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail="Date range cannot exceed 365 days"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Querying registration summary from {start_date} to {end_date} for product {product_id}")
|
|
||||||
|
|
||||||
# Get summary from service
|
|
||||||
summary = registration_service.get_registration_summary(
|
|
||||||
start_date, end_date, product_id
|
|
||||||
)
|
|
||||||
|
|
||||||
return summary
|
|
||||||
|
|
||||||
except HTTPException:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to get registration summary: {e}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=500,
|
|
||||||
detail=f"Internal server error: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/recent-registered-users", response_model=UserRegistrationResponse)
|
|
||||||
async def get_recent_registered_users(
|
|
||||||
days: int = Query(7, ge=1, le=365, description="Number of recent days to query"),
|
|
||||||
product_id: str = Query("freeleaps", description="Product identifier")
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Get daily registered users count for recent N days
|
|
||||||
|
|
||||||
Returns registration data for the last N days from today
|
|
||||||
|
|
||||||
Example:
|
|
||||||
- GET /api/metrics/recent-registered-users?days=7
|
|
||||||
- GET /api/metrics/recent-registered-users?days=30&product_id=freeleaps
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Calculate date range
|
|
||||||
end_date = date.today()
|
|
||||||
start_date = end_date - timedelta(days=days-1)
|
|
||||||
|
|
||||||
logger.info(f"Querying recent {days} days registration data from {start_date} to {end_date} for product {product_id}")
|
|
||||||
|
|
||||||
# Get data from service
|
|
||||||
result = registration_service.get_daily_registered_users(
|
|
||||||
start_date, end_date, product_id
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Successfully retrieved recent {days} days data, total registrations: {result.total_registrations}")
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to get recent registered users: {e}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=500,
|
|
||||||
detail=f"Internal server error: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/registered-users-by-days", response_model=UserRegistrationResponse)
|
|
||||||
async def get_registered_users_by_days(
|
|
||||||
start_date: date = Query(..., description="Start date in YYYY-MM-DD format"),
|
|
||||||
days: int = Query(..., ge=1, le=365, description="Number of days from start date"),
|
|
||||||
product_id: str = Query("freeleaps", description="Product identifier")
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Get daily registered users count starting from a specific date for N days
|
|
||||||
|
|
||||||
Returns registration data for N days starting from the specified start date
|
|
||||||
|
|
||||||
Example:
|
|
||||||
- GET /api/metrics/registered-users-by-days?start_date=2024-01-01&days=7
|
|
||||||
- GET /api/metrics/registered-users-by-days?start_date=2024-09-01&days=30&product_id=freeleaps
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Calculate end date
|
|
||||||
end_date = start_date + timedelta(days=days-1)
|
|
||||||
|
|
||||||
logger.info(f"Querying registration data from {start_date} for {days} days (until {end_date}) for product {product_id}")
|
|
||||||
|
|
||||||
# Get data from service
|
|
||||||
result = registration_service.get_daily_registered_users(
|
|
||||||
start_date, end_date, product_id
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Successfully retrieved {days} days data from {start_date}, total registrations: {result.total_registrations}")
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to get registered users by days: {e}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=500,
|
|
||||||
detail=f"Internal server error: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/daily-registered-users", response_model=UserRegistrationResponse)
|
|
||||||
async def get_daily_registered_users_post(
|
|
||||||
query: UserRegistrationQuery
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Get daily registered users count for a date range (POST method)
|
|
||||||
|
|
||||||
Same as GET method but accepts parameters in request body
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Validate date range
|
|
||||||
if query.start_date > query.end_date:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail="Start date must be before or equal to end date"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (query.end_date - query.start_date).days > 365:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail="Date range cannot exceed 365 days"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Querying registration data from {query.start_date} to {query.end_date} for product {query.product_id}")
|
|
||||||
|
|
||||||
# Get data from service
|
|
||||||
result = registration_service.get_daily_registered_users(
|
|
||||||
query.start_date, query.end_date, query.product_id
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Successfully retrieved data for {len(result.dates)} days")
|
|
||||||
return result
|
|
||||||
|
|
||||||
except HTTPException:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to get daily registered users: {e}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=500,
|
|
||||||
detail=f"Internal server error: {str(e)}"
|
|
||||||
)
|
|
||||||
9
apps/metrics/webapi/routes/starrocks_metrics/__init__.py
Normal file
9
apps/metrics/webapi/routes/starrocks_metrics/__init__.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from .metrics_query import router as metrics_query_router
|
||||||
|
from .available_metrics import router as available_metrics_router
|
||||||
|
from .metric_info import router as metric_info_router
|
||||||
|
|
||||||
|
api_router = APIRouter()
|
||||||
|
api_router.include_router(available_metrics_router, tags=["starrocks-metrics"])
|
||||||
|
api_router.include_router(metrics_query_router, tags=["starrocks-metrics"])
|
||||||
|
api_router.include_router(metric_info_router, tags=["starrocks-metrics"])
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
|
from common.log.module_logger import ModuleLogger
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
# Initialize logger
|
||||||
|
module_logger = ModuleLogger(__file__)
|
||||||
|
|
||||||
|
# Product -> supported StarRocks-backed metrics
|
||||||
|
SUPPORTED_STARROCKS_METRICS_MAP = {
|
||||||
|
"freeleaps": [
|
||||||
|
"daily_registered_users",
|
||||||
|
],
|
||||||
|
"magicleaps": [
|
||||||
|
"daily_registered_users",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/starrocks/product/{product_id}/available-metrics")
|
||||||
|
async def get_available_metrics(product_id: str):
|
||||||
|
"""
|
||||||
|
Get list of available StarRocks-backed metrics for a specific product.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
product_id: Product ID to get metrics for (required).
|
||||||
|
|
||||||
|
Returns a list of metric names available via StarRocks for the specified product.
|
||||||
|
"""
|
||||||
|
await module_logger.log_info(
|
||||||
|
f"Getting StarRocks available metrics list for product_id: {product_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if product_id not in SUPPORTED_STARROCKS_METRICS_MAP:
|
||||||
|
raise HTTPException(status_code=404, detail=f"Unknown product_id: {product_id}")
|
||||||
|
|
||||||
|
metrics = SUPPORTED_STARROCKS_METRICS_MAP[product_id]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"product_id": product_id,
|
||||||
|
"available_metrics": metrics,
|
||||||
|
"total_count": len(metrics),
|
||||||
|
"description": f"List of StarRocks-backed metrics for product '{product_id}'",
|
||||||
|
}
|
||||||
53
apps/metrics/webapi/routes/starrocks_metrics/metric_info.py
Normal file
53
apps/metrics/webapi/routes/starrocks_metrics/metric_info.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
|
from common.log.module_logger import ModuleLogger
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
# Initialize logger
|
||||||
|
module_logger = ModuleLogger(__file__)
|
||||||
|
|
||||||
|
# Product -> metric -> description
|
||||||
|
STARROCKS_METRIC_DESCRIPTIONS = {
|
||||||
|
"freeleaps": {
|
||||||
|
"daily_registered_users": "Daily registered users count from StarRocks table dws_daily_registered_users",
|
||||||
|
},
|
||||||
|
"magicleaps": {
|
||||||
|
"daily_registered_users": "Daily registered users count from StarRocks table dws_daily_registered_users",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/starrocks/product/{product_id}/metric/{metric_name}/info")
|
||||||
|
async def get_metric_info(
|
||||||
|
product_id: str,
|
||||||
|
metric_name: str
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Get information about a specific StarRocks-backed metric.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
product_id: Product identifier for the product's data.
|
||||||
|
metric_name: Name of the StarRocks-backed metric.
|
||||||
|
"""
|
||||||
|
await module_logger.log_info(
|
||||||
|
f"Getting StarRocks metric info for metric '{metric_name}' from product '{product_id}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
if product_id not in STARROCKS_METRIC_DESCRIPTIONS:
|
||||||
|
raise HTTPException(status_code=404, detail=f"Unknown product_id: {product_id}")
|
||||||
|
|
||||||
|
product_metrics = STARROCKS_METRIC_DESCRIPTIONS[product_id]
|
||||||
|
if metric_name not in product_metrics:
|
||||||
|
raise HTTPException(status_code=404, detail=f"Unknown metric '{metric_name}' for product '{product_id}'")
|
||||||
|
|
||||||
|
metric_info = {
|
||||||
|
"product_id": product_id,
|
||||||
|
"metric_name": metric_name,
|
||||||
|
"description": product_metrics[metric_name],
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"metric_info": metric_info,
|
||||||
|
"description": f"Information about StarRocks metric '{metric_name}' in product '{product_id}'",
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from common.log.module_logger import ModuleLogger
|
||||||
|
from backend.services.registration_analytics_service import RegistrationService
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationDataPoint(BaseModel):
|
||||||
|
"""Single data point in registration time series."""
|
||||||
|
date: str = Field(..., description="Date in YYYY-MM-DD format")
|
||||||
|
value: int = Field(..., description="Number of registered users")
|
||||||
|
product_id: str = Field(..., description="Product identifier")
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationTimeSeriesResponse(BaseModel):
|
||||||
|
"""Response model for registration time series data."""
|
||||||
|
metric_name: str = Field(..., description="Name of the queried metric")
|
||||||
|
data_points: List[RegistrationDataPoint] = Field(..., description="List of data points")
|
||||||
|
total_points: int = Field(..., description="Total number of data points")
|
||||||
|
time_range: Dict[str, str] = Field(..., description="Start and end date of the query")
|
||||||
|
total_registrations: int = Field(..., description="Total number of registrations in the period")
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationQueryRequest(BaseModel):
|
||||||
|
"""Request model for registration query."""
|
||||||
|
product_id: str = Field("freeleaps", description="Product ID to identify which product's data to query")
|
||||||
|
start_date: str = Field(..., description="Start date in YYYY-MM-DD format")
|
||||||
|
end_date: str = Field(..., description="End date in YYYY-MM-DD format")
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
# Initialize service and logger
|
||||||
|
registration_service = RegistrationService()
|
||||||
|
module_logger = ModuleLogger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/starrocks/dru_query", response_model=RegistrationTimeSeriesResponse)
|
||||||
|
async def metrics_query(
|
||||||
|
request: RegistrationQueryRequest
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Query registration time series data.
|
||||||
|
|
||||||
|
Returns XY curve data (time series) for user registrations within the given date range.
|
||||||
|
"""
|
||||||
|
await module_logger.log_info(
|
||||||
|
f"Querying registration data for product '{request.product_id}' from {request.start_date} to {request.end_date}")
|
||||||
|
|
||||||
|
# Parse dates - handle both YYYY-M-D and YYYY-MM-DD formats
|
||||||
|
def parse_date(date_str: str) -> date:
|
||||||
|
try:
|
||||||
|
return date.fromisoformat(date_str)
|
||||||
|
except ValueError:
|
||||||
|
# Try to parse YYYY-M-D format and convert to YYYY-MM-DD
|
||||||
|
parts = date_str.split('-')
|
||||||
|
if len(parts) == 3:
|
||||||
|
year, month, day = parts
|
||||||
|
return date(int(year), int(month), int(day))
|
||||||
|
raise ValueError(f"Invalid date format: {date_str}")
|
||||||
|
|
||||||
|
start_date = parse_date(request.start_date)
|
||||||
|
end_date = parse_date(request.end_date)
|
||||||
|
|
||||||
|
# Query the registration data
|
||||||
|
result = registration_service.get_daily_registered_users(
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date,
|
||||||
|
product_id=request.product_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Format response
|
||||||
|
response = RegistrationTimeSeriesResponse(
|
||||||
|
metric_name="daily_registered_users",
|
||||||
|
data_points=[
|
||||||
|
RegistrationDataPoint(
|
||||||
|
date=date_str,
|
||||||
|
value=count,
|
||||||
|
product_id=request.product_id
|
||||||
|
)
|
||||||
|
for date_str, count in zip(result.dates, result.counts)
|
||||||
|
],
|
||||||
|
total_points=len(result.dates),
|
||||||
|
time_range={
|
||||||
|
"start": request.start_date,
|
||||||
|
"end": request.end_date
|
||||||
|
},
|
||||||
|
total_registrations=result.total_registrations
|
||||||
|
)
|
||||||
|
|
||||||
|
await module_logger.log_info(
|
||||||
|
f"Successfully queried registration data with {len(result.dates)} data points, total registrations: {result.total_registrations}")
|
||||||
|
return response
|
||||||
Loading…
Reference in New Issue
Block a user