526 lines
21 KiB
Python
526 lines
21 KiB
Python
from rest_framework import viewsets, permissions, status
|
|
from rest_framework.decorators import action
|
|
from rest_framework.response import Response
|
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
from rest_framework import filters
|
|
from django.db.models import Q, Count, Avg, Sum
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
import json
|
|
|
|
from ..models import (
|
|
Dashboard, DashboardWidget, DataSource, Report,
|
|
ReportExecution, MetricDefinition, MetricValue
|
|
)
|
|
from .serializers import (
|
|
DashboardSerializer, DashboardWidgetSerializer, DataSourceSerializer,
|
|
ReportSerializer, ReportExecutionSerializer, MetricDefinitionSerializer,
|
|
MetricValueSerializer, AnalyticsStatsSerializer, DashboardCreateSerializer,
|
|
ReportExecuteSerializer, MetricCalculateSerializer, DataSourceTestSerializer,
|
|
WidgetDataSerializer
|
|
)
|
|
from core.utils import AuditLogger
|
|
|
|
|
|
class BaseViewSet(viewsets.ModelViewSet):
|
|
"""Base ViewSet with common functionality"""
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
|
|
|
def get_queryset(self):
|
|
# Filter by tenant if user has one
|
|
if hasattr(self.request.user, 'tenant') and self.request.user.tenant:
|
|
return self.queryset.filter(tenant=self.request.user.tenant)
|
|
return self.queryset
|
|
|
|
def perform_create(self, serializer):
|
|
if hasattr(self.request.user, 'tenant'):
|
|
serializer.save(tenant=self.request.user.tenant)
|
|
else:
|
|
serializer.save()
|
|
|
|
|
|
class DataSourceViewSet(BaseViewSet):
|
|
"""ViewSet for DataSource model"""
|
|
queryset = DataSource.objects.all()
|
|
serializer_class = DataSourceSerializer
|
|
filterset_fields = ['source_type', 'is_active']
|
|
search_fields = ['name', 'description']
|
|
ordering_fields = ['name', 'last_updated']
|
|
ordering = ['name']
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def active(self, request):
|
|
"""Get active data sources"""
|
|
queryset = self.get_queryset().filter(is_active=True)
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=True, methods=['post'])
|
|
def test_connection(self, request, pk=None):
|
|
"""Test data source connection"""
|
|
data_source = self.get_object()
|
|
serializer = DataSourceTestSerializer(data={'source_id': pk})
|
|
|
|
if serializer.is_valid():
|
|
try:
|
|
# Mock connection test - in real implementation, would test actual connection
|
|
test_result = {
|
|
'success': True,
|
|
'message': 'Connection successful',
|
|
'response_time': 0.15,
|
|
'records_available': 1000
|
|
}
|
|
|
|
# Update last_updated timestamp
|
|
data_source.last_updated = timezone.now()
|
|
data_source.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='DATA_SOURCE_TESTED',
|
|
model='DataSource',
|
|
object_id=str(data_source.source_id),
|
|
details={
|
|
'source_name': data_source.name,
|
|
'test_result': 'SUCCESS'
|
|
}
|
|
)
|
|
|
|
return Response(test_result)
|
|
|
|
except Exception as e:
|
|
return Response({
|
|
'success': False,
|
|
'message': f'Connection failed: {str(e)}',
|
|
'error': str(e)
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
class MetricDefinitionViewSet(BaseViewSet):
|
|
"""ViewSet for MetricDefinition model"""
|
|
queryset = MetricDefinition.objects.all()
|
|
serializer_class = MetricDefinitionSerializer
|
|
filterset_fields = ['metric_type', 'data_source', 'is_active']
|
|
search_fields = ['name', 'description']
|
|
ordering_fields = ['name', 'metric_type']
|
|
ordering = ['name']
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def active(self, request):
|
|
"""Get active metrics"""
|
|
queryset = self.get_queryset().filter(is_active=True)
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=True, methods=['post'])
|
|
def calculate(self, request, pk=None):
|
|
"""Calculate metric value"""
|
|
metric = self.get_object()
|
|
serializer = MetricCalculateSerializer(data=request.data)
|
|
|
|
if serializer.is_valid():
|
|
try:
|
|
# Mock calculation - in real implementation, would execute actual calculation
|
|
calculated_value = 85.5 # Mock value
|
|
|
|
# Determine status based on thresholds
|
|
status_value = 'NORMAL'
|
|
if metric.threshold_critical and calculated_value >= metric.threshold_critical:
|
|
status_value = 'CRITICAL'
|
|
elif metric.threshold_warning and calculated_value >= metric.threshold_warning:
|
|
status_value = 'WARNING'
|
|
|
|
# Create metric value record
|
|
metric_value = MetricValue.objects.create(
|
|
metric=metric,
|
|
value=calculated_value,
|
|
timestamp=timezone.now(),
|
|
status=status_value,
|
|
notes=f"Calculated via API by {request.user.get_full_name()}",
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='METRIC_CALCULATED',
|
|
model='MetricValue',
|
|
object_id=str(metric_value.value_id),
|
|
details={
|
|
'metric_name': metric.name,
|
|
'calculated_value': calculated_value,
|
|
'status': status_value
|
|
}
|
|
)
|
|
|
|
return Response({
|
|
'message': 'Metric calculated successfully',
|
|
'metric_value': MetricValueSerializer(metric_value).data
|
|
})
|
|
|
|
except Exception as e:
|
|
return Response({
|
|
'error': f'Calculation failed: {str(e)}'
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
class MetricValueViewSet(viewsets.ReadOnlyModelViewSet):
|
|
"""ViewSet for MetricValue model (read-only)"""
|
|
queryset = MetricValue.objects.all()
|
|
serializer_class = MetricValueSerializer
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
|
filterset_fields = ['metric', 'status']
|
|
search_fields = ['metric__name', 'notes']
|
|
ordering_fields = ['timestamp', 'value']
|
|
ordering = ['-timestamp']
|
|
|
|
def get_queryset(self):
|
|
if hasattr(self.request.user, 'tenant') and self.request.user.tenant:
|
|
return self.queryset.filter(tenant=self.request.user.tenant)
|
|
return self.queryset
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def latest(self, request):
|
|
"""Get latest metric values"""
|
|
queryset = self.get_queryset().order_by('metric', '-timestamp').distinct('metric')
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
|
|
class DashboardViewSet(BaseViewSet):
|
|
"""ViewSet for Dashboard model"""
|
|
queryset = Dashboard.objects.all()
|
|
serializer_class = DashboardSerializer
|
|
filterset_fields = ['is_public', 'is_default', 'created_by']
|
|
search_fields = ['name', 'description']
|
|
ordering_fields = ['name', 'created_at']
|
|
ordering = ['name']
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def create_dashboard(self, request):
|
|
"""Create a dashboard with widgets"""
|
|
serializer = DashboardCreateSerializer(data=request.data)
|
|
|
|
if serializer.is_valid():
|
|
# Create dashboard
|
|
dashboard = Dashboard.objects.create(
|
|
name=serializer.validated_data['name'],
|
|
description=serializer.validated_data.get('description', ''),
|
|
layout=serializer.validated_data.get('layout', {}),
|
|
is_public=serializer.validated_data.get('is_public', False),
|
|
created_by=request.user,
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Create widgets if provided
|
|
widgets_data = serializer.validated_data.get('widgets', [])
|
|
for widget_data in widgets_data:
|
|
data_source = DataSource.objects.get(id=widget_data['data_source_id'])
|
|
|
|
DashboardWidget.objects.create(
|
|
dashboard=dashboard,
|
|
title=widget_data['title'],
|
|
description=widget_data.get('description', ''),
|
|
widget_type=widget_data['widget_type'],
|
|
data_source=data_source,
|
|
configuration=widget_data.get('configuration', {}),
|
|
position_x=widget_data.get('position_x', 0),
|
|
position_y=widget_data.get('position_y', 0),
|
|
width=widget_data.get('width', 4),
|
|
height=widget_data.get('height', 3),
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='DASHBOARD_CREATED',
|
|
model='Dashboard',
|
|
object_id=str(dashboard.dashboard_id),
|
|
details={
|
|
'dashboard_name': dashboard.name,
|
|
'widgets_count': len(widgets_data)
|
|
}
|
|
)
|
|
|
|
return Response({
|
|
'message': 'Dashboard created successfully',
|
|
'dashboard': DashboardSerializer(dashboard).data
|
|
})
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def public(self, request):
|
|
"""Get public dashboards"""
|
|
queryset = self.get_queryset().filter(is_public=True)
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def my_dashboards(self, request):
|
|
"""Get user's dashboards"""
|
|
queryset = self.get_queryset().filter(created_by=request.user)
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
|
|
class DashboardWidgetViewSet(BaseViewSet):
|
|
"""ViewSet for DashboardWidget model"""
|
|
queryset = DashboardWidget.objects.all()
|
|
serializer_class = DashboardWidgetSerializer
|
|
filterset_fields = ['dashboard', 'widget_type', 'data_source', 'is_visible']
|
|
search_fields = ['title', 'description']
|
|
ordering_fields = ['title', 'position_x', 'position_y']
|
|
ordering = ['position_y', 'position_x']
|
|
|
|
@action(detail=True, methods=['get'])
|
|
def data(self, request, pk=None):
|
|
"""Get widget data"""
|
|
widget = self.get_object()
|
|
serializer = WidgetDataSerializer(data={'widget_id': pk})
|
|
|
|
if serializer.is_valid():
|
|
try:
|
|
# Mock data generation - in real implementation, would query actual data source
|
|
mock_data = {
|
|
'CHART': {
|
|
'labels': ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
|
|
'datasets': [{
|
|
'label': 'Sample Data',
|
|
'data': [65, 59, 80, 81, 56]
|
|
}]
|
|
},
|
|
'TABLE': {
|
|
'headers': ['Name', 'Value', 'Status'],
|
|
'rows': [
|
|
['Metric 1', '85.5', 'Normal'],
|
|
['Metric 2', '92.1', 'Warning'],
|
|
['Metric 3', '78.3', 'Normal']
|
|
]
|
|
},
|
|
'KPI': {
|
|
'value': 85.5,
|
|
'target': 90.0,
|
|
'trend': 'up',
|
|
'change': '+2.3%'
|
|
},
|
|
'GAUGE': {
|
|
'value': 75,
|
|
'min': 0,
|
|
'max': 100,
|
|
'thresholds': [50, 80]
|
|
}
|
|
}
|
|
|
|
widget_data = mock_data.get(widget.widget_type, {})
|
|
|
|
return Response({
|
|
'widget_id': widget.widget_id,
|
|
'title': widget.title,
|
|
'widget_type': widget.widget_type,
|
|
'data': widget_data,
|
|
'last_updated': timezone.now()
|
|
})
|
|
|
|
except Exception as e:
|
|
return Response({
|
|
'error': f'Failed to load widget data: {str(e)}'
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
class ReportViewSet(BaseViewSet):
|
|
"""ViewSet for Report model"""
|
|
queryset = Report.objects.all()
|
|
serializer_class = ReportSerializer
|
|
filterset_fields = ['report_type', 'data_source', 'is_active', 'created_by']
|
|
search_fields = ['name', 'description']
|
|
ordering_fields = ['name', 'created_at']
|
|
ordering = ['name']
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def active(self, request):
|
|
"""Get active reports"""
|
|
queryset = self.get_queryset().filter(is_active=True)
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=True, methods=['post'])
|
|
def execute(self, request, pk=None):
|
|
"""Execute a report"""
|
|
report = self.get_object()
|
|
serializer = ReportExecuteSerializer(data=request.data)
|
|
|
|
if serializer.is_valid():
|
|
try:
|
|
# Create execution record
|
|
execution = ReportExecution.objects.create(
|
|
report=report,
|
|
executed_by=request.user,
|
|
start_time=timezone.now(),
|
|
status='RUNNING',
|
|
parameters=serializer.validated_data.get('parameters', {}),
|
|
tenant=getattr(request.user, 'tenant', None)
|
|
)
|
|
|
|
# Mock execution - in real implementation, would execute actual report
|
|
import time
|
|
time.sleep(0.1) # Simulate processing time
|
|
|
|
# Update execution with results
|
|
execution.end_time = timezone.now()
|
|
execution.status = 'COMPLETED'
|
|
execution.result_count = 150 # Mock result count
|
|
execution.file_path = f'/reports/{execution.execution_id}.json'
|
|
execution.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='REPORT_EXECUTED',
|
|
model='ReportExecution',
|
|
object_id=str(execution.execution_id),
|
|
details={
|
|
'report_name': report.name,
|
|
'result_count': execution.result_count
|
|
}
|
|
)
|
|
|
|
return Response({
|
|
'message': 'Report executed successfully',
|
|
'execution': ReportExecutionSerializer(execution).data
|
|
})
|
|
|
|
except Exception as e:
|
|
# Update execution with error
|
|
if 'execution' in locals():
|
|
execution.end_time = timezone.now()
|
|
execution.status = 'FAILED'
|
|
execution.error_message = str(e)
|
|
execution.save()
|
|
|
|
return Response({
|
|
'error': f'Report execution failed: {str(e)}'
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
class ReportExecutionViewSet(viewsets.ReadOnlyModelViewSet):
|
|
"""ViewSet for ReportExecution model (read-only)"""
|
|
queryset = ReportExecution.objects.all()
|
|
serializer_class = ReportExecutionSerializer
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
|
filterset_fields = ['report', 'executed_by', 'status']
|
|
search_fields = ['report__name', 'executed_by__first_name', 'executed_by__last_name']
|
|
ordering_fields = ['start_time', 'end_time']
|
|
ordering = ['-start_time']
|
|
|
|
def get_queryset(self):
|
|
if hasattr(self.request.user, 'tenant') and self.request.user.tenant:
|
|
return self.queryset.filter(tenant=self.request.user.tenant)
|
|
return self.queryset
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def recent(self, request):
|
|
"""Get recent executions"""
|
|
queryset = self.get_queryset().order_by('-start_time')[:20]
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
return Response(serializer.data)
|
|
|
|
|
|
class AnalyticsStatsViewSet(viewsets.ViewSet):
|
|
"""ViewSet for analytics statistics"""
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def dashboard(self, request):
|
|
"""Get analytics dashboard statistics"""
|
|
tenant_filter = {}
|
|
if hasattr(request.user, 'tenant') and request.user.tenant:
|
|
tenant_filter['tenant'] = request.user.tenant
|
|
|
|
today = timezone.now().date()
|
|
|
|
# Dashboard statistics
|
|
dashboards = Dashboard.objects.filter(**tenant_filter)
|
|
total_dashboards = dashboards.count()
|
|
|
|
# Report statistics
|
|
reports = Report.objects.filter(**tenant_filter)
|
|
total_reports = reports.count()
|
|
|
|
# Metric statistics
|
|
metrics = MetricDefinition.objects.filter(**tenant_filter)
|
|
total_metrics = metrics.count()
|
|
|
|
# Data source statistics
|
|
data_sources = DataSource.objects.filter(**tenant_filter)
|
|
active_data_sources = data_sources.filter(is_active=True).count()
|
|
|
|
# Execution statistics
|
|
executions = ReportExecution.objects.filter(**tenant_filter)
|
|
reports_executed_today = executions.filter(start_time__date=today).count()
|
|
failed_executions = executions.filter(
|
|
start_time__date=today,
|
|
status='FAILED'
|
|
).count()
|
|
|
|
# Average execution time
|
|
completed_executions = executions.filter(
|
|
status='COMPLETED',
|
|
start_time__date=today
|
|
)
|
|
avg_execution_time = 0
|
|
if completed_executions.exists():
|
|
total_time = sum([
|
|
(exec.end_time - exec.start_time).total_seconds()
|
|
for exec in completed_executions
|
|
if exec.end_time
|
|
])
|
|
avg_execution_time = total_time / completed_executions.count()
|
|
|
|
# Popular reports (top 5)
|
|
popular_reports = list(
|
|
executions.filter(start_time__gte=today - timedelta(days=30))
|
|
.values('report__name')
|
|
.annotate(execution_count=Count('id'))
|
|
.order_by('-execution_count')[:5]
|
|
)
|
|
|
|
# Metric status breakdown
|
|
metric_values = MetricValue.objects.filter(**tenant_filter)
|
|
latest_values = metric_values.order_by('metric', '-timestamp').distinct('metric')
|
|
metric_status_breakdown = latest_values.values('status').annotate(
|
|
count=Count('id')
|
|
).order_by('-count')
|
|
|
|
stats = {
|
|
'total_dashboards': total_dashboards,
|
|
'total_reports': total_reports,
|
|
'total_metrics': total_metrics,
|
|
'active_data_sources': active_data_sources,
|
|
'reports_executed_today': reports_executed_today,
|
|
'failed_executions': failed_executions,
|
|
'avg_execution_time': round(avg_execution_time, 2),
|
|
'popular_reports': popular_reports,
|
|
'metric_status_breakdown': {
|
|
item['status']: item['count']
|
|
for item in metric_status_breakdown
|
|
}
|
|
}
|
|
|
|
serializer = AnalyticsStatsSerializer(stats)
|
|
return Response(serializer.data)
|
|
|